compose

package
v1.0.0-rc3 Latest Latest
Warning

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

Go to latest
Published: Aug 1, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package compose turns a rendered compose manifest into the Swarm specs a stack is made of.

It is the half of charts.Backend that needs no daemon: a manifest string goes in, an ordered set of service, network, config and secret specs comes out. Applying them is backend's job.

The transformation is docker/cli's own — cli/compose/{loader,schema,convert}, which are exported — rather than a second implementation. `docker stack deploy` is unusable as an applier for the reasons in swarmcli-cd#1 (--prune touches services only and swallows its own list error, networks are silently never updated, no dry-run, --detach returns before convergence, update order is Go map iteration), but every one of those is a defect of the *command*, not of the conversion underneath it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SameImage

func SameImage(live, wanted string) bool

SameImage reports whether a live spec's image is still the one a manifest asked for.

One definition and two callers, because the two are the same question asked for different reasons. backend asks it of the stack image label, to decide whether the digest sitting in the live spec is the one our own tag resolved to; drift asks it of the desired spec, to decide whether somebody ran `docker service update --image`. A copy in each is two places to fix a rule, and the two disagreeing is a controller that reports drift, redeploys, and reports the same drift again.

Neither side is the string that was written, for two separate reasons:

  • The daemon appends the digest it resolved the tag to, so the live side is matched both whole and with a digest stripped. Whole is what covers a manifest that pinned a digest itself.
  • The client rewrites what it sends. imageWithTagString runs reference.FamiliarString(reference.TagNameOnly(ref)) over the image on the way into every ServiceCreate and ServiceUpdate — unconditionally, before the request is built and whatever QueryRegistry says (docker client/service_create.go, client/service_update.go) — so `nginx` is stored as `nginx:latest` and `docker.io/library/nginx:1.25` as `nginx:1.25`. Nothing rewrites the manifest's side, so it is done here, through the same two functions rather than by hand: a second implementation is a second thing that can disagree with the daemon about what a reference means.

Only the wanted side is normalised, which is the half that matters. Doing it to the live side as well would strip the digest off `nginx@sha256:…`, default what was left to `nginx:latest`, and call an image somebody pinned by hand a match for `nginx` — the one drift a converge must not be blind to.

Types

type Network

type Network struct {
	// Name is already namespace-scoped, unless the manifest set an explicit
	// `name:`, in which case it is that.
	Name string
	Spec network.CreateOptions
}

Network pairs a network's name on the swarm with what to create.

type Service

type Service struct {
	// Name is the service's name in the manifest, unscoped.
	Name string
	Spec swarm.ServiceSpec
}

Service pairs the name the manifest used with the spec it produced.

Both are needed and neither is derivable from the other in general: Spec.Name is namespace-scoped, and Namespace.Descope would be a guess for a service whose own name contains the separator.

type Stack

type Stack struct {
	// Namespace scopes every name. A stack is a name prefix plus a
	// com.docker.stack.namespace label — Swarm has no /stacks endpoint, no
	// server-side desired state and no owner references.
	Namespace convert.Namespace
	Services  []Service
	Networks  []Network
	Configs   []swarm.ConfigSpec
	Secrets   []swarm.SecretSpec
	// ExternalNetworks names networks the manifest expects to already exist.
	// They are not ours to create, and a missing one is a pre-flight failure
	// rather than something to conjure.
	ExternalNetworks []string
}

Stack is everything one rendered manifest says should exist.

Every slice is sorted by name. Go map iteration order is one of the named defects of `docker stack deploy`, and reproducing it here would be self-inflicted: two reconciles of an unchanged manifest must produce the same work list in the same order, or a diff of the plan is noise and an operator reading the log sees a different deploy every time.

func Convert

func Convert(ctx context.Context, manifest, stack string, api client.APIClient, allow application.Allow) (*Stack, error)

Convert loads a rendered compose manifest and converts it to Swarm specs.

The api client is used only for what conversion genuinely cannot do offline: resolving the secret and config names a service references to their ids, and reading the negotiated API version that gates a few spec fields. Nothing is written.

allow is the application's own permissions, carried here because the one thing this package refuses on their strength — a bind mount — is legible from the parsed document and from nowhere later. The caller is a backend scoped to one application; the rest of the same value is read there, over the names conversion produces (backend.rejectForbiddenResources).

func ConvertUnresolved

func ConvertUnresolved(ctx context.Context, manifest, stack string, api client.APIClient, allow application.Allow) (*Stack, error)

ConvertUnresolved converts a manifest as though every config and secret it references already existed.

It exists to break a circle. A stack that reaches for one of the controller's own secrets or configs has to be refused whole, before anything is created (swarmcli-cd#63) — and what a service mounts is only legible from its converted spec. But converting a service resolves each reference to the id Swarm addresses it by, so the conversion that gets applied cannot run until the resources exist, and a chart's own config does not exist until this controller creates it (swarmcli-cd#84). One conversion cannot be both.

So this one answers the lookup instead of making it. Every name it produces is the name Convert produces, because a reference's name comes from the manifest and the namespace — namespace.Scope(source), or the top-level entry's own name: — and nothing in that asks the daemon. Only the id does.

**The result must not be applied.** Its references carry unresolvedID rather than the id of anything on the swarm; it is for reading names from, and Convert is what a deploy applies.

Jump to

Keyboard shortcuts

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