Skip to content

Commit

Permalink
use compose-go to load and parse compose files
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
  • Loading branch information
glours committed Feb 9, 2024
1 parent 34797d1 commit 6abad62
Show file tree
Hide file tree
Showing 179 changed files with 26,025 additions and 7,857 deletions.
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"
"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
}
}
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

0 comments on commit 6abad62

Please sign in to comment.