The bridge commands never go through the dry-run backend, so the
inherited --dry-run flag was silently ignored: `bridge convert` still
wiped and rewrote the output directory, pulled the transformer image
and ran it, and `bridge transformations create` still wrote its
destination folder.
Fail fast with an explicit error before any side effect instead of
pretending to honor the flag.
Signed-off-by: Guillaume Lours <[email protected]>
The legal values of DownOptions.Images lived in pkg/compose
(image_pruner.go) while the field itself was a bare string in pkg/api,
and the only validation fired inside ImagesToPrune — after down had
already removed the project's containers, leaving the teardown half
done on a typo. The CLI validated its --rmi flag separately, so only
SDK callers were exposed.
ImagePruneMode and its three values now live in pkg/api next to the
field they constrain (type-aliased in pkg/compose for existing
consumers), down() rejects any other value before touching a single
resource, and the CLI check reuses the same definition. A unit test
runs Down with expectation-free mocks: one daemon call would fail it.
Epic #14074, B.6.
Signed-off-by: Nicolas De Loof <[email protected]>
publish was promoted to a regular Compose command in 13618756d
(2025-03-12) but was left registered under 'alpha' as well.
Remove the leftover duplicate registration and the now-orphaned
generated docs (compose_alpha_publish.md,
docker_compose_alpha_publish.yaml), and regenerate
docker_compose_alpha.yaml accordingly.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Signed-off-by: Guillaume Lours <[email protected]>
compose-go's WithImagesResolved no longer special-cases a type=image
volume source that happens to match another service's name -- per
compose-spec/compose-go#929, such a source is always resolved as a
plain docker image reference, never treated as an implicit reference
to that service's locally built image. This surfaced as a real CI
failure after bumping the compose-go pin: the mock in
TestResolveImageDigests only expected calls for nginx/someimage/
hookimage and panicked on the now-unexpected lookup for "builder".
Update the test to expect the volume source to resolve like any other
image reference, and drop the stale comment in resolveServiceImageDigests
describing the old, now-removed behavior.
Signed-off-by: Nicolas De Loof <[email protected]>
The previous fix only covered create/start. projectOrName backs every
other service-targeting command too (stop, kill, pause, unpause, logs,
rm, down, ps, events), so they still surfaced the same two problems: a
raw "no such service" error for a job name, and -- when
COMPOSE_PROJECT_NAME is set -- a silent no-op, since projectOrName's
label-driven fallback swallows the load error entirely.
Fold the check into projectOrName so every caller is fixed at once,
instead of duplicating the wiring per command. start.go's own
job-handling code, now redundant, is removed; jobTargetErr and
unselectedJobs (cmd/compose/run.go) are unchanged, just called from one
more place.
Adds unit coverage for projectOrName itself (including the
COMPOSE_PROJECT_NAME path and the typo-correlation edge case) and e2e
coverage for two more callers (stop, down) to confirm the fix lives in
the shared helper rather than being re-implemented per command.
Signed-off-by: Nicolas De Loof <[email protected]>
projectOrName falls back to a label-driven, file-less project on any
load failure when COMPOSE_PROJECT_NAME is set -- swallowing the load
error entirely, including "no such service" for a target that's
actually a declared job. Since that branch returns err == nil,
jobTargetErr (gated on err != nil) and rejectScheduledJobs (gated on
project != nil) were both skipped: `docker compose start migrate`
fell through to a label-driven start that finds no container for a
job that was never run, and exited 0 having silently done nothing.
Refuse a named job explicitly on that fallback path too, reusing
unselectedJobs from the previous commit.
Also adds the test coverage this and the previous fix (job-target
error messages on create/start) were entirely missing: create.go and
start.go had no unit tests at all, and pkg/e2e/jobs_test.go only
exercised up/run.
Signed-off-by: Nicolas De Loof <[email protected]>
jobTargetErr was called with the full [SERVICE...] argument list, but
compose-go's selection error only ever names the one target it
actually failed on. The loop matched any name in the list against the
job set, so `docker compose create totally-bogus migrate` (a real
typo plus a legitimate job) reported the job error instead of the
real "no such service: totally-bogus", masking the typo.
Match the name the error actually names, not any job name that
happens to share the invocation. unselectedJobs is extracted out of
jobTargetErr so start's own project-resolution fallback (next commit)
can reuse the same unselected-reload logic.
Signed-off-by: Nicolas De Loof <[email protected]>
materializeJobClosure walked job-typed depends_on edges without
re-checking triggers.manual: false on anything but the top-level run
target, so `docker compose run A` where A depends_on job B (manual:
false) silently ran B anyway.
manual: false declares a job harmful to trigger outside its
schedule; depends_on doesn't change who caused the execution or when
-- pulling B in to satisfy A's dependency is still the run command
causing that out-of-schedule execution, one hop removed. Refuse it
the same way the top-level check already does, before anything in the
closure is created.
Signed-off-by: Nicolas De Loof <[email protected]>
docker compose create <job> / start <job> failed with the raw
compose-go error "no such service: <name>", since project loading
rejects the job name before rejectScheduledJobs or any job-aware
logic ever runs -- unlike run, neither command knows how to detect
that a name is a declared job. The result gave no hint that jobs
exist or that run is the right command.
Jobs stay run-only by design; this only replaces the error message
when the target is a declared job, via a small helper (jobTargetErr)
shared by both commands, reloading unselected exactly like run's own
job-detection fallback. Any other "no such service" (a real typo)
keeps the original, precise error.
Signed-off-by: Nicolas De Loof <[email protected]>
Only the interpolated path (imagesOnly) and pkg/compose/publish.go
walked project.Jobs the same way they walk project.Services. Three
sibling paths in config.go were missed:
- runConfigImages (--images) only printed service images.
- lockModel (--no-interpolate --lock-image-digests) dropped the
top-level "jobs" key entirely along with every other non-services
key, instead of filtering it down to image/type:image-volumes like
services.
- resolveImageDigests (--no-interpolate --resolve-image-digests) only
built its pseudo-project from model["services"], so job images and
type: image volume sources never resolved -- confirmed by a new test
that failed against the first attempt at this fix (job image stayed
unpinned) until dressed as its own pseudo-project the same way
generateImageDigestsOverride in publish.go already does, since
WithImagesResolved only walks a project's Services, never its Jobs.
The extra complexity that last one picked up is split into
resolveServiceImageDigests (services, unchanged from before this
commit) and resolveJobImageDigests (new), called from a small
dispatcher.
Signed-off-by: Nicolas De Loof <[email protected]>
The fallback reload runProject uses to detect a job target passed
warnUnsupportedAttributes, the same option as the first, selective
load right before it -- which had already fired every warning once.
For the common case where the target is neither a service nor a job
(a plain typo), every finding got logged twice before falling through
to the original, correct error anyway.
skipUnsupportedAttributesWarning already exists for exactly this
secondary-load case (see its other call sites in compose.go and
completion.go); use it here too.
Signed-off-by: Nicolas De Loof <[email protected]>
Every publish safeguard and image-pinning path used to walk only
project.Services, leaving jobs invisible:
- the sensitive-data checks (literal environment values, env_file
scans, bind-mount warnings, build-only rejection) now cover jobs — a
job declaring AWS_SECRET_ACCESS_KEY=... was published without the
guard rail a service gets;
- the image-digest override pins job images too (jobs dressed as
services run through the exact WithImagesResolved semantics), so the
published artifact is reproducible for jobs as well;
- the application index references job images;
- `config --images` lists them.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
Per the spec, any job can be triggered manually by an explicit run
command, its automated triggers notwithstanding — so run now accepts
scheduled jobs too. The exception is a job explicitly declared with
triggers.manual: false, which run rejects: meant for scheduled jobs
whose out-of-schedule execution would be harmful.
compose-go is bumped to the jobs-branch commit making Manual tri-state
(*bool) and allowing manual and schedule to be combined.
Signed-off-by: Nicolas De Loof <[email protected]>
`docker compose run <job>` materializes the job — a ContainerSpec +
WorkloadSpec, the same layers a service is made of — as a service for
the one-off machinery: its profile is activated, the project narrowed
to its dependencies by WithSelectedJob, and the exit code flows back.
The materialization happens during project loading, BEFORE service
selection and environment resolution: the job's own env_file and
label_file resolve exactly like a selected service's would, unrelated
services' env_file still don't need to exist, and a target that is
neither a service nor a job keeps the precise selection error (the
retry is keyed on "the target is a declared job", never on matching an
error message). A service and a job may share a name: the service wins,
matching what the selector resolved.
A job may depend on other jobs: the whole job closure materializes, so
every depends_on edge resolves to a runnable service satisfying its
declared condition — a dependency job runs to completion through the
exact machinery a service dependency does — instead of dangling as an
unresolvable name. Job extensions (x-*) survive materialization.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
The model may declare jobs this runtime cannot execute yet. Commands
that materialize the application warn about them, and refuse an active
(profile-enabled) scheduled job before any resource is created — up,
create and start alike: silently not scheduling would break
expectations, while manual jobs just wait for an explicit trigger.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
compose-spec/compose-go#866 is merged: jobs top-level element, container
specification layered as ContainerSpec/WorkloadSpec, pre_start hooks
carrying the full container specification resolved at load time. Bump
to the merged head and adapt in the same movement — composite literals
setting moved fields wrap them into the embedded ContainerSpec /
WorkloadSpec (promoted field access was already source-compatible, so
this is literal-only, no behavior change), and pre_start handling is
typed against PreStartHook.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
`docker compose port SERVICE PORT` picks the first matching publisher
as its answer, but containerPublishers()'s sort.Slice does not
guarantee tie order, so a dual-stack publish (same target port on
both an IPv4 and an IPv6 address) could non-deterministically return
either one. Switch to sort.SliceStable to preserve the daemon's
original order for ties.
Also drops a dead length check and a duplicated host:port formatter.
Signed-off-by: Guillaume Lours <[email protected]>
The github.com/AlecAivazis/survey/v2 module was archived and is no longer
maintained. Looking at the code, we didn't really use most of its features;
survey mostly provided terminal input handling around a simple yes/no
question.
Replace the interactive confirmation implementation with a small local
prompt reader while keeping the exported prompt types and existing
non-terminal prompt behavior unchanged. Preserve the confirmation hint,
default handling, retries for invalid input, and Ctrl+C handling.
Interactive prompts continue to use raw terminal input so Ctrl+C is handled
by the prompt instead of requiring a second interrupt to terminate the
process.
Signed-off-by: Sebastiaan van Stijn <[email protected]>
A plain line printed outside any operation lands below the tracked
block, so the block is no longer directly above the cursor. Reset the
screen so the next operation repaints a fresh block instead of moving
the cursor over rows that shifted — the same reason unsuspend resets
after buildkit output.
Suggested-by: Guillaume Lours <[email protected]>
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
- layoutFrame clips its output to the terminal height: height==1 no
longer overflows with header + more-marker
- the two status-to-color switches merge into eventColor(status, def)
- the clear-suspension sequence extracts into unsuspend()
- tests drive a manually-advanced fake clock through the injected
now(), and the cancel test loses its settling sleep — Done waits for
the refresh goroutine itself
- roots, per-parent children and the completed count are maintained
incrementally by apply(), so a 100ms tick with an unchanged tree no
longer rescans every task
- the frame<0 clamp in spinGlyph drops: time comes from the writer's
single clock and never regresses; the invariant is documented on the
model
- Start's retire-previous-cycle comment spells out that cancellation
only guarantees the old goroutine will exit, not that it already has
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
Transposes the applicable findings from the review of the ttyWriter fix
(#14119) to the rewritten renderer:
- Done waits for the refresh goroutine to exit (outside the lock), so
nothing can repaint after Done returns and output printed right after
an operation cannot be garbled by a stray frame
- a nested Start retires the previous cycle instead of leaking its
refresh goroutine until the parent context is cancelled
- build suspension no longer leaks across cycles: Start resets it and
starts a fresh block below whatever buildkit wrote
- drop the dead strings.ToLower in startDependencies: the unexported
start only reads projectName when no Project is passed
The remaining findings are structural to this rewrite: Done is a single
critical section, and per-cycle context cancellation replaces the
doneSignal handshake.
Signed-off-by: Nicolas De Loof <[email protected]>
The "[+] op N/M" header was the one line bypassing renderSegs, so on a
very narrow terminal it could wrap and desync the cursor arithmetic the
rest of the design guarantees against. Route it through the same clip
and cover degenerate widths (8, 12 cells) in the invariant test.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
The TTY renderer accumulated display-corruption fixes (truncation of
details, then progress sizes, then task ids; timer alignment; rune-based
measurement) that each patched one symptom of the same structural gap:
nothing guaranteed a rendered line fits the terminal, and once a line
wraps, cursor arithmetic desyncs and the block corrupts.
Replace it with three separable units:
- tty_model.go: pure event reducer with an injected clock, preserving
first-parent-wins updates, monotonic progress and header counters
- tty_layout.go: pure (model, size, now) -> lines function; all widths
are measured in terminal cells (go-runewidth, so CJK is correct) on
plain text before coloring, and every line is clipped to the terminal
width by construction, status text included
- tty_screen.go: diff-based repaint; unchanged rows are skipped,
identical frames write nothing, a frame is a single Write; a shrinking
terminal abandons the block instead of moving the cursor over reflowed
rows
The writer coordinates them behind a mutex and stops the refresh
goroutine through context cancellation, so Done cannot block when the
operation context was cancelled first (Ctrl-C during pull). The spinner
frame is derived from the clock instead of advancing on every call, and
truncation can no longer split multi-byte runes. Visual output is
unchanged: the snapshot test reproduces the previous renderer's golden
output character for character.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
Compose silently ignored several spec-valid attributes it doesn't
honor outside Swarm mode: deploy.update_config, credential_spec,
ports[].mode: host, and others had no visible effect on a plain
`docker compose up`.
Detection is wired into compose-go's loader as a fail-closed
allowlist: every attribute path the schema declares, minus the
handful this runtime deliberately doesn't implement, plus a few
value-conditional checks for attributes only unsupported for one
specific value. A newly-specified attribute gets flagged
automatically, with no corresponding code change needed here.
Depends on an unreleased compose-go change; go.mod carries a
temporary replace to a fork branch until it's released.
Signed-off-by: Guillaume Lours <[email protected]>
display.Mode resolves "auto" against stderr — the stream Compose's own
progress renders to — so `compose build >/dev/null` in an interactive
terminal resolves to "tty". Bake reuses that mode for a display writing
to stdout, and buildkit's progressui hard-fails in tty mode when the
stream is not a console.
Pass the unresolved "auto" to bake unless the user explicitly asked for
tty: progressui then probes the actual output stream and degrades to
plain by itself when stdout is redirected. An explicit --progress=tty
keeps failing loudly on a non-terminal, as `buildx bake` does. This
also revives the BUILDKIT_PROGRESS override in doBuildBake, which only
applies to AutoMode and could no longer trigger.
Fixes#14182
Signed-off-by: Nicolas De Loof <[email protected]>
`bridge convert -o <dir>` unconditionally ran `os.RemoveAll` on the
output directory and discarded any error, so a typo (`-o .`, `-o
$PWD`, `-o ~`) silently destroyed unrelated user data.
An empty or missing output directory is still created silently, but a
non-empty one now requires explicit confirmation (prompted
interactively, or via the new `--yes` flag for scripts/CI) before
being wiped.
Signed-off-by: Guillaume Lours <[email protected]>
OTel SDK/exporter errors already go through docker/cli's own
otel.SetErrorHandler(debug.OTELErrorHandler), installed by the plugin
framework at startup. Our own handler, installed at package init time,
was always clobbered before any command ran (plugin.Run() sets its handler
after all inits execute), so it never had any effect - verified by tracing
the exact call order and reproducing the flag behavior against a built
binary.
Drop that dead handler along with the COMPOSE_OTEL_DEBUG env var it
introduced. The one genuinely-discarded error (the tracer shutdown/flush
error in wrapRunE) now goes through logrus like everything else in the
codebase: it's already gated by --debug/-D via logrus's own level, so no
explicit check is needed.
Signed-off-by: hiroto.toyoda <[email protected]>
bridgeCommand and transformersCommand had no RunE, so an unknown
subcommand (e.g. "bridge zzz") fell through cobra's default
non-Runnable path: help printed, exit 0. Scripts couldn't distinguish
a typo from success. Mirrors the same fix already shipped on the
compose root command.
Signed-off-by: Guillaume Lours <[email protected]>
Fixes#14114: Done() blocked forever once the render goroutine had
already exited via ctx.Done() (e.g. after SIGTERM), since it sent on an
unbuffered channel with nobody left to receive.
Closing the channel instead of sending never blocks, but a bare close()
panics the moment ttyWriter runs a second Start/Done cycle on the same
shared bus - which already happens today (run's nested create/start,
publish's nested push, rm --stop's sequential stop then remove). Start
now hands out a fresh, once-only-closable signal each cycle instead of
a single shared channel, and run/publish stop opening a redundant
nested bracket around calls already inside one.
Signed-off-by: Guillaume Lours <[email protected]>
The display.Mode package global was left as "auto" on the most common
path (auto mode with an interactive or redirected terminal), so readers
like `up`'s NavigationMenu gate saw an unresolved value; that gate also
compared against the string literal "plain" instead of the constant.
applyDisplayMode wrote a mode that selectEventProcessor could then
contradict (--ansi always with a redirected stderr rendered Plain while
Mode said tty).
selectEventProcessor becomes the single resolution point: every branch
assigns the mode matching the renderer it returns, so after command
setup the global never holds ModeAuto. applyDisplayMode loses its
redundant writes and its misleading name (now applyAnsiMode); the
contract is documented on the Mode variable, and the existing
selectEventProcessor tests now pin the resolved mode for every branch.
Side effect, deliberate: with stderr redirected (auto -> plain
rendering) the navigation menu is no longer offered; it used to slip
through because Mode still read "auto".
Part of #14074 (section E).
Signed-off-by: Nicolas De Loof <[email protected]>
The --format flag defaulted to "table" instead of "", so the
opts.Format == "" guard in runPs never fired for a bare compose ps.
Setting the default to "" lets the existing config-fallback path work,
matching the behaviour of docker ps.
Fixes#13643
Supersedes #14032
Signed-off-by: Guillaume Lours <[email protected]>
Rename-only change, no behavior difference, no exported identifier
renamed (only parameter names of a few exported functions, which are
not part of the Go API).
- names that hide what a value is: parameters holding a *name* or *ID*
while named like the object — project→projectName, service→serviceName,
container→containerID — so string vs struct is readable at every call
site without opening the signature
- misleading receivers: monitor methods used c (reads as
client/container/CLI) → m; jsonWriter used p while its ttyWriter
sibling uses w → w
- cryptic abbreviations with non-trivial scope: nw (meant both
network.Summary and types.NetworkConfig) → network/networkConfig,
svc → service/serviceName/serviceCopy, oc/ocs → observedContainer(s),
cnts → serviceContainers, cnx → attachResponse
- container.Summary loop variables unified on ctr (was a c/ctr mix);
fillBindMounts also dropped its p/s/m single-letters, one of which
was shadowed by a loop variable of a different type
- option-struct identifiers unified on options (opts remains only for
variadic functional options); ambiguous ones get a precise name
(bindOptions, networkCreateOptions, projectOptionsFns)
Signed-off-by: Nicolas De Loof <[email protected]>
The Compose interface promised 'Scale manages numbers of container
instances running per service', but ScaleOptions had no replica count:
callers had to know they must mutate project.Services[x] via SetScale
before calling, and ScaleOptions.Services only tuned the recreation
policy. An SDK consumer following the interface doc got a no-op
convergence of the whole project.
ScaleOptions gains Replicas (service -> count); the backend applies it
to the model itself before converging, and derives the targeted
services from its keys when Services is not set. The CLI now does what
a CLI should: parse SERVICE=REPLICAS tuples and hand them to the
backend, instead of pre-mutating the model. Passing a pre-mutated
project with an empty Replicas map still behaves as before, so
existing SDK callers are unaffected.
Part of #14074 (B: pkg/api promises things the implementation does not
honor).
Signed-off-by: Nicolas De Loof <[email protected]>
RootCommand (cognitive complexity 62) buried four independent
concerns inside the PersistentPreRunE closure, where every branch
costs double. Each moves to a named function: runParentPreRun (cobra
doesn't chain the parent's PersistentPreRunE automatically),
resolveAnsiMode (--ansi vs deprecated --no-ansi vs COMPOSE_ANSI),
applyDisplayMode (ANSI + NO_COLOR + progress mode),
normalizeProjectOptions (--workdir deprecation, env-file paths) and
resolveMaxConcurrency (COMPOSE_PARALLEL_LIMIT vs --parallel). The
prerun now reads as the sequence of those steps; RootCommand drops to
complexity 22 and its FIXME suppression is gone.
No behavior change: same errors, same deprecation warnings, same
precedence rules.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
docker compose events <name> was passing service arguments straight to
the event filter without checking they exist in the project, so a typo
would produce an event stream matching nothing and block forever with
no output and no error.
Switch to projectOrName (which validates args via ToProject) before
calling backend.Events, consistent with logs, kill, and restart.
Signed-off-by: Guillaume Lours <[email protected]>
Cognitive complexity (gocognit, threshold 30 — the linter default) fits
this codebase better than raw cyclomatic complexity: it barely charges
guard clauses and early returns, and penalizes nesting instead. As a
result 8 of the 21 //nolint:gocyclo suppressions become unnecessary,
while deeply nested functions gocyclo never flagged are now caught.
The 19 functions still above the threshold keep a suppression, each
marked FIXME to complete the migration by restructuring them.
Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
Show an ENGINE column in `compose ps` default table output when the
com.docker.compose.engine label is present. Expose {{.Engine}} for custom
--format templates.
Rename api.EngineLabel to api.ContainerEngineLabel to avoid the
name collision with desktop.EngineLabel (com.docker.desktop.address).
Signed-off-by: Nick Sieger <[email protected]>
validateNavigationMenu validated nothing: it resolves the TTY /
COMPOSE_MENU / --menu precedence and mutates opts.navigationMenu —
rename to resolveNavigationMenu.
runScale duplicated setServiceScale's GetService/SetScale/write-back
dance inline; move the helper next to its natural home in scale.go and
use it from both callers.
Signed-off-by: Nicolas De Loof <[email protected]>
Inline small functions that were extracted from their single call site
without a real boundary to justify it — no reuse, no dedicated test, no
responsibility of their own — so each caller now tells its whole story
top-down:
- getExecTarget, attachContainer, logContainer: one-line trampolines to
getSpecifiedContainer / doAttachContainer / doLogContainer, which
other call sites already use directly
- removeImage: single-statement wrapper, unlike its removeVolume
sibling which has actual logic
- checkSelectedServices: named like a validation, actually a filter;
the subtle rule (an unknown service is only an error with an explicit
compose file) now reads where options.Services is rewritten
- prepareLabels: mutated the map it received while looking pure at the
call site; the label writes are now visible in getCreateConfigs
- setDefaultTarget: mutation-by-pointer of the loop copy, now visible
in the loop of injectFileReferences
- buildVolumeOptions/buildTmpfsOptions/buildImageOptions: nil-guard +
field copies; the buildMountOptions switch now shows side by side
what each mount type propagates (buildBindOption keeps real logic and
keeps buildMountOptions under the gocyclo limit)
- displayDryRunBuildEvent: was longer than its only caller
- hasMore: read like a predicate, was a one-line spinner restart
- escapeDollarSign: wrapped a single bytes.ReplaceAll
- extractEnvCLIDefined: replaced by the canonical compose-go helper
types.NewMappingWithEquals().ToMapping(), as run.go already does
- isPullPolicyValid: rebuilt the valid-values slice on every call; now
a package-level list checked at the call site
- viewFromStackList: projection now sits next to the render closure
that consumes exactly its three fields
No behavior change; single gocyclo threshold untouched.
Signed-off-by: Nicolas De Loof <[email protected]>
- pullRequiredImages resolves each distinct pulled image once, after
all pulls completed, for the host default platform — the exact way
getLocalImagesDigests resolves already-local images. Resolving from
each pull's goroutine, for the pulled platform, let the recorded
digest depend on pull completion order when several services pull
the same tag for different platforms (last-writer-wins), and made
the first up after a pull recreate containers. Platform-pinned
services keep getting their own platform's digest from
serviceImageDigest. In dry-run nothing was actually pulled, so the
local inspect is skipped — it reached the real daemon through a
DryRunClient dispatch that no longer matched and failed with 'No
such image'.
- explicit 'compose pull' treats daily/weekly/every_N windows as due:
it is the only way to force a refresh ahead of the window.
- 'compose pull' no longer silently skips services declaring both
provider: and image:.
- pre_start hook images go through the same mustPull interpreter on the
up path as on the pull path, so refresh windows apply consistently.
- matchLocalManifest falls back to the inspect's flat platform fields
when the lone available manifest carries no ImageData (locally built
images), instead of reporting the platform unsatisfied and pulling a
possibly local-only image.
- scale resolves DOCKER_DEFAULT_PLATFORM without validating
build.platforms: a conflict on a service that isn't being built must
not abort the command. run needs nothing: createOptions.Apply
resolves and validates platforms on its path already.
- resolveImageVolumes documents the accepted one-time-recreate and
retag-race tradeoffs.
Signed-off-by: Nicolas De Loof <[email protected]>
scale and run were the only container-creating commands that never
called applyPlatforms, yet both go through the regular create path and
its config-hash comparison (run for the dependencies it starts). With
DOCKER_DEFAULT_PLATFORM set, they hashed an empty service Platform where
up had hashed the resolved one, so every invocation recreated the
affected containers. run's project preparation is extracted to a helper
to keep runCommand under the complexity threshold.
No unit test: neither command has a test harness and the fix is the one
missing call, aligned on create/watch; the config-hash equality is
covered by the reconciler tests.
Signed-off-by: Guillaume Lours <[email protected]>
`docker compose bridge transformations create` panicked with an index
out of range when invoked without its PATH argument, as RunE indexed
args[0] without any validator. Add cli.ExactArgs(1) so the CLI reports
a proper usage error instead.
Also add cobra.NoArgs to `bridge convert` and `bridge transformations
list`, which silently ignored stray arguments.
Signed-off-by: Guillaume Lours <[email protected]>
Containers are created from a project whose service environment is
resolved (env_file merged into environment), but since v2.22.0 `config
--hash` skipped that resolution, so hashes diverged from the
com.docker.compose.config-hash label for services using env_file.
Resolve the environment of the hashed services only, so a broken
env_file or platforms on an unrelated service still doesn't prevent
hashing, and honor --no-env-resolution as an escape hatch.
Fixes#14001
Signed-off-by: Guillaume Lours <[email protected]>
Follow-up to #13603: scale, watch and shell completion loaded the
project without any tolerance option, so a missing env_file on a
service not involved in the operation aborted the command, while
up/exec/ps already tolerate this since #13156 and #13603.
Mirror the WithServices pattern: load with WithoutEnvironmentResolution
and resolve the environment once the project has been reduced to the
selected services, so targeted services still get their env_file
validated. Completion only needs names and never resolves. This also
aligns the config hash of scale-created containers with up-created
ones.
Signed-off-by: Guillaume Lours <[email protected]>
The runServices, runVolumes, runNetworks, runModels and runHash paths
called ProjectOptions.ToProject directly, bypassing the configOptions
wrapper that applies --no-consistency, --no-interpolate, --no-normalize,
--no-path-resolution, --profile filtering and env_file discarding.
Restore the variadic wrapper (mirroring configOptions.ToModel) and route
the five call sites through it.
Regression introduced by b80bb0586 (LoadProject API migration).
Fixes#13974
Signed-off-by: Guillaume Lours <[email protected]>
Address review feedback on pre_start hook image resolution:
1. GetDependentImages now skips a hook image equal to the service image
(resolved via GetImageNameOrDefault), so `config --images` no longer
prints a duplicate line and pullRequiredImages no longer schedules a
redundant pull for it.
2. pullRequiredImages (up/create path) now dedups dependent images by
reference via a `scheduled` set, so several hooks/services sharing the
same missing image don't schedule concurrent redundant pulls. The hook
pass moved to a helper (addPreStartHookPulls) to keep complexity in check.
3. The `pull` command no longer skips hook images under `pull_policy: build`.
A hook image is a registry image that can't be built, so only `never`
justifies skipping it — making `pull` consistent with the `up` path.
Signed-off-by: Nicolas De Loof <[email protected]>
pre_start hooks run as ephemeral init containers with their own image
(ServiceHook.Image), but that image was ignored by image resolution:
`config --images` didn't list it, `pull` didn't fetch it, and `up` failed
at runtime with "No such image" when it wasn't already present locally.
Add a GetDependentImages helper that returns a service's pre_start hook
images, and use it wherever service images are collected/pulled:
getLocalImagesDigests, pullRequiredImages (up path), the pull command, and
`config --images`. Hook images inherit the parent service pull policy.
post_start/pre_stop hooks run via ExecCreate inside the service container
and never use hook.Image, so they are intentionally out of scope.
Digest resolution/locking (--resolve-image-digests / --lock-image-digests)
is not covered: compose-go's WithImagesResolved only resolves service.Image
(needs an upstream change), and the --lock-image-digests override merges
pre_start by concatenation, which would duplicate hooks.
Signed-off-by: Nicolas De Loof <[email protected]>