rollout

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: Apache-2.0 Imports: 17 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 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

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.

Variables

View Source
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
	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) 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

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 string, at time.Time) error

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

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.

Jump to

Keyboard shortcuts

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