Help us shape the future of Go! Take the 2026 Go Developer Survey

rollout

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package rollout reads Deployment rollout completeness, and reports current Job/CronJob state, through the typed client-go clientset (k8s.io/api/apps/v1, k8s.io/api/batch/v1) — no dynamic client needed, since these are stable, already-vendored Go types — following the same client-go adaptor shape pkg/k8s established (cluster.go/kubeconfig.go/fake.go): a thin wrapper over kubernetes.Interface, redaction at every error boundary, a fake clientset for tests. Nothing here knows what a "promotion" is or what image a caller wanted — that comparison is internal/engine's job (AGENTS.md §4.3: pkg/* is activity-shaped, with no domain knowledge of the orchestration above it).

This package reads cluster facts, with exactly one exception: Restart stamps a Deployment's, StatefulSet's or DaemonSet's pod-template restart annotation, which is what makes its pods roll. That is the whole of the write surface here, and it holds the same position pkg/argo.Refresh holds there — one named write beside otherwise read-only methods, so "does this package write" has a short answer rather than a survey.

The rollout-completeness checks are deploymentRolloutComplete below and its StatefulSet and DaemonSet siblings in workload.go: logic ported from k8s.io/kubectl/pkg/polymorphichelpers/rollout_status.go (Apache License 2.0) rather than importing k8s.io/kubectl itself for the ~100 lines this needs (AGENTS.md §4.7).

Index

Constants

View Source
const (
	KindDeployment  = "Deployment"
	KindStatefulSet = "StatefulSet"
	KindDaemonSet   = "DaemonSet"
)

The workload kinds Restart can roll: the three apps/v1 controllers whose pods come from a pod template that `kubectl rollout restart` stamps. A Job or CronJob has no rollout to trigger, and re-running one is a different operation with different consequences, so neither is here.

View Source
const RestartAnnotation = "kubectl.kubernetes.io/restartedAt"

RestartAnnotation is the pod-template annotation Restart stamps. Deliberately kubectl's own key: `kubectl rollout restart` writes exactly this, so a restart hoist causes is indistinguishable from one an operator caused by hand, and either tool can see the other's.

Nothing in Kubernetes treats the key specially. Any change to the pod template starts a rollout; this one is chosen because it changes nothing else.

View Source
const RestartStampLayout = "2006-01-02T15:04:05.000000000Z07:00"

RestartStampLayout is how Restart renders its timestamp: RFC3339 with a fixed nine fractional digits. See Restart for why the sub-second part is load-bearing.

View Source
const StrategyOnDelete = "OnDelete"

StrategyOnDelete is the update strategy, available to StatefulSets and DaemonSets, under which the controller never replaces a pod itself: a changed pod template reaches a pod only when that pod is deleted. A restart stamp on such a workload is written and then does nothing on its own, which is why both GracefulRestartConcerns and the rollout check name it rather than treat it as either a rollout in progress or a finished one.

Variables

View Source
var ErrNotFound = errors.New("rollout: object not found")

ErrNotFound is wrapped by Deployment, Workload, JobLike and Restart when the named object does not exist. For Restart it is terminal: a workload that is not there was not restarted, and no retry changes that.

Functions

func IsRestartable added in v0.3.0

func IsRestartable(kind string) bool

IsRestartable reports whether kind is one RestartableKinds lists.

func RestartableKinds added in v0.3.0

func RestartableKinds() []string

RestartableKinds lists the kinds Restart and Workload accept, in the order a restart lists them. A fresh slice per call, so no caller can change what another one sees.

Types

type ContainerImage

type ContainerImage struct {
	Name  string // the container's own name field
	Init  bool   // an initContainer
	Image string
}

ContainerImage is one container's current, live image reference, as the Deployment's own spec.template.spec.containers[]/initContainers[].image field holds it — the field a promotion actually writes (AGENTS.md invariant 4), not a runtime pod status.

type DeploymentStatus

type DeploymentStatus struct {
	Namespace, Name string
	Images          []ContainerImage
	// Complete reports whether the rollout has finished, by kubectl's own four-condition test
	// (deploymentRolloutComplete). DeadlineExceeded is the one condition kubectl treats as a
	// hard failure rather than "still rolling out" — the Deployment's own progressDeadlineSeconds
	// has been exceeded — and is reported here rather than as a Go error, since it is a fact
	// about cluster state, not a call failure.
	Complete         bool
	DeadlineExceeded bool
	// Detail is a short, kubectl-style human-readable line: what's still pending, or why the
	// deadline was exceeded, or that the rollout finished.
	Detail string
	// RestartedAt is the pod template's RestartAnnotation value, "" when it has never been
	// restarted this way. Read so a restart can say what it supersedes, and so a caller can
	// tell its own stamp from someone else's.
	RestartedAt string
	// The fields below describe whether a restart of this Deployment can actually be graceful
	// (WorkloadStatus.GracefulRestartConcerns, through DeploymentStatus.Workload).
	// They are read here because the read that fetches them is already being made; hoist warns
	// on them and never blocks (AGENTS.md principle 5), since "roll it anyway" is a legitimate
	// thing to want and the operator is the one who knows.
	//
	// Replicas is spec.replicas (1 when unset, matching Kubernetes' own default). Strategy is
	// spec.strategy.type ("RollingUpdate" when unset). MaxUnavailable and MaxSurge are that
	// strategy's own values already RESOLVED against Replicas by Kubernetes' own rules —
	// percentages round down for unavailable and up for surge — because the raw 25%/25%
	// defaults say nothing on their own: at one replica they resolve to 0 and 1, which is
	// precisely the case where a naive "one replica means downtime" claim is wrong.
	// ReadinessProbes counts containers that declare one.
	Replicas        int32
	Strategy        string
	MaxUnavailable  int32
	MaxSurge        int32
	ReadinessProbes int
}

DeploymentStatus is one Deployment's current rollout state, read straight from its own object — no domain knowledge of what any caller wanted it to say.

func (DeploymentStatus) Workload added in v0.3.0

func (d DeploymentStatus) Workload() WorkloadStatus

Workload converts a Deployment's read into the WorkloadStatus a restart works with.

type Fake

type Fake struct {
	Deployments map[depKey]DeploymentStatus
	// Workloads holds StatefulSets and DaemonSets, keyed by kind. A Deployment always lives in
	// Deployments, and Workload for one reads it from there, so a Deployment has one record in
	// the fake as it has one object in a cluster.
	Workloads map[jobKey]WorkloadStatus
	JobLikes  map[jobKey]JobLikeStatus
	// DeploymentErr, WorkloadErr and JobLikeErr, when set, are returned by every call to the
	// matching method instead of the configured/zero-value behavior. A Workload read of a
	// Deployment is a Deployment read and honours DeploymentErr; WorkloadErr covers the other
	// kinds.
	DeploymentErr, WorkloadErr, JobLikeErr, RestartErr error
	// Strict makes an unknown Deployment (or StatefulSet, or DaemonSet) return ErrNotFound, as the real client does, instead
	// of a zero-value status. Opt-in rather than the default because most tests here configure
	// only the Deployments they care about and rely on the zero value for the rest — but a test
	// about ABSENCE cannot use a fake that reports everything as present, and shipping code that
	// distinguishes the two deserves a fake that can too.
	Strict bool
	// OnRestart, when set, runs on every successful Restart — how a test models what the real
	// API server does next (the pods actually rolling), since the fake has no controller.
	OnRestart func(namespace, name string, at time.Time)
	// RestartLandsDespiteErr models the one outcome a patch cannot report: the API server
	// committed the write and the response was lost. With it set alongside RestartErr, the
	// stamp is recorded AND the error returned — which is the only way to exercise a caller's
	// did-it-actually-land recovery. Without it, RestartErr means the write did not happen.
	RestartLandsDespiteErr bool

	Calls []string
	// contains filtered or unexported fields
}

Fake is an in-memory Rollout for tests in other packages (internal/engine's step tests in particular — mirroring pkg/argo.Fake/pkg/forge.Fake/pkg/git's test doubles). An unconfigured Deployment or JobLike reports the zero status with a nil error — mirroring pkg/k8s.Fake's RunningImages (a legitimately-empty answer, not a NotFound), since a test that hasn't configured one is not opting into the not-found scenario; a test that wants ErrNotFound sets DeploymentErr/JobLikeErr itself. Calls records every method invocation, in order.

func (*Fake) Deployment

func (f *Fake) Deployment(_ context.Context, namespace, name string) (DeploymentStatus, error)

Deployment implements Rollout.

func (*Fake) JobLike

func (f *Fake) JobLike(_ context.Context, namespace, name, kind string) (JobLikeStatus, error)

JobLike implements Rollout.

func (*Fake) Restart

func (f *Fake) Restart(_ context.Context, namespace, name, kind string, at time.Time) error

Restart implements Rollout: records the call ("Restart <Kind> <ns>/<name>"), stamps the recorded status's annotation so a later read can see it, and runs OnRestart.

func (*Fake) SetDeployment

func (f *Fake) SetDeployment(namespace, name string, st DeploymentStatus)

SetDeployment records namespace/name's current status, thread-safely.

func (*Fake) SetJobLike

func (f *Fake) SetJobLike(namespace, name, kind string, st JobLikeStatus)

SetJobLike records namespace/name/kind's current status, thread-safely.

func (*Fake) SetWorkload added in v0.3.0

func (f *Fake) SetWorkload(st WorkloadStatus)

SetWorkload records a StatefulSet's or DaemonSet's current status under st's own Kind, Namespace and Name, thread-safely. A Deployment is recorded with SetDeployment instead, and SetWorkload panics on one rather than keep a second copy the reads would never see.

func (*Fake) Workload added in v0.3.0

func (f *Fake) Workload(ctx context.Context, namespace, name, kind string) (WorkloadStatus, error)

Workload implements Rollout. A Deployment is read exactly as Deployment reads it, and recorded as the same "Deployment <ns>/<name>" call; the other kinds record "<Kind> <ns>/<name>".

type JobLikeStatus

type JobLikeStatus struct {
	Namespace, Name, Kind string // Kind is "Job" or "CronJob"
	Detail                string
}

JobLikeStatus is a Job or CronJob's current state, report-only (AGENTS.md invariant 4: hoist never gates on these, only surfaces them).

type Rollout

type Rollout interface {
	// Deployment reads namespace/name's current images and rollout completeness.
	Deployment(ctx context.Context, namespace, name string) (DeploymentStatus, error)
	// Workload reads namespace/name's current state for a restart, for any kind in
	// RestartableKinds. A Deployment's answer is Deployment's own, converted.
	Workload(ctx context.Context, namespace, name, kind string) (WorkloadStatus, error)
	// JobLike reads namespace/name's current state, for kind "Job" or "CronJob".
	JobLike(ctx context.Context, namespace, name, kind string) (JobLikeStatus, error)
	// Restart rolls namespace/name's pods without changing what it runs, by stamping the pod
	// template's restart annotation — exactly what `kubectl rollout restart` does — for any kind
	// in RestartableKinds. It is the ONE write in this package; everything else here reads (see
	// the package doc, and pkg/argo.Refresh, which holds the same position there).
	Restart(ctx context.Context, namespace, name, kind string, at time.Time) error
}

Rollout is what internal/engine's RolledOutStep, `hoist watch` and `hoist restart` need from the cluster's workloads. Every method reads except Restart, which is this package's only write — see its own doc comment, and the package doc above.

func FromClientset

func FromClientset(cs kubernetes.Interface, hide ...string) Rollout

FromClientset wraps an existing clientset — client-go's fake in tests. Every string in hide is scrubbed from every error message the returned Rollout produces.

func NewFromKubeconfig

func NewFromKubeconfig(kubeconfigContext string) (Rollout, string, error)

NewFromKubeconfig builds a Rollout over the user's kubeconfig, the same loading rules and the same "duplicated on purpose, not shared" reasoning as pkg/argo.NewFromKubeconfig's doc comment explains (pkg/k8s.NewCluster's own shape, without a cross-package dependency between self-contained adaptors — AGENTS.md §4.3). The second result is the context in use.

type WorkloadStatus added in v0.3.0

type WorkloadStatus struct {
	Kind, Namespace, Name string
	Images                []ContainerImage
	// Complete and DeadlineExceeded mean what they mean on DeploymentStatus, by kubectl's own
	// per-kind `rollout status` test. Only a Deployment has a progress deadline. An OnDelete
	// workload is never Complete: its rollout does not happen until someone deletes its pods.
	Complete         bool
	DeadlineExceeded bool
	Detail           string
	// RestartedAt is the pod template's RestartAnnotation value, "" when never restarted this way.
	RestartedAt string
	// Replicas is spec.replicas for a Deployment or StatefulSet (1 when unset), and for a
	// DaemonSet status.desiredNumberScheduled — one pod per node it should run on, which is the
	// number a DaemonSet's own maxUnavailable and maxSurge are resolved against.
	Replicas int32
	// Strategy is spec.strategy.type for a Deployment and spec.updateStrategy.type otherwise,
	// with Kubernetes' own default ("RollingUpdate") when unset.
	Strategy string
	// MaxUnavailable and MaxSurge are resolved against Replicas by the kind's own controller
	// rules (DeploymentStatus documents a Deployment's; see Workload for the others). A
	// StatefulSet never surges, so its MaxSurge is always 0.
	MaxUnavailable int32
	MaxSurge       int32
	// Partition is a StatefulSet's rollingUpdate.partition: pods with an ordinal below it keep
	// the old template through a rolling update. 0 for every other kind. It is an ordinal, not a
	// count: with spec.ordinals.start the pods are numbered from OrdinalStart.
	Partition int32
	// OrdinalStart is a StatefulSet's spec.ordinals.start (0 when unset, and for every other
	// kind): its pods are numbered OrdinalStart .. OrdinalStart+Replicas-1.
	OrdinalStart int32
	// ParallelPods is true for a StatefulSet whose podManagementPolicy is Parallel, the one
	// policy under which its rollingUpdate.maxUnavailable is honoured; under the default
	// OrderedReady pods are replaced one at a time whatever that field says.
	ParallelPods bool
	// ReadinessProbes counts containers that declare one.
	ReadinessProbes int
}

WorkloadStatus is one restartable workload's current state — a Deployment, StatefulSet or DaemonSet — read straight from its own object, with no knowledge of what any caller wanted it to say. It is what a restart reads before writing, and again while watching what followed.

DeploymentStatus stays the read RolledOutStep and `hoist watch` use; a Deployment's WorkloadStatus is that same read (DeploymentStatus.Workload), so the two cannot disagree about one Deployment.

func (WorkloadStatus) GracefulRestartConcerns added in v0.3.0

func (w WorkloadStatus) GracefulRestartConcerns() []string

GracefulRestartConcerns lists, in a stable order, the reasons a restart of this workload is unlikely to be seamless — or, for an OnDelete workload, will not happen on its own at all. Empty when there is nothing to say. Informational: the caller shows them and proceeds (principle 5).

Each is stated only when it is actually true of this workload's own settings. An earlier version warned "only 1 replica: nothing serves while the new pod starts" on replica count alone, which is wrong for a Deployment under the default strategy: 25% maxUnavailable of one replica rounds down to zero, so the old pod keeps serving until the new one is ready. A warning that fires on a workload that is in fact fine is worse than none, because it teaches the operator to skip reading them. The same rule is why a StatefulSet's maxUnavailable is consulted only under the Parallel policy, the one where the controller honours it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL