mirror of
https://github.com/docker/cli.git
synced 2026-01-13 18:22:35 +03:00
Add stack config command
Make use of existing modules and functions in order to output the merged configs. Added skip interpolation flag of variables, so that you can pipe the output back to stack deploy without much hassle. Signed-off-by: Stoica-Marcu Floris-Andrei <floris.sm@gmail.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
committed by
Sebastiaan van Stijn
parent
429d716fbc
commit
dfc214115b
@@ -33,6 +33,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