Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/command/stack/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&opts.Bundlefile, "bundle-file", "", "Path to a Distributed Application Bundle file")
flags.SetAnnotation("bundle-file", "experimental", nil)
flags.SetAnnotation("bundle-file", "swarm", nil)
flags.StringVarP(&opts.Composefile, "compose-file", "c", "", "Path to a Compose file")
flags.StringSliceVarP(&opts.Composefiles, "compose-file", "c", []string{}, "Path to a Compose file")
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
flags.BoolVar(&opts.SendRegistryAuth, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
flags.SetAnnotation("with-registry-auth", "swarm", nil)
Expand Down
6 changes: 3 additions & 3 deletions cli/command/stack/kubernetes/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
cmdOut := dockerCli.Out()
// Check arguments
if opts.Composefile == "" {
return errors.Errorf("Please specify a Compose file (with --compose-file).")
if len(opts.Composefiles) == 0 {
return errors.Errorf("Please specify only one compose file (with --compose-file).")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This feature doesn't work with kubernetes deploy?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@dnephin not right now because we parse the file differently. I want to tackle this in a follow-up (i.e. use the same loading/parsing code for both swarm and k8s and thus remove this limitation).

}
// Initialize clients
stacks, err := dockerCli.stacks()
Expand All @@ -37,7 +37,7 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
}

// Parse the compose file
stack, cfg, err := LoadStack(opts.Namespace, opts.Composefile)
stack, cfg, err := LoadStack(opts.Namespace, opts.Composefiles)
if err != nil {
return err
}
Expand Down
7 changes: 4 additions & 3 deletions cli/command/stack/kubernetes/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import (

// LoadStack loads a stack from a Compose file, with a given name.
// FIXME(vdemeester) remove this and use cli/compose/loader for both swarm and kubernetes
func LoadStack(name, composeFile string) (*apiv1beta1.Stack, *composetypes.Config, error) {
if composeFile == "" {
return nil, nil, errors.New("compose-file must be set")
func LoadStack(name string, composeFiles []string) (*apiv1beta1.Stack, *composetypes.Config, error) {
if len(composeFiles) != 1 {
return nil, nil, errors.New("compose-file must be set (and only one)")
}
composeFile := composeFiles[0]

workingDir, err := os.Getwd()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cli/command/stack/options/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "github.com/docker/cli/opts"
// Deploy holds docker stack deploy options
type Deploy struct {
Bundlefile string
Composefile string
Composefiles []string
Namespace string
ResolveImage string
SendRegistryAuth bool
Expand Down
4 changes: 2 additions & 2 deletions cli/command/stack/swarm/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ func RunDeploy(dockerCli command.Cli, opts options.Deploy) error {
}

switch {
case opts.Bundlefile == "" && opts.Composefile == "":
case opts.Bundlefile == "" && len(opts.Composefiles) == 0:
return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
case opts.Bundlefile != "" && opts.Composefile != "":
case opts.Bundlefile != "" && len(opts.Composefiles) != 0:
return errors.Errorf("You cannot specify both a bundle file and a Compose file.")
case opts.Bundlefile != "":
return deployBundle(ctx, dockerCli, opts)
Expand Down
48 changes: 38 additions & 10 deletions cli/command/stack/swarm/deploy_composefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
)

func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error {
configDetails, err := getConfigDetails(opts.Composefile, dockerCli.In())
configDetails, err := getConfigDetails(opts.Composefiles, dockerCli.In())
if err != nil {
return err
}
Expand All @@ -39,13 +39,14 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl
return err
}

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

deprecatedProperties := loader.GetDeprecatedProperties(configDetails)
deprecatedProperties := loader.GetDeprecatedProperties(dicts...)
if len(deprecatedProperties) > 0 {
fmt.Fprintf(dockerCli.Err(), "Ignoring deprecated options:\n\n%s\n\n",
propertyWarnings(deprecatedProperties))
Expand Down Expand Up @@ -97,6 +98,16 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
}

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

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

return dicts
}

func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
serviceNetworks := map[string]struct{}{}
for _, serviceConfig := range serviceConfigs {
Expand All @@ -120,29 +131,32 @@ func propertyWarnings(properties map[string]string) string {
return strings.Join(msgs, "\n\n")
}

func getConfigDetails(composefile string, stdin io.Reader) (composetypes.ConfigDetails, error) {
func getConfigDetails(composefiles []string, stdin io.Reader) (composetypes.ConfigDetails, error) {
var details composetypes.ConfigDetails

if composefile == "-" {
if len(composefiles) == 0 {
return details, errors.New("no composefile(s)")
}

if composefiles[0] == "-" && len(composefiles) == 1 {
workingDir, err := os.Getwd()
if err != nil {
return details, err
}
details.WorkingDir = workingDir
} else {
absPath, err := filepath.Abs(composefile)
absPath, err := filepath.Abs(composefiles[0])
if err != nil {
return details, err
}
details.WorkingDir = filepath.Dir(absPath)
}

configFile, err := getConfigFile(composefile, stdin)
var err error
details.ConfigFiles, err = loadConfigFiles(composefiles, stdin)
if err != nil {
return details, err
}
// TODO: support multiple files
details.ConfigFiles = []composetypes.ConfigFile{*configFile}
details.Environment, err = buildEnvironment(os.Environ())
return details, err
}
Expand All @@ -160,7 +174,21 @@ func buildEnvironment(env []string) (map[string]string, error) {
return result, nil
}

func getConfigFile(filename string, stdin io.Reader) (*composetypes.ConfigFile, error) {
func loadConfigFiles(filenames []string, stdin io.Reader) ([]composetypes.ConfigFile, error) {
var configFiles []composetypes.ConfigFile

for _, filename := range filenames {
configFile, err := loadConfigFile(filename, stdin)
if err != nil {
return configFiles, err
}
configFiles = append(configFiles, *configFile)
}

return configFiles, nil
}

func loadConfigFile(filename string, stdin io.Reader) (*composetypes.ConfigFile, error) {
var bytes []byte
var err error

Expand Down
4 changes: 2 additions & 2 deletions cli/command/stack/swarm/deploy_composefile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ services:
file := fs.NewFile(t, "test-get-config-details", fs.WithContent(content))
defer file.Remove()

details, err := getConfigDetails(file.Path(), nil)
details, err := getConfigDetails([]string{file.Path()}, nil)
require.NoError(t, err)
assert.Equal(t, filepath.Dir(file.Path()), details.WorkingDir)
require.Len(t, details.ConfigFiles, 1)
Expand All @@ -41,7 +41,7 @@ services:
foo:
image: alpine:3.5
`
details, err := getConfigDetails("-", strings.NewReader(content))
details, err := getConfigDetails([]string{"-"}, strings.NewReader(content))
require.NoError(t, err)
cwd, err := os.Getwd()
require.NoError(t, err)
Expand Down
80 changes: 51 additions & 29 deletions cli/compose/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,43 @@ func Load(configDetails types.ConfigDetails) (*types.Config, error) {
if len(configDetails.ConfigFiles) < 1 {
return nil, errors.Errorf("No files specified")
}
if len(configDetails.ConfigFiles) > 1 {
return nil, errors.Errorf("Multiple files are not yet supported")
}

configDict := getConfigDict(configDetails)
configDetails.Version = schema.Version(configDict)
configs := []*types.Config{}

if err := validateForbidden(configDict); err != nil {
return nil, err
}
for _, file := range configDetails.ConfigFiles {
configDict := file.Config
version := schema.Version(configDict)
if configDetails.Version == "" {
configDetails.Version = version
}
if configDetails.Version != version {
return nil, errors.Errorf("version mismatched between two composefiles : %v and %v", configDetails.Version, version)
}

var err error
configDict, err = interpolateConfig(configDict, configDetails.LookupEnv)
if err != nil {
return nil, err
}
if err := validateForbidden(configDict); err != nil {
return nil, err
}

if err := schema.Validate(configDict, configDetails.Version); err != nil {
return nil, err
var err error
configDict, err = interpolateConfig(configDict, configDetails.LookupEnv)
if err != nil {
return nil, err
}

if err := schema.Validate(configDict, configDetails.Version); err != nil {
return nil, err
}

cfg, err := loadSections(configDict, configDetails)
if err != nil {
return nil, err
}
cfg.Filename = file.Filename

configs = append(configs, cfg)
}
return loadSections(configDict, configDetails)

return merge(configs)
}

func validateForbidden(configDict map[string]interface{}) error {
Expand Down Expand Up @@ -142,14 +158,16 @@ func getSection(config map[string]interface{}, key string) map[string]interface{

// GetUnsupportedProperties returns the list of any unsupported properties that are
// used in the Compose files.
func GetUnsupportedProperties(configDetails types.ConfigDetails) []string {
func GetUnsupportedProperties(configDicts ...map[string]interface{}) []string {
unsupported := map[string]bool{}

for _, service := range getServices(getConfigDict(configDetails)) {
serviceDict := service.(map[string]interface{})
for _, property := range types.UnsupportedProperties {
if _, isSet := serviceDict[property]; isSet {
unsupported[property] = true
for _, configDict := range configDicts {
for _, service := range getServices(configDict) {
serviceDict := service.(map[string]interface{})
for _, property := range types.UnsupportedProperties {
if _, isSet := serviceDict[property]; isSet {
unsupported[property] = true
}
}
}
}
Expand All @@ -168,8 +186,17 @@ func sortedKeys(set map[string]bool) []string {

// GetDeprecatedProperties returns the list of any deprecated properties that
// are used in the compose files.
func GetDeprecatedProperties(configDetails types.ConfigDetails) map[string]string {
return getProperties(getServices(getConfigDict(configDetails)), types.DeprecatedProperties)
func GetDeprecatedProperties(configDicts ...map[string]interface{}) map[string]string {
deprecated := map[string]string{}

for _, configDict := range configDicts {
deprecatedProperties := getProperties(getServices(configDict), types.DeprecatedProperties)
for key, value := range deprecatedProperties {
deprecated[key] = value
}
}

return deprecated
}

func getProperties(services map[string]interface{}, propertyMap map[string]string) map[string]string {
Expand Down Expand Up @@ -198,11 +225,6 @@ func (e *ForbiddenPropertiesError) Error() string {
return "Configuration contains forbidden properties"
}

// TODO: resolve multiple files into a single config
func getConfigDict(configDetails types.ConfigDetails) map[string]interface{} {
return configDetails.ConfigFiles[0].Config
}

func getServices(configDict map[string]interface{}) map[string]interface{} {
if services, ok := configDict["services"]; ok {
if servicesDict, ok := services.(map[string]interface{}); ok {
Expand Down
5 changes: 3 additions & 2 deletions cli/compose/loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ networks:
config, err := Load(buildConfigDetails(dict, env))
require.NoError(t, err)
expected := &types.Config{
Filename: "filename.yml",
Services: []types.ServiceConfig{
{
Name: "web",
Expand Down Expand Up @@ -670,7 +671,7 @@ services:
_, err = Load(configDetails)
require.NoError(t, err)

unsupported := GetUnsupportedProperties(configDetails)
unsupported := GetUnsupportedProperties(dict)
assert.Equal(t, []string{"build", "links", "pid"}, unsupported)
}

Expand Down Expand Up @@ -713,7 +714,7 @@ services:
_, err = Load(configDetails)
require.NoError(t, err)

deprecated := GetDeprecatedProperties(configDetails)
deprecated := GetDeprecatedProperties(dict)
assert.Len(t, deprecated, 2)
assert.Contains(t, deprecated, "container_name")
assert.Contains(t, deprecated, "expose")
Expand Down
Loading