Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use compose-go to load and parse compose files #4863

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 13 additions & 3 deletions cli/command/stack/config.go
Expand Up @@ -2,14 +2,16 @@ package stack

import (
"fmt"
"path/filepath"

specLoader "github.com/compose-spec/compose-go/v2/loader"
compose "github.com/compose-spec/compose-go/v2/types"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: keep it consistent with the other import names. Either stick with compose or composetypes.

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/stack/loader"
"github.com/docker/cli/cli/command/stack/options"
composeLoader "github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v2"
)
Expand Down Expand Up @@ -45,9 +47,17 @@ func newConfigCommand(dockerCli command.Cli) *cobra.Command {
}

// outputConfig returns the merged and interpolated config file
func outputConfig(configFiles composetypes.ConfigDetails, skipInterpolation bool) (string, error) {
optsFunc := func(opts *composeLoader.Options) {
func outputConfig(configFiles compose.ConfigDetails, skipInterpolation bool) (string, error) {
var dir string
for _, f := range configFiles.ConfigFiles {
if f.Filename != "" {
dir = filepath.Base(f.Filename)
break
}
}
Comment on lines +52 to +57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to iterate over the config files here? I don't know much about this part of the code, but would like to know what problem it solves. For example, In which cases would the filename be empty?

optsFunc := func(opts *specLoader.Options) {
opts.SkipInterpolation = skipInterpolation
opts.SetProjectName(specLoader.NormalizeProjectName(dir), true)
}
config, err := composeLoader.Load(configFiles, optsFunc)
if err != nil {
Expand Down
27 changes: 16 additions & 11 deletions cli/command/stack/config_test.go
Expand Up @@ -4,8 +4,8 @@ import (
"io"
"testing"

"github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
composetypes "github.com/compose-spec/compose-go/v2/types"

"github.com/docker/cli/internal/test"
"gotest.tools/v3/assert"
)
Expand Down Expand Up @@ -40,13 +40,18 @@ services:
image: busybox:${VERSION}
command: cat file2.txt
`,
expected: `version: "3.7"
expected: `name: firstconfig
services:
foo:
command:
- cat
- file2.txt
image: busybox:1.0
networks:
default: null
networks:
default:
name: firstconfig_default
`,
},
{
Expand All @@ -64,28 +69,28 @@ services:
image: busybox:${VERSION}
command: cat file2.txt
`,
expected: `version: "3.7"
expected: `name: firstconfig
services:
foo:
command:
- cat
- file2.txt
image: busybox:${VERSION}
networks:
default: null
networks:
default:
name: firstconfig_default
`,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
firstConfigData, err := loader.ParseYAML([]byte(tc.fileOne))
assert.Check(t, err)
secondConfigData, err := loader.ParseYAML([]byte(tc.fileTwo))
assert.Check(t, err)

actual, err := outputConfig(composetypes.ConfigDetails{
ConfigFiles: []composetypes.ConfigFile{
{Config: firstConfigData, Filename: "firstConfig"},
{Config: secondConfigData, Filename: "secondConfig"},
{Content: []byte(tc.fileOne), Filename: "firstConfig"},
{Content: []byte(tc.fileTwo), Filename: "secondConfig"},
},
Environment: map[string]string{
"VERSION": "1.0",
Expand Down
4 changes: 2 additions & 2 deletions cli/command/stack/deploy.go
Expand Up @@ -22,11 +22,11 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
if err := validateStackName(opts.Namespace); err != nil {
return err
}
config, err := loader.LoadComposefile(dockerCli, opts)
project, err := loader.LoadComposefile(dockerCli, opts)
if err != nil {
return err
}
return swarm.RunDeploy(cmd.Context(), dockerCli, opts, config)
return swarm.RunDeploy(cmd.Context(), dockerCli, opts, project)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
Expand Down
29 changes: 11 additions & 18 deletions cli/command/stack/loader/loader.go
Expand Up @@ -12,23 +12,26 @@ import (
"sort"
"strings"

specloader "github.com/compose-spec/compose-go/v2/loader"
composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/schema"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/pkg/errors"
)

// LoadComposefile parse the composefile specified in the cli and returns its Config and version.
func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes.Config, error) {
func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes.Project, error) {
configDetails, err := GetConfigDetails(opts.Composefiles, dockerCli.In())
if err != nil {
return nil, err
}

dicts := getDictsFrom(configDetails.ConfigFiles)
config, err := loader.Load(configDetails)
projectNameFunc := func(o *specloader.Options) {
o.SetProjectName(opts.Namespace, true)
}
project, err := loader.Load(configDetails, projectNameFunc)
if err != nil {
if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok {
// this error is intentionally formatted multi-line
Expand All @@ -38,28 +41,18 @@ func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes.
return nil, err
}

unsupportedProperties := loader.GetUnsupportedProperties(dicts...)
unsupportedProperties := loader.GetUnsupportedProperties(project.Services)
if len(unsupportedProperties) > 0 {
fmt.Fprintf(dockerCli.Err(), "Ignoring unsupported options: %s\n\n",
strings.Join(unsupportedProperties, ", "))
}

deprecatedProperties := loader.GetDeprecatedProperties(dicts...)
deprecatedProperties := loader.GetDeprecatedProperties(project.Services)
if len(deprecatedProperties) > 0 {
fmt.Fprintf(dockerCli.Err(), "Ignoring deprecated options:\n\n%s\n\n",
propertyWarnings(deprecatedProperties))
}
return config, nil
}

func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]any {
dicts := []map[string]any{}

for _, configFile := range configFiles {
dicts = append(dicts, configFile.Config)
}

return dicts
return project, nil
}

func propertyWarnings(properties map[string]string) string {
Expand Down Expand Up @@ -157,7 +150,7 @@ func loadConfigFile(filename string, stdin io.Reader) (*composetypes.ConfigFile,
return nil, err
}

config, err := loader.ParseYAML(bytes)
config, err := specloader.ParseYAML(bytes)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions cli/command/stack/swarm/deploy.go
Expand Up @@ -4,10 +4,10 @@ import (
"context"
"fmt"

composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
Expand All @@ -22,7 +22,7 @@ const (
)

// RunDeploy is the swarm implementation of docker stack deploy
func RunDeploy(ctx context.Context, dockerCli command.Cli, opts options.Deploy, cfg *composetypes.Config) error {
func RunDeploy(ctx context.Context, dockerCli command.Cli, opts options.Deploy, project *composetypes.Project) error {
if err := validateResolveImageFlag(&opts); err != nil {
return err
}
Expand All @@ -32,7 +32,7 @@ func RunDeploy(ctx context.Context, dockerCli command.Cli, opts options.Deploy,
opts.ResolveImage = ResolveImageNever
}

return deployCompose(ctx, dockerCli, opts, cfg)
return deployCompose(ctx, dockerCli, opts, project)
}

// validateResolveImageFlag validates the opts.resolveImage command line option
Expand Down
18 changes: 9 additions & 9 deletions cli/command/stack/swarm/deploy_composefile.go
Expand Up @@ -4,10 +4,10 @@ import (
"context"
"fmt"

composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/swarm"
Expand All @@ -16,7 +16,7 @@ import (
"github.com/pkg/errors"
)

func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy, config *composetypes.Config) error {
func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy, project *composetypes.Project) error {
if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
return err
}
Expand All @@ -25,45 +25,45 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl

if opts.Prune {
services := map[string]struct{}{}
for _, service := range config.Services {
for _, service := range project.Services {
services[service.Name] = struct{}{}
}
pruneServices(ctx, dockerCli, namespace, services)
}

serviceNetworks := getServicesDeclaredNetworks(config.Services)
networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks)
serviceNetworks := getServicesDeclaredNetworks(project.Services)
networks, externalNetworks := convert.Networks(namespace, project.Networks, serviceNetworks)
if err := validateExternalNetworks(ctx, dockerCli.Client(), externalNetworks); err != nil {
return err
}
if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
return err
}

secrets, err := convert.Secrets(namespace, config.Secrets)
secrets, err := convert.Secrets(namespace, project.Secrets)
if err != nil {
return err
}
if err := createSecrets(ctx, dockerCli, secrets); err != nil {
return err
}

configs, err := convert.Configs(namespace, config.Configs)
configs, err := convert.Configs(namespace, project.Configs)
if err != nil {
return err
}
if err := createConfigs(ctx, dockerCli, configs); err != nil {
return err
}

services, err := convert.Services(ctx, namespace, config, dockerCli.Client())
services, err := convert.Services(ctx, namespace, project, dockerCli.Client())
if err != nil {
return err
}
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
}

func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
func getServicesDeclaredNetworks(serviceConfigs composetypes.Services) map[string]struct{} {
serviceNetworks := map[string]struct{}{}
for _, serviceConfig := range serviceConfigs {
if len(serviceConfig.Networks) == 0 {
Expand Down
10 changes: 5 additions & 5 deletions cli/compose/convert/compose.go
Expand Up @@ -4,7 +4,7 @@ import (
"os"
"strings"

composetypes "github.com/docker/cli/cli/compose/types"
composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
Expand Down Expand Up @@ -52,7 +52,7 @@ func AddStackLabel(namespace Namespace, labels map[string]string) map[string]str
type networkMap map[string]composetypes.NetworkConfig

// Networks from the compose-file type to the engine API type
func Networks(namespace Namespace, networks networkMap, servicesNetworks map[string]struct{}) (map[string]types.NetworkCreate, []string) {
func Networks(namespace Namespace, networks composetypes.Networks, servicesNetworks map[string]struct{}) (map[string]types.NetworkCreate, []string) {
if networks == nil {
networks = make(map[string]composetypes.NetworkConfig)
}
Expand All @@ -61,7 +61,7 @@ func Networks(namespace Namespace, networks networkMap, servicesNetworks map[str
result := make(map[string]types.NetworkCreate)
for internalName := range servicesNetworks {
network := networks[internalName]
if network.External.External {
if network.External {
externalNetworks = append(externalNetworks, network.Name)
continue
}
Expand Down Expand Up @@ -102,7 +102,7 @@ func Networks(namespace Namespace, networks networkMap, servicesNetworks map[str
func Secrets(namespace Namespace, secrets map[string]composetypes.SecretConfig) ([]swarm.SecretSpec, error) {
result := []swarm.SecretSpec{}
for name, secret := range secrets {
if secret.External.External {
if secret.External {
continue
}

Expand Down Expand Up @@ -137,7 +137,7 @@ func Secrets(namespace Namespace, secrets map[string]composetypes.SecretConfig)
func Configs(namespace Namespace, configs map[string]composetypes.ConfigObjConfig) ([]swarm.ConfigSpec, error) {
result := []swarm.ConfigSpec{}
for name, config := range configs {
if config.External.External {
if config.External {
continue
}

Expand Down
14 changes: 5 additions & 9 deletions cli/compose/convert/compose_test.go
Expand Up @@ -3,7 +3,7 @@ package convert
import (
"testing"

composetypes "github.com/docker/cli/cli/compose/types"
composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"gotest.tools/v3/assert"
Expand Down Expand Up @@ -37,7 +37,7 @@ func TestNetworks(t *testing.T) {
"attachablenet": {},
"named": {},
}
source := networkMap{
source := composetypes.Networks{
"normal": composetypes.NetworkConfig{
Driver: "overlay",
DriverOpts: map[string]string{
Expand All @@ -56,7 +56,7 @@ func TestNetworks(t *testing.T) {
},
},
"outside": composetypes.NetworkConfig{
External: composetypes.External{External: true},
External: true,
Name: "special",
},
"attachablenet": composetypes.NetworkConfig{
Expand Down Expand Up @@ -121,9 +121,7 @@ func TestSecrets(t *testing.T) {
Labels: map[string]string{"monster": "mash"},
},
"ext": {
External: composetypes.External{
External: true,
},
External: true,
},
}

Expand Down Expand Up @@ -152,9 +150,7 @@ func TestConfigs(t *testing.T) {
Labels: map[string]string{"monster": "mash"},
},
"ext": {
External: composetypes.External{
External: true,
},
External: true,
},
}

Expand Down