Documentation
¶
Overview ¶
Package plan computes and renders a preview of the changes a deploy would make, comparing the rendered manifest for an environment against the manifest stored in the last successful Helm release.
ComputeDiff is the diff engine: it parses two rendered Kubernetes manifests, matches resources by (apiVersion, kind, namespace, name), and runs github.com/homeport/dyff field-by-field on resources present on both sides. Plan is the resulting domain model, consumed by a text renderer (RenderText) and a JSON renderer (NewJSONDocument).
Rendering the chart itself lives on deployah.dev/deployah/internal/helm.Client instead, since `deployah plan` and `deployah deploy` share that one rendering engine.
Index ¶
- Variables
- func ApplyMasking(p *Plan)
- func CountResources(manifest string) (int, error)
- func HooksChanged(previous, current []*v1.Hook) bool
- func LastSuccessfulRelease(ctx context.Context, client historyClient, project, environment string) (release *v1.Release, warning string, err error)
- func RenderJSON(w io.Writer, p *Plan) error
- func RenderText(w io.Writer, p *Plan, opts TextOptions) error
- type Action
- type BuildClient
- type Change
- type FieldChangeKind
- type FieldDiff
- type Header
- type JSONChange
- type JSONDocument
- type JSONField
- type JSONSummary
- type Mode
- type PathSegment
- type Plan
- type ResourceYAML
- type Summary
- type TextOptions
Constants ¶
This section is empty.
Variables ¶
var ErrChangesPresent = errors.New("plan has pending changes")
ErrChangesPresent is returned by the `deployah plan` command when --detailed-exitcode is set and the computed plan has at least one change. internal/cmd/root.go checks for it with errors.Is to translate it into exit code 2 instead of the generic exit code 1.
Functions ¶
func ApplyMasking ¶
func ApplyMasking(p *Plan)
ApplyMasking flags every FieldDiff belonging to a Secret resource's data or stringData block as Masked. It must run on every Plan before display, including --output json, since the JSON schema always masks secrets: see FieldDiff.
Masking is based on resource kind and field location, not on where a value came from during templating, so it can't be bypassed by routing a secret value through the chart differently.
func CountResources ¶
CountResources reports how many Kubernetes resources a rendered manifest contains, using the same parsing ComputeDiff uses. `deployah plan --offline` has no prior release to diff against, so it reports this count instead of a per-resource change list.
func HooksChanged ¶
HooksChanged reports whether the set of Helm hooks differs between two releases (added, removed, or a hook whose manifest content changed). It is not part of ComputeDiff because Hooks live outside the "---"-separated manifest string (deployah.dev/deployah/internal/render.RenderResult.Manifest never includes them); the caller compares the two hook slices it already has from the previous release and the current render.
func LastSuccessfulRelease ¶
func LastSuccessfulRelease(ctx context.Context, client historyClient, project, environment string) (release *v1.Release, warning string, err error)
LastSuccessfulRelease walks a release's history, newest revision first, and returns the newest revision whose status is "deployed" or "superseded": the manifest a plan should diff the current render against. warning is set when the newest revision itself isn't successful, so the caller can surface that alongside the older successful revision actually used for the diff.
func RenderJSON ¶
RenderJSON writes p to w as pretty-printed format_version "1.0" JSON; see NewJSONDocument.
func RenderText ¶
func RenderText(w io.Writer, p *Plan, opts TextOptions) error
RenderText writes a human-readable rendering of p to w: a header block describing the target release, one line per changed resource with its field-level changes indented underneath, and a trailing summary line.
RenderText calls ApplyMasking itself (safe to repeat) so a caller can never forget it and leak a secret; opts.ShowSecrets is the only way to see a masked value in text output.
Types ¶
type Action ¶
type Action string
Action classifies how a resource changes between the previous and current manifest.
const ( // ActionAdd means the resource exists in the current manifest but not in // the previous one. ActionAdd Action = "add" // ActionChange means the resource exists on both sides with at least one // field difference. ActionChange Action = "change" // ActionDestroy means the resource exists in the previous manifest but // not in the current one. ActionDestroy Action = "destroy" )
type BuildClient ¶
type BuildClient interface {
RenderManifests(ctx context.Context, manifest *spec.Spec, environment string, resolved *spec.ResolvedSpec, postRenderer postrenderer.PostRenderer) (*render.RenderResult, func(), error)
// contains filtered or unexported methods
}
BuildClient is the subset of deployah.dev/deployah/internal/session.HelmClient that BuildPlan needs: render the chart client-side and read release history. Defined narrowly, like [historyClient], so this package does not depend on internal/session and tests can inject a minimal fake.
type Change ¶
type Change struct {
Action Action
Kind string
APIVersion string
Name string
Namespace string
// Fields is empty for [ActionAdd] and [ActionDestroy]; the whole
// resource is the change in those cases.
Fields []FieldDiff
}
Change describes one Kubernetes resource that differs between the previous and current manifest.
type FieldChangeKind ¶
type FieldChangeKind string
FieldChangeKind classifies one field-level difference within a resource.
const ( // FieldAdded means the field is present in the current resource only. FieldAdded FieldChangeKind = "added" // FieldChanged means the field's value differs between the two resources. FieldChanged FieldChangeKind = "changed" // FieldRemoved means the field is present in the previous resource only. FieldRemoved FieldChangeKind = "removed" )
type FieldDiff ¶
type FieldDiff struct {
Path string
ChangeKind FieldChangeKind
Old string
New string
Masked bool
// Segments is Path broken into its structured parts, so a renderer can
// reconstruct the real nested manifest shape (ModeYAML) instead of
// working from the flattened dot string. Derived from dyff's own
// ytbx.PathElement list, which is the only source that distinguishes a
// plain map key from a named list-entry identifier -- the flattened
// Path string alone cannot tell "containers.web" apart from a map with
// a literal key "web".
Segments []PathSegment
}
FieldDiff is one field-level difference inside a Change, using dyff's dot-style, name-keyed path notation. Old is meaningless when ChangeKind is FieldAdded, New when it's FieldRemoved. When Masked is true, Old and New still hold the real values (for --show-secrets); the JSON renderer must always omit them regardless.
type Header ¶
type Header struct {
Project string
Environment string
Release string
Namespace string
Context string
// Revision is the current (last successful) release revision. It is
// meaningless when FreshInstall is true.
Revision int
// FreshInstall is true when no prior successful release exists, so
// every resource in the current manifest renders as an addition.
FreshInstall bool
// Warning is a non-fatal note about the release history, e.g. that the
// latest revision failed or is pending and the plan compares against an
// older successful revision instead.
Warning string
}
Header carries the identifying and contextual information shown above the list of changes: which project, environment, release, and cluster this plan describes.
type JSONChange ¶
type JSONChange struct {
Action Action `json:"action"`
Kind string `json:"kind"`
APIVersion string `json:"api_version"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Fields []JSONField `json:"fields"`
}
JSONChange is one entry in JSONDocument.Changes.
type JSONDocument ¶
type JSONDocument struct {
FormatVersion string `json:"format_version"`
Project string `json:"project"`
Environment string `json:"environment"`
Release string `json:"release"`
Namespace string `json:"namespace"`
Context string `json:"context"`
// Revision is null when FreshInstall is true: there is no current
// revision to report yet.
Revision *int `json:"revision"`
FreshInstall bool `json:"fresh_install"`
Warning string `json:"warning,omitempty"`
Changes []JSONChange `json:"changes"`
// HooksChanged is true when Helm hooks changed without a matching entry
// in Changes, so a hook-only change still explains a non-zero
// --detailed-exitcode against an otherwise-empty Changes/Summary.
HooksChanged bool `json:"hooks_changed,omitempty"`
Summary JSONSummary `json:"summary"`
// Drift and DriftIncomplete are omitted when there is nothing to report
// (either --drift wasn't requested, or it found nothing); the schema
// does not distinguish those two cases.
Drift []JSONChange `json:"drift,omitempty"`
DriftIncomplete []string `json:"drift_incomplete,omitempty"`
}
JSONDocument is the "--output json" wire format for a Plan (format_version "1.0"). Field names use snake_case.
func NewJSONDocument ¶
func NewJSONDocument(p *Plan) *JSONDocument
NewJSONDocument converts p into the format_version "1.0" JSON document. It masks secret field values unconditionally (calling ApplyMasking is safe to repeat): JSON output ignores --show-secrets by design, so a CI job can pipe it anywhere without a credential-leak review.
type JSONField ¶
type JSONField struct {
Path string `json:"path"`
Old string `json:"old,omitempty"`
New string `json:"new,omitempty"`
Masked bool `json:"masked,omitempty"`
Change string `json:"change,omitempty"`
}
JSONField is one entry in JSONChange.Fields. A masked field omits Old and New and carries Change (the FieldChangeKind as a string) instead; an unmasked field carries Old/New and omits Masked and Change.
type JSONSummary ¶
type JSONSummary struct {
Add int `json:"add"`
Change int `json:"change"`
Destroy int `json:"destroy"`
}
JSONSummary is JSONDocument.Summary.
type Mode ¶
type Mode int
Mode selects how RenderText displays field paths and values.
const ( // ModeCompact maps common Kubernetes paths to Deployah's own spec // vocabulary (e.g. "spec.template.spec.containers.web.image" becomes // "image"), falling back to the raw dyff path for anything unmapped. // This is the default. ModeCompact Mode = iota // ModeRaw always shows the raw dyff dot-style path, e.g. // "spec.template.spec.containers.web.image", bypassing ModeCompact's // vocabulary mapping. This is `deployah plan --raw`. ModeRaw // ModeYAML shows every changed field as a YAML block (path on its own // line, old/new values indented underneath) instead of a single // flattened "path: old -> new" line, for both scalar and nested // map/list values. This is `deployah plan --yaml`. ModeYAML )
type PathSegment ¶
type PathSegment struct {
// Name is the map key, or -- when ListKey is set -- the value that
// identifies one entry in a named-entry list (e.g. "web" in a
// container list entry matched by its "name" field).
Name string
// ListKey is the identifying field name for a named-entry list item
// (almost always "name" for Kubernetes; occasionally "key" or another
// field dyff detected as unique). Empty for a plain map key or a
// positional list index.
ListKey string
// Idx is the positional index into a list whose entries dyff could not
// match by identity (e.g. a plain string list like `command`). -1 for
// a map key or a named list-entry segment.
Idx int
}
PathSegment is one element of a FieldDiff's structured path.
type Plan ¶
type Plan struct {
Header Header
Changes []Change
Summary Summary
// HooksChanged is true when the release's Helm hooks differ between the
// previous and current render but are not otherwise represented in
// Changes (hooks are not regular cluster resources tracked by the diff).
HooksChanged bool
// DriftChecked is true when `--drift` ran, regardless of outcome, so
// renderers can tell "checked, found nothing" from "not requested" even
// though Drift is empty in both cases.
DriftChecked bool
// Drift lists fields that differ between a server-side apply
// prediction and a resource's live state, but are not already
// explained by Changes. See deployah.dev/deployah/internal/drift.
Drift []Change
// DriftIncomplete lists resource labels drift could not be checked for
// (e.g. missing RBAC), so the plan can say it is incomplete instead of
// silently omitting them.
DriftIncomplete []string
}
Plan is the full result of comparing a previous manifest (the last successful release, or none on a fresh install) against a freshly rendered current manifest.
func BuildPlan ¶
func BuildPlan(ctx context.Context, client BuildClient, manifest *spec.Spec, environment, clusterContext string, resolved *spec.ResolvedSpec, postRenderer postrenderer.PostRenderer) (*Plan, *render.RenderResult, func(), error)
BuildPlan renders manifest for environment via client and diffs the result against the last successful release, returning the fully populated Plan (Header included) alongside the render result. It is the single render-diff-header pipeline shared by `deployah plan` and the plan `deployah deploy` shows before confirming.
The caller must invoke the returned cleanup func once done with result.ChartPath (same contract as helm.Client.RenderManifests). On error, cleanup is still returned when a chart was prepared and must be called. postRenderer, when non-nil, is forwarded to RenderManifests so extras appear in the diff.
func ComputeDiff ¶
ComputeDiff parses previous and current as "---"-separated multi-document Kubernetes manifests and returns the resulting Plan's Changes and Summary. previous may be the empty string (a fresh install), in which case every resource in current shows as an addition. The returned Plan's Header is always the zero value; the caller fills it in from what LastSuccessfulRelease and the render step already know.
func (*Plan) HasChanges ¶
HasChanges reports whether applying this plan would change the cluster: any resource-level change, or a hook-only change.
type ResourceYAML ¶
type ResourceYAML struct {
// Label is "Kind/name" (or "Kind/namespace/name" for namespaced
// resources), the same compact identifier [ComputeDiff] uses to key
// resources internally.
Label string
// YAML is the resource's own manifest, on its own (no "---" separator
// or sibling documents).
YAML string
}
ResourceYAML is one Kubernetes resource extracted from a rendered manifest, re-encoded as a standalone single-document YAML string.
func SplitResources ¶
func SplitResources(manifest string) ([]ResourceYAML, error)
SplitResources splits a rendered manifest into one ResourceYAML per contained Kubernetes resource, using the same parsing ComputeDiff uses. It backs drift detection (deployah.dev/deployah/internal/drift), which runs a server-side apply dry-run against each resource individually.
type Summary ¶
Summary counts changes by action for the trailer line ("Plan: 1 to add, ...").
type TextOptions ¶
type TextOptions struct {
Mode Mode
// ShowSecrets reveals the real value of fields [ApplyMasking] flagged.
// The caller must refuse this on a non-interactive terminal and must
// never set it together with JSON output; RenderText itself applies it
// unconditionally once given.
ShowSecrets bool
// Theme colors the +/~/- resource lines, header labels, and warning/
// note text. The zero value renders every style call as the terminal
// default, so callers that don't set this keep plain, uncolored output.
Theme theme.ResolvedTheme
}
TextOptions controls how RenderText formats a Plan.