From 6bb053884ee41ed502e5e212862b83abb9894937 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 5 Nov 2025 15:47:58 +0100 Subject: [PATCH] introduce --insecure-registry, reserved for testing purpose Signed-off-by: Nicolas De Loof --- cmd/compose/compose.go | 28 +++++++++++-------- cmd/compose/publish.go | 5 ++++ docs/reference/docker_compose.yaml | 11 ++++++++ .../docker_compose_alpha_publish.yaml | 10 +++++++ docs/reference/docker_compose_publish.yaml | 10 +++++++ internal/oci/resolver.go | 11 ++++---- pkg/api/api.go | 11 ++++++-- pkg/compose/loader.go | 10 +++---- pkg/compose/publish.go | 7 ++++- pkg/e2e/publish_test.go | 18 ++++++------ pkg/remote/oci.go | 19 +++++++------ 11 files changed, 97 insertions(+), 43 deletions(-) diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index bcafe158346..25774b300f5 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -131,16 +131,17 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error { } type ProjectOptions struct { - ProjectName string - Profiles []string - ConfigPaths []string - WorkDir string - ProjectDir string - EnvFiles []string - Compatibility bool - Progress string - Offline bool - All bool + ProjectName string + Profiles []string + ConfigPaths []string + WorkDir string + ProjectDir string + EnvFiles []string + Compatibility bool + Progress string + Offline bool + All bool + insecureRegistries []string } // ProjectFunc does stuff within a types.Project @@ -216,6 +217,8 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) { f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable") f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name") f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files") + f.StringArrayVar(&o.insecureRegistries, "insecure-registry", []string{}, "Use insecure registry to pull Compose OCI artifacts. Doesn't apply to images") + _ = f.MarkHidden("insecure-registry") f.StringArrayVar(&o.EnvFiles, "env-file", defaultStringArrayVar(ComposeEnvFiles), "Specify an alternate environment file") f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)") f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)") @@ -337,6 +340,9 @@ 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, + }, } project, err := backend.LoadProject(ctx, loadOpts) @@ -352,7 +358,7 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL return nil } git := remote.NewGitRemoteLoader(dockerCli, o.Offline) - oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline) + oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline, api.OCIOptions{}) return []loader.ResourceLoader{git, oci} } diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go index 86d526f31b8..b0556b4ed0c 100644 --- a/cmd/compose/publish.go +++ b/cmd/compose/publish.go @@ -36,6 +36,7 @@ type publishOptions struct { withEnvironment bool assumeYes bool app bool + insecureRegistry bool } func publishCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command { @@ -56,6 +57,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba flags.BoolVar(&opts.withEnvironment, "with-env", false, "Include environment variables in the published OCI artifact") flags.BoolVarP(&opts.assumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts`) flags.BoolVar(&opts.app, "app", false, "Published compose application (includes referenced images)") + flags.BoolVar(&opts.insecureRegistry, "insecure-registry", false, "Use insecure registry") flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { // assumeYes was introduced by mistake as `--y` if name == "y" { @@ -64,6 +66,8 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Ba } return pflag.NormalizedName(name) }) + // Should **only** be used for testing purpose, we don't want to promote use of insecure registries + _ = flags.MarkHidden("insecure-registry") return cmd } @@ -92,5 +96,6 @@ func runPublish(ctx context.Context, dockerCli command.Cli, backendOptions *Back Application: opts.app, OCIVersion: api.OCIVersion(opts.ociVersion), WithEnvironment: opts.withEnvironment, + InsecureRegistry: opts.insecureRegistry, }) } diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml index 02a39d93232..f4a12de3247 100644 --- a/docs/reference/docker_compose.yaml +++ b/docs/reference/docker_compose.yaml @@ -139,6 +139,17 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: insecure-registry + value_type: stringArray + default_value: '[]' + description: | + Use insecure registry to pull Compose OCI artifacts. Doesn't apply to images + deprecated: false + hidden: true + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: no-ansi value_type: bool default_value: "false" diff --git a/docs/reference/docker_compose_alpha_publish.yaml b/docs/reference/docker_compose_alpha_publish.yaml index 9ab6a20c453..9059cbf4869 100644 --- a/docs/reference/docker_compose_alpha_publish.yaml +++ b/docs/reference/docker_compose_alpha_publish.yaml @@ -15,6 +15,16 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: insecure-registry + value_type: bool + default_value: "false" + description: Use insecure registry + deprecated: false + hidden: true + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: oci-version value_type: string description: | diff --git a/docs/reference/docker_compose_publish.yaml b/docs/reference/docker_compose_publish.yaml index e939a8ff897..c3189d89c57 100644 --- a/docs/reference/docker_compose_publish.yaml +++ b/docs/reference/docker_compose_publish.yaml @@ -15,6 +15,16 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: insecure-registry + value_type: bool + default_value: "false" + description: Use insecure registry + deprecated: false + hidden: true + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: oci-version value_type: string description: | diff --git a/internal/oci/resolver.go b/internal/oci/resolver.go index 94d31421ea0..7d2f663b393 100644 --- a/internal/oci/resolver.go +++ b/internal/oci/resolver.go @@ -20,7 +20,7 @@ import ( "context" "io" "net/url" - "os" + "slices" "strings" "github.com/containerd/containerd/v2/core/remotes" @@ -35,7 +35,7 @@ import ( ) // NewResolver setup an OCI Resolver based on docker/cli config to provide registry credentials -func NewResolver(config *configfile.ConfigFile) remotes.Resolver { +func NewResolver(config *configfile.ConfigFile, insecureRegistries ...string) remotes.Resolver { return docker.NewResolver(docker.ResolverOptions{ Hosts: docker.ConfigureDefaultRegistries( docker.WithAuthorizer(docker.NewDockerAuthorizer( @@ -51,10 +51,9 @@ func NewResolver(config *configfile.ConfigFile) remotes.Resolver { return auth.Username, auth.Password, nil }), )), - docker.WithPlainHTTP(func(s string) (bool, error) { - // Used for testing **only** - _, b := os.LookupEnv("__TEST__INSECURE__REGISTRY__") - return b, nil + docker.WithPlainHTTP(func(domain string) (bool, error) { + // Should be used for testing **only** + return slices.Contains(insecureRegistries, domain), nil }), ), }) diff --git a/pkg/api/api.go b/pkg/api/api.go index 19b1ea4086f..f6734e13f62 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -69,6 +69,12 @@ type ProjectLoadOptions struct { // All registered listeners will be notified of events. // This is optional - pass nil or empty slice if not needed. LoadListeners []LoadListener + + OCI OCIOptions +} + +type OCIOptions struct { + InsecureRegistries []string } // Compose is the API interface one can use to programmatically use docker/compose in a third-party software @@ -484,8 +490,9 @@ type PublishOptions struct { ResolveImageDigests bool Application bool WithEnvironment bool - - OCIVersion OCIVersion + OCIVersion OCIVersion + // Use plain HTTP to access registry. Should only be used for testing purpose + InsecureRegistry bool } func (e Event) String() string { diff --git a/pkg/compose/loader.go b/pkg/compose/loader.go index 311181161b6..69840ff1af8 100644 --- a/pkg/compose/loader.go +++ b/pkg/compose/loader.go @@ -33,7 +33,7 @@ import ( // It loads and validates a Compose project from configuration files. func (s *composeService) LoadProject(ctx context.Context, options api.ProjectLoadOptions) (*types.Project, error) { // Setup remote loaders (Git, OCI) - remoteLoaders := s.createRemoteLoaders(options.Offline) + remoteLoaders := s.createRemoteLoaders(options) projectOptions, err := s.buildProjectOptions(options, remoteLoaders) if err != nil { @@ -66,12 +66,12 @@ func (s *composeService) LoadProject(ctx context.Context, options api.ProjectLoa } // createRemoteLoaders creates Git and OCI remote loaders if not in offline mode -func (s *composeService) createRemoteLoaders(offline bool) []loader.ResourceLoader { - if offline { +func (s *composeService) createRemoteLoaders(options api.ProjectLoadOptions) []loader.ResourceLoader { + if options.Offline { return nil } - git := remote.NewGitRemoteLoader(s.dockerCli, offline) - oci := remote.NewOCIRemoteLoader(s.dockerCli, offline) + git := remote.NewGitRemoteLoader(s.dockerCli, options.Offline) + oci := remote.NewOCIRemoteLoader(s.dockerCli, options.Offline, options.OCI) return []loader.ResourceLoader{git, oci} } diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index c1017bac201..5d7b0fe7b61 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -89,7 +89,12 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re return err } - resolver := oci.NewResolver(s.configFile()) + var insecureRegistries []string + if options.InsecureRegistry { + insecureRegistries = append(insecureRegistries, reference.Domain(named)) + } + + resolver := oci.NewResolver(s.configFile(), insecureRegistries...) descriptor, err := oci.PushManifest(ctx, resolver, named, layers, options.OCIVersion) if err != nil { diff --git a/pkg/e2e/publish_test.go b/pkg/e2e/publish_test.go index 33413d528d9..7e901552072 100644 --- a/pkg/e2e/publish_test.go +++ b/pkg/e2e/publish_test.go @@ -186,19 +186,17 @@ func TestPublish(t *testing.T) { c.RunDockerCmd(t, "rm", "--force", registryName) }) - cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/oci/compose.yaml", "-f", "./fixtures/publish/oci/compose-override.yaml", - "-p", projectName, "publish", "--with-env", "--yes", registry+"/test:test") - icmd.RunCmd(cmd, func(cmd *icmd.Cmd) { - cmd.Env = append(cmd.Env, "__TEST__INSECURE__REGISTRY__=true") - }).Assert(t, icmd.Expected{ExitCode: 0}) + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/oci/compose.yaml", "-f", "./fixtures/publish/oci/compose-override.yaml", + "-p", projectName, "publish", "--with-env", "--yes", "--insecure-registry", registry+"/test:test") + res.Assert(t, icmd.Expected{ExitCode: 0}) // docker exec -it compose-e2e-publish-registry tree /var/lib/registry/docker/registry/v2/ - cmd = c.NewDockerComposeCmd(t, "--verbose", "--project-name=oci", "-f", fmt.Sprintf("oci://%s/test:test", registry), "config") - res := icmd.RunCmd(cmd, func(cmd *icmd.Cmd) { - cmd.Env = append(cmd.Env, - "XDG_CACHE_HOME="+t.TempDir(), - "__TEST__INSECURE__REGISTRY__=true") + cmd := c.NewDockerComposeCmd(t, "--verbose", "--project-name=oci", + "--insecure-registry", registry, + "-f", fmt.Sprintf("oci://%s/test:test", registry), "config") + res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) { + cmd.Env = append(cmd.Env, "XDG_CACHE_HOME="+t.TempDir()) }) res.Assert(t, icmd.Expected{ExitCode: 0}) assert.Equal(t, res.Stdout(), `name: oci diff --git a/pkg/remote/oci.go b/pkg/remote/oci.go index 1055993da91..95ddc773365 100644 --- a/pkg/remote/oci.go +++ b/pkg/remote/oci.go @@ -31,6 +31,7 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/internal/oci" + "github.com/docker/compose/v2/pkg/api" spec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -76,18 +77,20 @@ func ociRemoteLoaderEnabled() (bool, error) { return true, nil } -func NewOCIRemoteLoader(dockerCli command.Cli, offline bool) loader.ResourceLoader { +func NewOCIRemoteLoader(dockerCli command.Cli, offline bool, options api.OCIOptions) loader.ResourceLoader { return ociRemoteLoader{ - dockerCli: dockerCli, - offline: offline, - known: map[string]string{}, + dockerCli: dockerCli, + offline: offline, + known: map[string]string{}, + insecureRegistries: options.InsecureRegistries, } } type ociRemoteLoader struct { - dockerCli command.Cli - offline bool - known map[string]string + dockerCli command.Cli + offline bool + known map[string]string + insecureRegistries []string } func (g ociRemoteLoader) Accept(path string) bool { @@ -115,7 +118,7 @@ func (g ociRemoteLoader) Load(ctx context.Context, path string) (string, error) return "", err } - resolver := oci.NewResolver(g.dockerCli.ConfigFile()) + resolver := oci.NewResolver(g.dockerCli.ConfigFile(), g.insecureRegistries...) descriptor, content, err := oci.Get(ctx, resolver, ref) if err != nil {