Merge pull request #3544 from thaJeztah/carry_2740_add_config_command
Add stack config command (carry 2740)
This commit is contained in:
@ -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)")
|
||||
|
||||
60
cli/command/stack/config.go
Normal file
60
cli/command/stack/config.go
Normal 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
|
||||
}
|
||||
106
cli/command/stack/config_test.go
Normal file
106
cli/command/stack/config_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user