Merge pull request #3544 from thaJeztah/carry_2740_add_config_command

Add stack config command (carry 2740)
This commit is contained in:
Sebastiaan van Stijn
2022-04-29 14:03:41 +02:00
committed by GitHub
12 changed files with 427 additions and 11 deletions

View File

@ -34,6 +34,7 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
newPsCommand(dockerCli),
newRemoveCommand(dockerCli),
newServicesCommand(dockerCli),
newConfigCommand(dockerCli),
)
flags := cmd.PersistentFlags()
flags.String("orchestrator", "", "Orchestrator to use (swarm|all)")

View File

@ -0,0 +1,60 @@
package stack
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"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"
)
func newConfigCommand(dockerCli command.Cli) *cobra.Command {
var opts options.Config
cmd := &cobra.Command{
Use: "config [OPTIONS]",
Short: "Outputs the final config file, after doing merges and interpolations",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
configDetails, err := loader.GetConfigDetails(opts.Composefiles, dockerCli.In())
if err != nil {
return err
}
cfg, err := outputConfig(configDetails, opts.SkipInterpolation)
if err != nil {
return err
}
_, err = fmt.Fprintf(dockerCli.Out(), "%s", cfg)
return err
},
}
flags := cmd.Flags()
flags.StringSliceVarP(&opts.Composefiles, "compose-file", "c", []string{}, `Path to a Compose file, or "-" to read from stdin`)
flags.BoolVar(&opts.SkipInterpolation, "skip-interpolation", false, "Skip interpolation and output only merged config")
return cmd
}
// outputConfig returns the merged and interpolated config file
func outputConfig(configFiles composetypes.ConfigDetails, skipInterpolation bool) (string, error) {
optsFunc := func(options *composeLoader.Options) {
options.SkipInterpolation = skipInterpolation
}
config, err := composeLoader.Load(configFiles, optsFunc)
if err != nil {
return "", err
}
d, err := yaml.Marshal(&config)
if err != nil {
return "", err
}
return string(d), nil
}

View File

@ -0,0 +1,106 @@
package stack
import (
"io"
"testing"
"github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/cli/internal/test"
"gotest.tools/v3/assert"
)
func TestConfigWithEmptyComposeFile(t *testing.T) {
cmd := newConfigCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
assert.ErrorContains(t, cmd.Execute(), `Please specify a Compose file`)
}
var configMergeTests = []struct {
name string
skipInterpolation bool
first string
second string
merged string
}{
{
name: "With Interpolation",
skipInterpolation: false,
first: `version: "3.7"
services:
foo:
image: busybox:latest
command: cat file1.txt
`,
second: `version: "3.7"
services:
foo:
image: busybox:${VERSION}
command: cat file2.txt
`,
merged: `version: "3.7"
services:
foo:
command:
- cat
- file2.txt
image: busybox:1.0
`,
},
{
name: "Without Interpolation",
skipInterpolation: true,
first: `version: "3.7"
services:
foo:
image: busybox:latest
command: cat file1.txt
`,
second: `version: "3.7"
services:
foo:
image: busybox:${VERSION}
command: cat file2.txt
`,
merged: `version: "3.7"
services:
foo:
command:
- cat
- file2.txt
image: busybox:${VERSION}
`,
},
}
func TestConfigMergeInterpolation(t *testing.T) {
for _, tt := range configMergeTests {
t.Run(tt.name, func(t *testing.T) {
firstConfig := []byte(tt.first)
secondConfig := []byte(tt.second)
firstConfigData, err := loader.ParseYAML(firstConfig)
assert.NilError(t, err)
secondConfigData, err := loader.ParseYAML(secondConfig)
assert.NilError(t, err)
env := map[string]string{
"VERSION": "1.0",
}
cfg, err := outputConfig(composetypes.ConfigDetails{
ConfigFiles: []composetypes.ConfigFile{
{Config: firstConfigData, Filename: "firstConfig"},
{Config: secondConfigData, Filename: "secondConfig"},
},
Environment: env,
}, tt.skipInterpolation)
assert.NilError(t, err)
assert.Equal(t, cfg, tt.merged)
})
}
}

View File

@ -18,7 +18,7 @@ import (
// 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) {
configDetails, err := getConfigDetails(opts.Composefiles, dockerCli.In())
configDetails, err := GetConfigDetails(opts.Composefiles, dockerCli.In())
if err != nil {
return nil, err
}
@ -68,7 +68,8 @@ func propertyWarnings(properties map[string]string) string {
return strings.Join(msgs, "\n\n")
}
func getConfigDetails(composefiles []string, stdin io.Reader) (composetypes.ConfigDetails, error) {
// GetConfigDetails parse the composefiles specified in the cli and returns their ConfigDetails
func GetConfigDetails(composefiles []string, stdin io.Reader) (composetypes.ConfigDetails, error) {
var details composetypes.ConfigDetails
if len(composefiles) == 0 {

View File

@ -21,7 +21,7 @@ services:
file := fs.NewFile(t, "test-get-config-details", fs.WithContent(content))
defer file.Remove()
details, err := getConfigDetails([]string{file.Path()}, nil)
details, err := GetConfigDetails([]string{file.Path()}, nil)
assert.NilError(t, err)
assert.Check(t, is.Equal(filepath.Dir(file.Path()), details.WorkingDir))
assert.Assert(t, is.Len(details.ConfigFiles, 1))
@ -36,7 +36,7 @@ services:
foo:
image: alpine:3.5
`
details, err := getConfigDetails([]string{"-"}, strings.NewReader(content))
details, err := GetConfigDetails([]string{"-"}, strings.NewReader(content))
assert.NilError(t, err)
cwd, err := os.Getwd()
assert.NilError(t, err)

View File

@ -11,6 +11,12 @@ type Deploy struct {
Prune bool
}
// Config holds docker stack config options
type Config struct {
Composefiles []string
SkipInterpolation bool
}
// List holds docker stack ls options
type List struct {
Format string