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 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 check is deploymentRolloutComplete below: logic ported from k8s.io/kubectl/pkg/polymorphichelpers/rollout_status.go (Apache License 2.0) rather than importing k8s.io/kubectl itself for the ~60 lines this needs (AGENTS.md §4.7).
Index ¶
- Constants
- Variables
- type ContainerImage
- type DeploymentStatus
- type Fake
- func (f *Fake) Deployment(_ context.Context, namespace, name string) (DeploymentStatus, error)
- func (f *Fake) JobLike(_ context.Context, namespace, name, kind string) (JobLikeStatus, error)
- func (f *Fake) Restart(_ context.Context, namespace, name string, at time.Time) error
- func (f *Fake) SetDeployment(namespace, name string, st DeploymentStatus)
- func (f *Fake) SetJobLike(namespace, name, kind string, st JobLikeStatus)
- type JobLikeStatus
- type Rollout
Constants ¶
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.
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.
Variables ¶
var ErrNotFound = errors.New("rollout: object not found")
ErrNotFound is wrapped by Deployment, JobLike and Restart when the named object does not exist. For Restart it is terminal: a Deployment that is not there was not restarted, and no retry changes that.
Functions ¶
This section is empty.
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.
// 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
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) GracefulRestartConcerns ¶
func (d DeploymentStatus) GracefulRestartConcerns() []string
GracefulRestartConcerns lists, in a stable order, the reasons a restart of this Deployment is unlikely to be seamless. 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 Deployment's own settings. An earlier version warned "only 1 replica: nothing serves while the new pod starts" on replica count alone, which is wrong 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 Deployment that is in fact fine is worse than none, because it teaches the operator to skip reading them.
type Fake ¶
type Fake struct {
Deployments map[depKey]DeploymentStatus
JobLikes map[jobKey]JobLikeStatus
// DeploymentErr and JobLikeErr, when set, are returned by every call to the matching
// method instead of the configured/zero-value behavior.
DeploymentErr, JobLikeErr, RestartErr error
// Strict makes an unknown Deployment 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 ¶
Deployment implements Rollout.
func (*Fake) Restart ¶
Restart implements Rollout: records the call, stamps the recorded status's annotation so a later Deployment 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.
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)
// 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. 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 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 ¶
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.