Browse Source

restore support for `config --no-interpolate`

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 1 năm trước cách đây
mục cha
commit
1680f9a874
9 tập tin đã thay đổi với 127 bổ sung82 xóa
  1. 20 2
      cmd/compose/compose.go
  2. 69 19
      cmd/compose/config.go
  3. 2 2
      go.mod
  4. 2 2
      go.sum
  5. 0 2
      pkg/api/api.go
  6. 1 33
      pkg/compose/compose.go
  7. 1 12
      pkg/compose/publish.go
  8. 18 0
      pkg/compose/pull.go
  9. 14 10
      pkg/e2e/compose_test.go

+ 20 - 2
cmd/compose/compose.go

@@ -201,6 +201,24 @@ func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cl
 	return project.Name, nil
 }
 
+func (o *ProjectOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
+	remotes := o.remoteLoaders(dockerCli)
+	for _, r := range remotes {
+		po = append(po, cli.WithResourceLoader(r))
+	}
+
+	options, err := o.toProjectOptions(po...)
+	if err != nil {
+		return nil, err
+	}
+
+	if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
+		api.Separator = "_"
+	}
+
+	return options.LoadModel(ctx)
+}
+
 func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) {
 	var metrics tracing.Metrics
 
@@ -241,7 +259,7 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s
 		api.Separator = "_"
 	}
 
-	project, err := cli.ProjectFromOptions(ctx, options)
+	project, err := options.LoadProject(ctx)
 	if err != nil {
 		return nil, metrics, compose.WrapComposeError(err)
 	}
@@ -468,7 +486,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
 		psCommand(&opts, dockerCli, backend),
 		listCommand(dockerCli, backend),
 		logsCommand(&opts, dockerCli, backend),
-		configCommand(&opts, dockerCli, backend),
+		configCommand(&opts, dockerCli),
 		killCommand(&opts, dockerCli, backend),
 		runCommand(&opts, dockerCli, backend),
 		removeCommand(&opts, dockerCli, backend),

+ 69 - 19
cmd/compose/config.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
 	"os"
 	"sort"
@@ -28,8 +29,8 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
+	"gopkg.in/yaml.v3"
 
-	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/compose"
 )
@@ -51,18 +52,29 @@ type configOptions struct {
 	noConsistency       bool
 }
 
-func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) {
-	po = append(po,
+func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
+	po = append(po, o.ToProjectOptions()...)
+	project, _, err := o.ProjectOptions.ToProject(ctx, dockerCli, services, po...)
+	return project, err
+}
+
+func (o *configOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
+	po = append(po, o.ToProjectOptions()...)
+	return o.ProjectOptions.ToModel(ctx, dockerCli, services, po...)
+}
+
+func (o *configOptions) ToProjectOptions() []cli.ProjectOptionsFn {
+	return []cli.ProjectOptionsFn{
 		cli.WithInterpolation(!o.noInterpolate),
 		cli.WithResolvedPaths(!o.noResolvePath),
 		cli.WithNormalization(!o.noNormalize),
 		cli.WithConsistency(!o.noConsistency),
 		cli.WithDefaultProfiles(o.Profiles...),
-		cli.WithDiscardEnvFile)
-	return o.ProjectOptions.ToProject(ctx, dockerCli, services, po...)
+		cli.WithDiscardEnvFile,
+	}
 }
 
-func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
+func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
 	opts := configOptions{
 		ProjectOptions: p,
 	}
@@ -100,7 +112,7 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
 				return runConfigImages(ctx, dockerCli, opts, args)
 			}
 
-			return runConfig(ctx, dockerCli, backend, opts, args)
+			return runConfig(ctx, dockerCli, opts, args)
 		}),
 		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
@@ -123,18 +135,56 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
 	return cmd
 }
 
-func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, opts configOptions, services []string) error {
+func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
 	var content []byte
-	project, _, err := opts.ToProject(ctx, dockerCli, services)
+	model, err := opts.ToModel(ctx, dockerCli, services)
 	if err != nil {
 		return err
 	}
 
-	content, err = backend.Config(ctx, project, api.ConfigOptions{
-		Format:              opts.Format,
-		Output:              opts.Output,
-		ResolveImageDigests: opts.resolveImageDigests,
-	})
+	if opts.resolveImageDigests {
+		// create a pseudo-project so we can rely on WithImagesResolved to resolve images
+		p := &types.Project{
+			Services: types.Services{},
+		}
+		services := model["services"].(map[string]any)
+		for name, s := range services {
+			service := s.(map[string]any)
+			if image, ok := service["image"]; ok {
+				p.Services[name] = types.ServiceConfig{
+					Image: image.(string),
+				}
+			}
+		}
+
+		p, err = p.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client()))
+		if err != nil {
+			return err
+		}
+
+		for name, s := range services {
+			service := s.(map[string]any)
+			config := p.Services[name]
+			if config.Image != "" {
+				service["image"] = config.Image
+			}
+			services[name] = service
+		}
+		model["services"] = services
+	}
+
+	switch opts.Format {
+	case "json":
+		content, err = json.MarshalIndent(model, "", "  ")
+	case "yaml":
+		buf := bytes.NewBuffer([]byte{})
+		encoder := yaml.NewEncoder(buf)
+		encoder.SetIndent(2)
+		err = encoder.Encode(model)
+		content = buf.Bytes()
+	default:
+		return fmt.Errorf("unsupported format %q", opts.Format)
+	}
 	if err != nil {
 		return err
 	}
@@ -155,7 +205,7 @@ func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service,
 }
 
 func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
-	project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
+	project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
 	if err != nil {
 		return err
 	}
@@ -167,7 +217,7 @@ func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions)
 }
 
 func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
-	project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
+	project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
 	if err != nil {
 		return err
 	}
@@ -182,7 +232,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
 	if opts.hash != "*" {
 		services = append(services, strings.Split(opts.hash, ",")...)
 	}
-	project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
+	project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
 	if err != nil {
 		return err
 	}
@@ -218,7 +268,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
 
 func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
 	set := map[string]struct{}{}
-	project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
+	project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
 	if err != nil {
 		return err
 	}
@@ -239,7 +289,7 @@ func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions,
 }
 
 func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
-	project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
+	project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
 	if err != nil {
 		return err
 	}

+ 2 - 2
go.mod

@@ -6,7 +6,7 @@ require (
 	github.com/AlecAivazis/survey/v2 v2.3.7
 	github.com/Microsoft/go-winio v0.6.1
 	github.com/buger/goterm v1.0.4
-	github.com/compose-spec/compose-go/v2 v2.0.0-rc.8
+	github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60
 	github.com/containerd/console v1.0.3
 	github.com/containerd/containerd v1.7.12
 	github.com/davecgh/go-spew v1.1.1
@@ -49,6 +49,7 @@ require (
 	golang.org/x/sync v0.6.0
 	golang.org/x/sys v0.16.0
 	google.golang.org/grpc v1.59.0
+	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools/v3 v3.5.1
 )
 
@@ -170,7 +171,6 @@ require (
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/api v0.26.7 // indirect
 	k8s.io/apimachinery v0.26.7 // indirect
 	k8s.io/apiserver v0.26.7 // indirect

+ 2 - 2
go.sum

@@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
 github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
-github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 h1:b7l+GqFF+2W4M4kLQUDRTGhqmTiRwT3bYd9X7xrxp5Q=
-github.com/compose-spec/compose-go/v2 v2.0.0-rc.8/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc=
+github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60 h1:NlkpaLBPFr05mNJWVMH7PP4L30gFG6k4z1QpypLUSh8=
+github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc=
 github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
 github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
 github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=

+ 0 - 2
pkg/api/api.go

@@ -52,8 +52,6 @@ type Service interface {
 	Ps(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
 	// List executes the equivalent to a `docker stack ls`
 	List(ctx context.Context, options ListOptions) ([]Stack, error)
-	// Config executes the equivalent to a `compose config`
-	Config(ctx context.Context, project *types.Project, options ConfigOptions) ([]byte, error)
 	// Kill executes the equivalent to a `compose kill`
 	Kill(ctx context.Context, projectName string, options KillOptions) error
 	// RunOneOffContainer creates a service oneoff container and starts its dependencies

+ 1 - 33
pkg/compose/compose.go

@@ -25,12 +25,10 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/jonboulle/clockwork"
-
 	"github.com/docker/docker/api/types/volume"
+	"github.com/jonboulle/clockwork"
 
 	"github.com/compose-spec/compose-go/v2/types"
-	"github.com/distribution/reference"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/config/configfile"
 	"github.com/docker/cli/cli/flags"
@@ -40,7 +38,6 @@ import (
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/client"
-	"github.com/opencontainers/go-digest"
 )
 
 var stdioToStdout bool
@@ -147,35 +144,6 @@ func getContainerNameWithoutProject(c moby.Container) string {
 	return name[len(project)+1:]
 }
 
-func (s *composeService) Config(ctx context.Context, project *types.Project, options api.ConfigOptions) ([]byte, error) {
-	if options.ResolveImageDigests {
-		var err error
-		project, err = project.WithImagesResolved(func(named reference.Named) (digest.Digest, error) {
-			auth, err := encodedAuth(named, s.configFile())
-			if err != nil {
-				return "", err
-			}
-			inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth)
-			if err != nil {
-				return "", err
-			}
-			return inspect.Descriptor.Digest, nil
-		})
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	switch options.Format {
-	case "json":
-		return project.MarshalJSON()
-	case "yaml":
-		return project.MarshalYAML()
-	default:
-		return nil, fmt.Errorf("unsupported format %q", options.Format)
-	}
-}
-
 // projectFromName builds a types.Project based on actual resources with compose labels set
 func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
 	project := &types.Project{

+ 1 - 12
pkg/compose/publish.go

@@ -26,7 +26,6 @@ import (
 	"github.com/docker/compose/v2/internal/ocipush"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/progress"
-	"github.com/opencontainers/go-digest"
 )
 
 func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
@@ -111,17 +110,7 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje
 	if err != nil {
 		return nil, err
 	}
-	project, err = project.WithImagesResolved(func(named reference.Named) (digest.Digest, error) {
-		auth, err := encodedAuth(named, s.configFile())
-		if err != nil {
-			return "", err
-		}
-		inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth)
-		if err != nil {
-			return "", err
-		}
-		return inspect.Descriptor.Digest, nil
-	})
+	project, err = project.WithImagesResolved(ImageDigestResolver(ctx, s.configFile(), s.apiClient()))
 	if err != nil {
 		return nil, err
 	}

+ 18 - 0
pkg/compose/pull.go

@@ -28,10 +28,13 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/distribution/reference"
 	"github.com/docker/buildx/driver"
+	"github.com/docker/cli/cli/config/configfile"
 	moby "github.com/docker/docker/api/types"
+	"github.com/docker/docker/client"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/registry"
 	"github.com/hashicorp/go-multierror"
+	"github.com/opencontainers/go-digest"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -242,6 +245,21 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
 	return inspected.ID, nil
 }
 
+// ImageDigestResolver creates a func able to resolve image digest from a docker ref,
+func ImageDigestResolver(ctx context.Context, file *configfile.ConfigFile, apiClient client.APIClient) func(named reference.Named) (digest.Digest, error) {
+	return func(named reference.Named) (digest.Digest, error) {
+		auth, err := encodedAuth(named, file)
+		if err != nil {
+			return "", err
+		}
+		inspect, err := apiClient.DistributionInspect(ctx, named.String(), auth)
+		if err != nil {
+			return "", err
+		}
+		return inspect.Descriptor.Digest, nil
+	}
+}
+
 func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
 	repoInfo, err := registry.ParseRepositoryInfo(ref)
 	if err != nil {

+ 14 - 10
pkg/e2e/compose_test.go

@@ -235,7 +235,7 @@ func TestCompatibility(t *testing.T) {
 	})
 }
 
-func TestConvert(t *testing.T) {
+func TestConfig(t *testing.T) {
 	const projectName = "compose-e2e-convert"
 	c := NewParallelCLI(t)
 
@@ -244,20 +244,22 @@ func TestConvert(t *testing.T) {
 
 	t.Run("up", func(t *testing.T) {
 		res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
-		res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
+		res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s
+networks:
+  default:
+    name: compose-e2e-convert_default
+services:
   nginx:
     build:
       context: %s
       dockerfile: Dockerfile
     networks:
       default: null
-networks:
-  default:
-    name: compose-e2e-convert_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
+`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
 	})
 }
 
-func TestConvertInterpolate(t *testing.T) {
+func TestConfigInterpolate(t *testing.T) {
 	const projectName = "compose-e2e-convert-interpolate"
 	c := NewParallelCLI(t)
 
@@ -266,16 +268,18 @@ func TestConvertInterpolate(t *testing.T) {
 
 	t.Run("convert", func(t *testing.T) {
 		res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose-interpolate.yaml", "-p", projectName, "convert", "--no-interpolate")
-		res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
+		res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s
+networks:
+  default:
+    name: compose-e2e-convert-interpolate_default
+services:
   nginx:
     build:
       context: %s
       dockerfile: ${MYVAR}
     networks:
       default: null
-networks:
-  default:
-    name: compose-e2e-convert-interpolate_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
+`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
 	})
 }