application

package
v1.0.0-rc1 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package application defines the Application spec, its observed status, and the JSON both serialise to. It is the wire contract shared by the config loader, the reconciler, the HTTP API and the CLI.

Per D3 the API is designed UI-first, and these types are that design: a shape that is awkward to render is a bug here rather than in a handler.

The package is deliberately top-level rather than internal. Per D6 a private companion repository imports this one, and Go's internal rule is per-module, so anything the companion touches cannot live under internal/ — the same reason Eldara-Tech/swarmcli has no internal/ directory at all.

It depends on nothing but the standard library and a YAML decoder: not on Docker, not on the reconciler, not on the CE charts package. The types that mirror charts concepts are re-expressed here so that the API's shape does not move when CE's does.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AppSetStatus

type AppSetStatus struct {
	// Mode is how the set is sourced, as the deployment configured it —
	// "static", "git" or "path". It is a label passed in rather than something
	// the source infers about itself, so the bootstrap's own vocabulary is what
	// an operator sees reflected back.
	Mode string `json:"mode"`

	// Source is what the bootstrap was pointed at, for a human: the repository,
	// the revision it tracks and the file within it, or the directory and file
	// an external process keeps current. Mode alone cannot be checked against
	// anything, and "which branch is this controller following" is the first
	// question a set that does not look right raises — and the one thing nothing
	// in git can change (D-f of #47).
	Source string `json:"source,omitempty"`

	// Revision is the commit the running set was loaded from. Empty when the
	// set does not come from a repository.
	Revision string `json:"revision,omitempty"`

	// LoadedAt is when the running set last loaded successfully — not when it
	// was last checked. A load that changed nothing does not move it, so the
	// pair with Error answers "how old is what I am running".
	LoadedAt time.Time `json:"loadedAt"`

	// Error is why the last attempt failed: a load that was refused, or the
	// applications a diff could not apply. Cleared by the next attempt that
	// succeeds.
	Error string `json:"error,omitempty"`

	// Stale reports that the running set is a last-good one and a newer version
	// is being refused. It is the field a UI colours; Error is what it shows
	// beside it. An error with Stale false is one of two different problems: a
	// set that loaded but could not be fully applied, or — with Applications at
	// zero and LoadedAt unset — a controller that has never managed to load one,
	// which is the louder of the three.
	Stale bool `json:"stale"`

	// Orphaned names applications that left the set. Their loops are stopped and
	// their stacks are still deployed and no longer reconciled by anyone —
	// reported rather than removed, which is what pruning them is for. In
	// memory only: a restart forgets them, because nothing here has a store.
	Orphaned []string `json:"orphaned,omitempty"`
}

AppSetStatus is where the set of applications came from and how that is going.

type ChartSource

type ChartSource struct {
	Release      string           `json:"release" yaml:"release"`
	Path         string           `json:"path,omitempty" yaml:"path,omitempty"` // chart directory within the repo
	Ref          string           `json:"ref,omitempty" yaml:"ref,omitempty"`   // repo/chart
	Version      string           `json:"version,omitempty" yaml:"version,omitempty"`
	Values       []string         `json:"values,omitempty" yaml:"values,omitempty"` // paths within the repo
	Repositories []RepositorySpec `json:"repositories,omitempty" yaml:"repositories,omitempty"`
}

ChartSource is the "one application, one chart" case that does not deserve a release file in the repository. Its rules are the ones charts itself enforces: Version is required for a repository reference and forbidden for a path, because a floating pin would silently upgrade production on the next reconcile.

type Compat

type Compat struct {
	Status   CompatState `json:"status"`
	Required string      `json:"required,omitempty"`
	Engine   string      `json:"engine,omitempty"`
	Reason   string      `json:"reason,omitempty"`
}

Compat is a chart's declared swarmcliVersion verdict. Planning records it and never enforces it, so an unattended controller has to surface it or the operator never learns the chart wanted a newer engine.

type CompatState

type CompatState string

CompatState is a chart's swarmcliVersion verdict against the engine this controller embeds.

const (
	CompatUnknown      CompatState = ""
	CompatOK           CompatState = "ok"
	CompatIncompatible CompatState = "incompatible"
)

func (CompatState) MarshalJSON

func (c CompatState) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*CompatState) UnmarshalJSON

func (c *CompatState) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

type ControllerStatus

type ControllerStatus struct {
	AppSet       AppSetStatus `json:"appSet"`
	Applications int          `json:"applications"`
}

ControllerStatus is the controller itself as last observed, as distinct from the applications it reconciles.

It exists because the app set became something that can fail on its own: once the set is pulled from git rather than mounted at deploy time, "every application looks fine" and "the controller has been refusing every commit for an hour" are both true at the same time, and nothing in the per-application views can say the second. This is where that is said.

Applications counts what is actually being reconciled, which is not necessarily what the last-loaded file declares: an application the set added but that could not be started is in the file and not in this count.

type Destination

type Destination struct {
	Swarm string `json:"swarm,omitempty" yaml:"swarm,omitempty"`
}

Destination names the swarm, resolved through the SwarmRegistry seam. Empty means the local swarm, which is the only one Phase 1 can resolve.

type DriftDetection

type DriftDetection string

DriftDetection is how an application's drift is decided. Phase 1 has one mode: manifest, which compares the rendered manifest against what was last applied. Comparing the desired ServiceSpec against the live one is Phase 2.

const (
	DriftUnknown  DriftDetection = ""
	DriftManifest DriftDetection = "manifest"
)

func (DriftDetection) MarshalJSON

func (d DriftDetection) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*DriftDetection) UnmarshalJSON

func (d *DriftDetection) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

func (DriftDetection) Valid

func (d DriftDetection) Valid() bool

Valid reports whether d names a mode this build implements. It is what the config loader checks: unlike the wire, applications.yaml gets no leniency.

type Duration

type Duration time.Duration

Duration is a time.Duration that reads and writes as a string ("30s", "10m") rather than as a count of nanoseconds. These values are written by hand in applications.yaml and read by humans in API output, and 600000000000 is neither writable nor readable.

func (Duration) MarshalJSON

func (d Duration) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (Duration) MarshalYAML

func (d Duration) MarshalYAML() (any, error)

MarshalYAML implements yaml.Marshaler.

func (Duration) String

func (d Duration) String() string

String renders d in the form time.ParseDuration accepts.

func (*Duration) UnmarshalJSON

func (d *Duration) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

func (*Duration) UnmarshalYAML

func (d *Duration) UnmarshalYAML(value *yaml.Node) error

UnmarshalYAML implements yaml.Unmarshaler. It is needed separately because gopkg.in/yaml.v3 does not honour encoding.TextUnmarshaler.

type Health

type Health struct {
	State    HealthState   `json:"state"`
	Message  string        `json:"message,omitempty"`
	Services ServiceCounts `json:"services"`
}

Health answers "is what is running actually working". It is a separate axis from Sync — a stack can be synced and degraded at once, and collapsing the two loses the distinction that makes the view useful. Services carries the counts a list row renders without descending into Releases.

type HealthState

type HealthState string

HealthState is whether what is running is working. Missing — declared but not present — is deliberately distinct from Degraded, which is present and unhealthy: a UI needs to tell those apart and so does an operator.

const (
	HealthUnknown     HealthState = ""
	HealthHealthy     HealthState = "healthy"
	HealthProgressing HealthState = "progressing"
	HealthDegraded    HealthState = "degraded"
	HealthMissing     HealthState = "missing"
)

func (HealthState) MarshalJSON

func (h HealthState) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*HealthState) UnmarshalJSON

func (h *HealthState) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

type History

type History struct {
	Releases []ReleaseHistory `json:"releases"`
}

History is one application's release history: every release it declares, with the revisions recorded for each.

It is served by its own endpoint rather than carried on Status for the same reason ReleaseDiff is — a list view rendering twenty applications must not drag their histories along.

type ReleaseDiff

type ReleaseDiff struct {
	Release string     `json:"release"`
	Action  SyncAction `json:"action"`
	Diff    string     `json:"diff"`
}

ReleaseDiff is the manifest change one release would undergo.

It is deliberately not part of ReleaseStatus: a diff carries whole manifests, and a list view rendering twenty applications must not drag them along. It is served by its own endpoint, for one application at a time.

type ReleaseHistory

type ReleaseHistory struct {
	Name      string     `json:"name"`
	Revisions []Revision `json:"revisions"`
}

ReleaseHistory is one release's revisions, newest first.

A release the repository declares but that has never been deployed has an empty Revisions rather than being absent: a history view shows the release with nothing under it, which is the honest answer and a different one from "no such release".

type ReleaseStatus

type ReleaseStatus struct {
	Name     string          `json:"name"`
	Chart    string          `json:"chart"`
	Version  string          `json:"version"`
	Revision int             `json:"revision"` // charts revision number; 0 when never installed
	Action   SyncAction      `json:"action"`
	Sync     SyncState       `json:"sync"`
	Health   Health          `json:"health"`
	Services []ServiceStatus `json:"services,omitempty"`
	Compat   *Compat         `json:"compat,omitempty"`
}

ReleaseStatus is one release of one application.

type RepositorySpec

type RepositorySpec struct {
	Name string `json:"name" yaml:"name"`
	URL  string `json:"url" yaml:"url"`
}

RepositorySpec names a chart repository a ChartSource resolves Ref against.

type Revision

type Revision struct {
	Revision int    `json:"revision"`
	Chart    string `json:"chart"`
	Version  string `json:"version"`
	// Status is the engine's derived status: the highest revision keeps its
	// stored status and every lower deployed one reads "superseded".
	Status string `json:"status"`
	// Created is RFC3339, as the engine recorded it.
	Created string `json:"created,omitempty"`
	// Owner is the stamp naming what produced the revision, empty when
	// unclaimed. A history view showing a revision this controller did not
	// install is telling the operator something worth knowing.
	Owner string `json:"owner,omitempty"`
}

Revision is one recorded revision of one release.

Deliberately not charts.Release. That type carries the rendered manifest and the merged values of every revision, and a history response covering six releases at ten revisions each would then be sixty manifests — for a view that renders a table of numbers, dates and chart versions. The manifest of a specific revision is a different request, if it is ever wanted.

type ServiceCounts

type ServiceCounts struct {
	Healthy int `json:"healthy"`
	Total   int `json:"total"`
}

ServiceCounts is the "3/4" a list row shows.

type ServiceStatus

type ServiceStatus struct {
	Name        string      `json:"name"`
	Mode        string      `json:"mode"`
	Running     int         `json:"running"`
	Desired     int         `json:"desired"`
	Completed   int         `json:"completed,omitempty"`
	Health      HealthState `json:"health"`
	UpdateState string      `json:"updateState,omitempty"` // "" means never updated, not finished
	Message     string      `json:"message,omitempty"`
}

ServiceStatus is one Swarm service under a release.

type Source

type Source struct {
	RepoURL  string `json:"repoURL" yaml:"repoURL"`
	Revision string `json:"revision" yaml:"revision"` // branch, tag or SHA, as written

	ReleaseFile string       `json:"releaseFile,omitempty" yaml:"releaseFile,omitempty"` // path within the repo
	Chart       *ChartSource `json:"chart,omitempty" yaml:"chart,omitempty"`
}

Source locates the desired state in git. Exactly one of ReleaseFile and Chart is set, and which one is present is the source type — a separate discriminator field would be a second thing to keep consistent with it.

type Spec

type Spec struct {
	Name   string `json:"name" yaml:"name"`
	Source Source `json:"source" yaml:"source"`

	// RegistryAuth names a Docker secret holding a docker config.json
	// ({"auths":{...}}). The controller uses only this application's secret to
	// authenticate the pulls its images need, so one application cannot pull
	// another's private images even though both credentials are mounted in the
	// same controller. Empty means the application's images are public.
	//
	// It is a secret name, not a path: the controller reads it from the default
	// mount /run/secrets/<name>, so the secret must be mounted there — the
	// short form `secrets: [<name>]` in stack.yml does exactly that.
	RegistryAuth string `json:"registryAuth,omitempty" yaml:"registryAuth,omitempty"`

	Destination    Destination    `json:"destination" yaml:"destination"`
	SyncPolicy     SyncPolicy     `json:"syncPolicy" yaml:"syncPolicy"`
	DriftDetection DriftDetection `json:"driftDetection" yaml:"driftDetection"`
}

Spec is what an operator declares in applications.yaml. It is read-only over the API: the file is the only source of truth, whether it is mounted at deploy time or committed to git, and the API serves it rather than owning it.

type Status

type Status struct {
	Sync       Sync            `json:"sync"`
	Health     Health          `json:"health"`
	Releases   []ReleaseStatus `json:"releases,omitempty"`
	Error      string          `json:"error,omitempty"` // last reconcile error; not a failed sync
	ObservedAt time.Time       `json:"observedAt"`
}

Status is one application as last observed. The list view renders it without Releases and the detail view renders it with. Releases is never legitimately empty once populated — charts rejects a release file declaring no releases — so its absence unambiguously means "not requested" rather than "none".

type Sync

type Sync struct {
	State    SyncState   `json:"state"`
	Revision string      `json:"revision,omitempty"` // resolved SHA, never a branch name
	Summary  SyncSummary `json:"summary"`
	LastSync *SyncResult `json:"lastSync,omitempty"`
}

Sync answers "does the swarm match git".

Revision is the commit the assessment was made against; LastSync.Revision is what was actually deployed. When the two differ there is a newer commit that has not been applied, which is a different condition from being OutOfSync, and a UI shows both.

type SyncAction

type SyncAction string

SyncAction is what a sync would do to one release. The names mirror the chart engine's own vocabulary value-for-value; diverging from it would mean translating in both directions for no gain.

const (
	ActionUnknown   SyncAction = ""
	ActionUnchanged SyncAction = "unchanged"
	ActionInstall   SyncAction = "install"
	ActionUpgrade   SyncAction = "upgrade"
)

func (SyncAction) MarshalJSON

func (a SyncAction) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*SyncAction) UnmarshalJSON

func (a *SyncAction) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

type SyncPolicy

type SyncPolicy struct {
	Automated  bool     `json:"automated" yaml:"automated"`
	Interval   Duration `json:"interval,omitempty" yaml:"interval,omitempty"`
	Wait       bool     `json:"wait,omitempty" yaml:"wait,omitempty"`
	Timeout    Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	HistoryMax int      `json:"historyMax,omitempty" yaml:"historyMax,omitempty"`
}

SyncPolicy governs when and how a plan is applied. Wait, Timeout and HistoryMax map onto charts.InstallOptions; Interval overrides the controller-wide poll interval for one application.

type SyncResult

type SyncResult struct {
	Revision   string    `json:"revision"`
	StartedAt  time.Time `json:"startedAt"`
	FinishedAt time.Time `json:"finishedAt"`
	Succeeded  bool      `json:"succeeded"`
	Error      string    `json:"error,omitempty"`
}

SyncResult records the outcome of the last sync that was actually attempted.

type SyncState

type SyncState string

SyncState is whether the swarm matches git.

const (
	SyncUnknown   SyncState = ""
	SyncSynced    SyncState = "synced"
	SyncOutOfSync SyncState = "out-of-sync"
)

func (SyncState) MarshalJSON

func (s SyncState) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*SyncState) UnmarshalJSON

func (s *SyncState) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

type SyncSummary

type SyncSummary struct {
	Install   int `json:"install"`
	Upgrade   int `json:"upgrade"`
	Unchanged int `json:"unchanged"`
}

SyncSummary is the plan that made the state OutOfSync, counted by action.

type View

type View struct {
	Spec   Spec   `json:"spec"`
	Status Status `json:"status"`
}

View is what every API read returns: the declared spec beside what the controller last observed. Keeping them separate is what lets applications become writable later without moving anything.

Jump to

Keyboard shortcuts

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