From b792f5359b7f39f88e19338ead2421c0fc949ef9 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Thu, 2 Jul 2026 19:56:45 +0300 Subject: [PATCH] fix(oci): honor --insecure-registry when up re-loads the model `docker compose -f oci:///... up` failed against a plain-HTTP/loopback registry unless `--yes` was passed, even though the initial project load succeeded. `up` loads the project once via ToProject/LoadProject (which correctly forwarded --insecure-registry), then runUp calls checksForRemoteStack. Without --yes that path re-loads the model via ToModel to prompt for interpolation variables, and ToModel builds its OCI loader through ProjectOptions.remoteLoaders, which constructed an empty `api.OCIOptions{}`. The re-load therefore dropped the insecure-registry list, so the resolver spoke HTTPS to a plain-HTTP registry and failed before the prompt was ever shown. (config/viz share this ToModel path and hit the same failure.) The root cause was two independent OCI-options construction sites that drifted. Consolidate them into ProjectOptions.ociOptions() so every load path pulls an OCI artifact with the same configuration. Also expose ociRemoteLoader.InsecureRegistries() so the wiring can be asserted in a unit test. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Domantas Petrauskas --- cmd/compose/compose.go | 17 ++++-- cmd/compose/compose_remote_loaders_test.go | 67 ++++++++++++++++++++++ pkg/remote/oci.go | 8 +++ 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 cmd/compose/compose_remote_loaders_test.go diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 7fb0991efd1..83d84fde4d1 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -348,9 +348,7 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, b Compatibility: o.Compatibility, ProjectOptionsFns: po, LoadListeners: []api.LoadListener{metricsListener}, - OCI: api.OCIOptions{ - InsecureRegistries: o.insecureRegistries, - }, + OCI: o.ociOptions(), } project, err := backend.LoadProject(ctx, loadOpts) @@ -369,10 +367,21 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL return nil } git := remote.NewGitRemoteLoader(dockerCli, o.Offline) - oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline, api.OCIOptions{}) + oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline, o.ociOptions()) return []loader.ResourceLoader{git, oci} } +// ociOptions builds the OCI loader configuration from the project options. +// Both the primary project load and the loaders returned by remoteLoaders +// must use this so the --insecure-registry flag is honored on every path +// that pulls an OCI compose artifact (e.g. the interpolation-variable +// re-load that `up` runs via ToModel). See docker/compose#13824. +func (o *ProjectOptions) ociOptions() api.OCIOptions { + return api.OCIOptions{ + InsecureRegistries: o.insecureRegistries, + } +} + func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) { opts := []cli.ProjectOptionsFn{ cli.WithWorkingDirectory(o.ProjectDir), diff --git a/cmd/compose/compose_remote_loaders_test.go b/cmd/compose/compose_remote_loaders_test.go new file mode 100644 index 00000000000..7593f95ef61 --- /dev/null +++ b/cmd/compose/compose_remote_loaders_test.go @@ -0,0 +1,67 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "testing" + + "go.uber.org/mock/gomock" + "gotest.tools/v3/assert" + + "github.com/docker/compose/v5/pkg/mocks" +) + +// insecureRegistriesLoader is implemented by the OCI remote loader; used to +// observe which registries a loader will contact over plain HTTP. +type insecureRegistriesLoader interface { + InsecureRegistries() []string +} + +// TestRemoteLoaders_PropagatesInsecureRegistries guards docker/compose#13824: +// the OCI loader returned by remoteLoaders must carry the --insecure-registry +// values. This loader backs the ToModel re-load that `up` runs (via +// checksForRemoteStack) to prompt for interpolation variables; dropping the +// flag here made `docker compose -f oci://localhost:5000/... up` fail against +// an insecure registry unless --yes was passed. +func TestRemoteLoaders_PropagatesInsecureRegistries(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + // remoteLoaders only stores dockerCli in the loaders; it never calls it, + // so a bare mock with no expectations is sufficient. + dockerCli := mocks.NewMockCli(ctrl) + + o := &ProjectOptions{insecureRegistries: []string{"localhost:5000"}} + + var got []string + found := false + for _, l := range o.remoteLoaders(dockerCli) { + if oci, ok := l.(insecureRegistriesLoader); ok { + found = true + got = oci.InsecureRegistries() + } + } + + assert.Assert(t, found, "remoteLoaders returned no OCI loader") + assert.DeepEqual(t, []string{"localhost:5000"}, got) +} + +// TestOCIOptions_PropagatesInsecureRegistries covers the shared helper that +// both OCI load paths (LoadProject and remoteLoaders) rely on. +func TestOCIOptions_PropagatesInsecureRegistries(t *testing.T) { + o := &ProjectOptions{insecureRegistries: []string{"localhost:5000", "registry.test:443"}} + assert.DeepEqual(t, []string{"localhost:5000", "registry.test:443"}, o.ociOptions().InsecureRegistries) +} diff --git a/pkg/remote/oci.go b/pkg/remote/oci.go index 7b663d770a8..93a44d44b1f 100644 --- a/pkg/remote/oci.go +++ b/pkg/remote/oci.go @@ -113,6 +113,14 @@ func (g *ociRemoteLoader) Accept(path string) bool { return strings.HasPrefix(path, OciPrefix) } +// InsecureRegistries returns the registry hosts this loader contacts over +// plain HTTP. Exposed so callers/tests can verify the --insecure-registry +// flag is propagated into the loader on every OCI load path +// (docker/compose#13824). +func (g *ociRemoteLoader) InsecureRegistries() []string { + return g.insecureRegistries +} + //nolint:gocyclo func (g *ociRemoteLoader) Load(ctx context.Context, path string) (string, error) { enabled, err := ociRemoteLoaderEnabled()