diff --git a/aci/compose.go b/aci/compose.go index c63c2b28..61555f64 100644 --- a/aci/compose.go +++ b/aci/compose.go @@ -22,9 +22,9 @@ import ( "net/http" "github.com/compose-spec/compose-go/types" + "github.com/docker/compose-cli/utils" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/docker/compose-cli/aci/convert" @@ -86,11 +86,36 @@ func (cs *aciComposeService) Copy(ctx context.Context, project *types.Project, o } func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { + if err := checkUnsupportedUpOptions(options); err != nil { + return err + } return progress.Run(ctx, func(ctx context.Context) error { return cs.up(ctx, project) }) } +func checkUnsupportedUpOptions(o api.UpOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Start.CascadeStop, false, "abort-on-container-exit"}, + {o.Create.RecreateDependencies, "", "always-recreate-deps"}, + {len(o.Start.AttachTo), 0, "attach-dependencies"}, + {len(o.Start.ExitCodeFrom), 0, "exit-code-from"}, + {o.Create.Recreate, "", "force-recreate"}, + {o.Create.QuietPull, false, "quiet-pull"}, + {o.Create.RemoveOrphans, false, "remove-orphans"}, + {o.Create.Inherit, true, "renew-anon-volumes"}, + {o.Create.Timeout, nil, "timeout"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "up", c.option) + } + return errs +} + func (cs *aciComposeService) up(ctx context.Context, project *types.Project) error { logrus.Debugf("Up on project with name %q", project.Name) @@ -130,11 +155,8 @@ func (cs aciComposeService) warnKeepVolumeOnDown(ctx context.Context, projectNam } func (cs *aciComposeService) Down(ctx context.Context, projectName string, options api.DownOptions) error { - if options.Volumes { - return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on ACI") - } - if options.Images != "" { - return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on ACI") + if err := checkUnsupportedDownOptions(options); err != nil { + return err } return progress.Run(ctx, func(ctx context.Context) error { logrus.Debugf("Down on project with name %q", projectName) @@ -155,7 +177,17 @@ func (cs *aciComposeService) Down(ctx context.Context, projectName string, optio }) } +func checkUnsupportedDownOptions(o api.DownOptions) error { + var errs error + errs = utils.CheckUnsupported(errs, o.Volumes, false, "down", "volumes") + errs = utils.CheckUnsupported(errs, o.Images, "", "down", "images") + return errs +} + func (cs *aciComposeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { + if err := checkUnsupportedPsOptions(options); err != nil { + return nil, err + } groupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID) if err != nil { return nil, err @@ -198,7 +230,14 @@ func (cs *aciComposeService) Ps(ctx context.Context, projectName string, options return res, nil } +func checkUnsupportedPsOptions(o api.PsOptions) error { + return utils.CheckUnsupported(nil, o.All, false, "ps", "all") +} + func (cs *aciComposeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) { + if err := checkUnsupportedListOptions(opts); err != nil { + return nil, err + } containerGroups, err := getACIContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup) if err != nil { return nil, err @@ -226,6 +265,10 @@ func (cs *aciComposeService) List(ctx context.Context, opts api.ListOptions) ([] return stacks, nil } +func checkUnsupportedListOptions(o api.ListOptions) error { + return utils.CheckUnsupported(nil, o.All, false, "ls", "all") +} + func (cs *aciComposeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { return api.ErrNotImplemented } diff --git a/cli/config/flags.go b/cli/config/flags.go index f38d967e..3e33db37 100644 --- a/cli/config/flags.go +++ b/cli/config/flags.go @@ -35,10 +35,10 @@ type ConfigFlags struct { // AddConfigFlags adds persistent (global) flags func (c *ConfigFlags) AddConfigFlags(flags *pflag.FlagSet) { - flags.StringVar(&c.Config, config.ConfigFlagName, confDir(), "Location of the client config files `DIRECTORY`") + flags.StringVar(&c.Config, config.ConfigFlagName, ConfDir(), "Location of the client config files `DIRECTORY`") } -func confDir() string { +func ConfDir() string { env := os.Getenv("DOCKER_CONFIG") if env != "" { return env diff --git a/cli/main.go b/cli/main.go index 2b82a8cc..88c57df8 100644 --- a/cli/main.go +++ b/cli/main.go @@ -324,7 +324,7 @@ func exit(ctx string, err error, ctype string) { if errors.Is(err, api.ErrNotImplemented) { name := metrics.GetCommand(os.Args[1:]) - fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx) + fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s). %q\n", name, ctx, err) os.Exit(1) } diff --git a/cmd/compose/down.go b/cmd/compose/down.go new file mode 100644 index 00000000..588f7f08 --- /dev/null +++ b/cmd/compose/down.go @@ -0,0 +1,94 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "context" + "fmt" + "time" + + "github.com/compose-spec/compose-go/types" + "github.com/spf13/cobra" + + "github.com/docker/compose-cli/pkg/api" +) + +type downOptions struct { + *projectOptions + removeOrphans bool + timeChanged bool + timeout int + volumes bool + images string +} + +func downCommand(p *projectOptions, backend api.Service) *cobra.Command { + opts := downOptions{ + projectOptions: p, + } + downCmd := &cobra.Command{ + Use: "down", + Short: "Stop and remove containers, networks", + PreRun: func(cmd *cobra.Command, args []string) { + opts.timeChanged = cmd.Flags().Changed("timeout") + }, + PreRunE: Adapt(func(ctx context.Context, args []string) error { + if opts.images != "" { + if opts.images != "all" && opts.images != "local" { + return fmt.Errorf("invalid value for --rmi: %q", opts.images) + } + } + return nil + }), + RunE: Adapt(func(ctx context.Context, args []string) error { + return runDown(ctx, backend, opts) + }), + ValidArgsFunction: noCompletion(), + } + flags := downCmd.Flags() + flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") + flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds") + flags.BoolVarP(&opts.volumes, "volumes", "v", false, "Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.") + flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`) + return downCmd +} + +func runDown(ctx context.Context, backend api.Service, opts downOptions) error { + name := opts.ProjectName + var project *types.Project + if opts.ProjectName == "" { + p, err := opts.toProject(nil) + if err != nil { + return err + } + project = p + name = p.Name + } + + var timeout *time.Duration + if opts.timeChanged { + timeoutValue := time.Duration(opts.timeout) * time.Second + timeout = &timeoutValue + } + return backend.Down(ctx, name, api.DownOptions{ + RemoveOrphans: opts.removeOrphans, + Project: project, + Timeout: timeout, + Images: opts.images, + Volumes: opts.volumes, + }) +} diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go index 5d3a6176..46dee5e1 100644 --- a/ecs/cloudformation.go +++ b/ecs/cloudformation.go @@ -38,6 +38,7 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/distribution/distribution/v3/reference" cliconfig "github.com/docker/cli/cli/config" + "github.com/docker/compose-cli/utils" "github.com/docker/compose/v2/pkg/api" "github.com/opencontainers/go-digest" "sigs.k8s.io/kustomize/kyaml/yaml" @@ -46,11 +47,10 @@ import ( "github.com/docker/compose-cli/api/config" ) -func (b *ecsAPIService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { - return api.ErrNotImplemented -} - func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) { + if err := checkUnsupportedConvertOptions(options); err != nil { + return nil, err + } err := b.resolveServiceImagesDigests(ctx, project) if err != nil { return nil, err @@ -102,6 +102,10 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, opt return bytes, err } +func checkUnsupportedConvertOptions(o api.ConvertOptions) error { + return utils.CheckUnsupported(nil, o.Output, "", "convert", "output") +} + func (b *ecsAPIService) resolveServiceImagesDigests(ctx context.Context, project *types.Project) error { configFile, err := cliconfig.Load(config.Dir()) if err != nil { @@ -368,8 +372,8 @@ func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.S strings.ToUpper(port.Protocol), port.Target, ) - //add listener to dependsOn - //https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer + // add listener to dependsOn + // https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer template.Resources[listenerName] = &elasticloadbalancingv2.Listener{ DefaultActions: []elasticloadbalancingv2.Listener_Action{ { diff --git a/ecs/down.go b/ecs/down.go index 45ae72dd..4ec28fb5 100644 --- a/ecs/down.go +++ b/ecs/down.go @@ -19,17 +19,14 @@ package ecs import ( "context" + "github.com/docker/compose-cli/utils" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" - "github.com/pkg/errors" ) func (b *ecsAPIService) Down(ctx context.Context, projectName string, options api.DownOptions) error { - if options.Volumes { - return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on ECS") - } - if options.Images != "" { - return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on ECS") + if err := checkUnsupportedDownOptions(options); err != nil { + return err } return progress.Run(ctx, func(ctx context.Context) error { return b.down(ctx, projectName) @@ -89,3 +86,21 @@ func doDelete(ctx context.Context, delete func(ctx context.Context, arn string) return nil } } + +func checkUnsupportedDownOptions(o api.DownOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Volumes, false, "volumes"}, + {o.Images, "", "images"}, + {o.RemoveOrphans, false, "remove-orphans"}, + {o.Timeout, nil, "timeout"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "down", c.option) + } + + return errs +} diff --git a/ecs/exec.go b/ecs/exec.go deleted file mode 100644 index 3d6d05e4..00000000 --- a/ecs/exec.go +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/compose/v2/pkg/api" -) - -func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} diff --git a/ecs/images.go b/ecs/images.go deleted file mode 100644 index 525025e6..00000000 --- a/ecs/images.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/docker/compose/v2/pkg/api" -) - -func (b *ecsAPIService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/ecs/list.go b/ecs/list.go index d54b2c45..27c01300 100644 --- a/ecs/list.go +++ b/ecs/list.go @@ -20,10 +20,14 @@ import ( "context" "fmt" + "github.com/docker/compose-cli/utils" "github.com/docker/compose/v2/pkg/api" ) func (b *ecsAPIService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) { + if err := checkUnsupportedListOptions(opts); err != nil { + return nil, err + } stacks, err := b.aws.ListStacks(ctx) if err != nil { return nil, err @@ -41,6 +45,10 @@ func (b *ecsAPIService) List(ctx context.Context, opts api.ListOptions) ([]api.S } +func checkUnsupportedListOptions(o api.ListOptions) error { + return utils.CheckUnsupported(nil, o.All, false, "ls", "all") +} + func (b *ecsAPIService) checkStackState(ctx context.Context, name string) error { resources, err := b.aws.ListStackResources(ctx, name) if err != nil { diff --git a/ecs/logs.go b/ecs/logs.go index 830bafd8..0cdb22d8 100644 --- a/ecs/logs.go +++ b/ecs/logs.go @@ -25,9 +25,29 @@ import ( ) func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { + if err := checkUnsupportedLogOptions(options); err != nil { + return err + } if len(options.Services) > 0 { consumer = utils.FilteredLogConsumer(consumer, options.Services) } err := b.aws.GetLogs(ctx, projectName, consumer.Log, options.Follow) return err } + +func checkUnsupportedLogOptions(o api.LogOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Since, "", "since"}, + {o.Tail, "", "tail"}, + {o.Timestamps, false, "timestamps"}, + {o.Until, "", "until"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "logs", c.option) + } + return errs +} diff --git a/ecs/notimplemented.go b/ecs/notimplemented.go new file mode 100644 index 00000000..3a579d67 --- /dev/null +++ b/ecs/notimplemented.go @@ -0,0 +1,96 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ecs + +import ( + "context" + + "github.com/compose-spec/compose-go/types" + "github.com/docker/compose/v2/pkg/api" +) + +func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Pause(ctx context.Context, project string, options api.PauseOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) UnPause(ctx context.Context, project string, options api.PauseOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Events(ctx context.Context, project string, options api.EventsOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) { + return "", 0, api.ErrNotImplemented +} + +func (b *ecsAPIService) Copy(ctx context.Context, project *types.Project, options api.CopyOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { + return 0, api.ErrNotImplemented +} + +func (b *ecsAPIService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { + return api.ErrNotImplemented +} + +func (b *ecsAPIService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { + return nil, api.ErrNotImplemented +} + +func (b *ecsAPIService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) { + return nil, api.ErrNotImplemented +} + +func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { + return 0, api.ErrNotImplemented +} + +func (b *ecsAPIService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { + return api.ErrNotImplemented +} diff --git a/ecs/ps.go b/ecs/ps.go index 0e8bad32..f91b48b6 100644 --- a/ecs/ps.go +++ b/ecs/ps.go @@ -19,10 +19,15 @@ package ecs import ( "context" + "github.com/docker/compose-cli/utils" "github.com/docker/compose/v2/pkg/api" ) func (b *ecsAPIService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { + if err := checkUnsupportedPsOptions(options); err != nil { + return nil, err + } + cluster, err := b.aws.GetStackClusterID(ctx, projectName) if err != nil { return nil, err @@ -56,3 +61,9 @@ func (b *ecsAPIService) Ps(ctx context.Context, projectName string, options api. } return summary, nil } + +func checkUnsupportedPsOptions(o api.PsOptions) error { + var errs error + errs = utils.CheckUnsupported(errs, o.All, false, "ps", "all") + return errs +} diff --git a/ecs/run.go b/ecs/run.go deleted file mode 100644 index 9d993c08..00000000 --- a/ecs/run.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/compose-spec/compose-go/types" - - "github.com/docker/compose/v2/pkg/api" -) - -func (b *ecsAPIService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} - -func (b *ecsAPIService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { - return api.ErrNotImplemented -} diff --git a/ecs/top.go b/ecs/top.go deleted file mode 100644 index 8ba06b3a..00000000 --- a/ecs/top.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/docker/compose/v2/pkg/api" -) - -func (b *ecsAPIService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/ecs/up.go b/ecs/up.go index 57fe6f0c..39718ed2 100644 --- a/ecs/up.go +++ b/ecs/up.go @@ -24,60 +24,16 @@ import ( "syscall" "github.com/compose-spec/compose-go/types" + "github.com/docker/compose-cli/utils" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" "github.com/sirupsen/logrus" ) -func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Pause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) UnPause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Events(ctx context.Context, project string, options api.EventsOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) { - return "", 0, api.ErrNotImplemented -} - -func (b *ecsAPIService) Copy(ctx context.Context, project *types.Project, options api.CopyOptions) error { - return api.ErrNotImplemented -} - func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { + if err := checkUnsupportedUpOptions(options); err != nil { + return err + } return progress.Run(ctx, func(ctx context.Context) error { return b.up(ctx, project, options) }) @@ -142,3 +98,24 @@ func (b *ecsAPIService) up(ctx context.Context, project *types.Project, options err = b.WaitStackCompletion(ctx, project.Name, operation, previousEvents...) return err } + +func checkUnsupportedUpOptions(o api.UpOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Create.Inherit, true, "renew-anon-volumes"}, + {o.Create.RemoveOrphans, false, "remove-orphans"}, + {o.Create.QuietPull, false, "quiet-pull"}, + {o.Create.Recreate, api.RecreateDiverged, "force-recreate"}, + {o.Create.RecreateDependencies, api.RecreateDiverged, "always-recreate-deps"}, + {len(o.Start.AttachTo), 0, "attach-dependencies"}, + {len(o.Start.ExitCodeFrom), 0, "exit-code-from"}, + {o.Create.Timeout, nil, "timeout"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "up", c.option) + } + return errs +} diff --git a/go.mod b/go.mod index d6df130c..837c2bb4 100644 --- a/go.mod +++ b/go.mod @@ -19,12 +19,12 @@ require ( github.com/awslabs/goformation/v4 v4.15.6 github.com/buger/goterm v1.0.0 github.com/cnabio/cnab-to-oci v0.3.1-beta1 - github.com/compose-spec/compose-go v0.0.0-20210901090333-feb401cda7f7 + github.com/compose-spec/compose-go v0.0.0-20210906143156-938b039f7805 github.com/containerd/console v1.0.2 github.com/containerd/containerd v1.5.4 github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e github.com/docker/cli v20.10.7+incompatible - github.com/docker/compose/v2 v2.0.0-20210902070652-5b30accf351f + github.com/docker/compose/v2 v2.0.0-rc.3.0.20210916150857-15cd03448553 github.com/docker/docker v20.10.7+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 diff --git a/go.sum b/go.sum index f1f0f634..73fd24b0 100644 --- a/go.sum +++ b/go.sum @@ -255,8 +255,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/compose-spec/compose-go v0.0.0-20210901090333-feb401cda7f7 h1:wI9VC+EgX61YYB/xyQVb9BBfsFOklZ/BinipIpFcWn4= -github.com/compose-spec/compose-go v0.0.0-20210901090333-feb401cda7f7/go.mod h1:Hnmn5ZCVA3sSBN2urjCZNNIyNqCPayRGH7PmMSaV2Q0= +github.com/compose-spec/compose-go v0.0.0-20210906143156-938b039f7805 h1:iFShT4oPik+NnXdzksa4yLvJYDDZBPMjHPN3qtJaXPA= +github.com/compose-spec/compose-go v0.0.0-20210906143156-938b039f7805/go.mod h1:Hnmn5ZCVA3sSBN2urjCZNNIyNqCPayRGH7PmMSaV2Q0= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -412,9 +412,10 @@ github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHv github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli-docs-tool v0.1.1/go.mod h1:oMzPNt1wC3TcxuY22GMnOODNOxkwGH51gV3AhqAjFQ4= github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE= -github.com/docker/compose/v2 v2.0.0-20210902070652-5b30accf351f h1:YjG63wEdSZGdZYt/EOEclCQldN5Bg0jPNWDx//psKEw= -github.com/docker/compose/v2 v2.0.0-20210902070652-5b30accf351f/go.mod h1:9t3b24Terzo7Dx37p5Vtb7JEMPekAsnZUf5JmRblivg= +github.com/docker/compose/v2 v2.0.0-rc.3.0.20210916150857-15cd03448553 h1:uv/kMiCOXbCaFR8vn6pISh50pPWo01rCeSHVxBXGVes= +github.com/docker/compose/v2 v2.0.0-rc.3.0.20210916150857-15cd03448553/go.mod h1:1ElTlmGxnapGk1DF8EscG9uozzScMMBn3wkDeiQCE5k= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= diff --git a/kube/compose.go b/kube/compose.go index 76149d82..1792a29c 100644 --- a/kube/compose.go +++ b/kube/compose.go @@ -27,7 +27,6 @@ import ( "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" utils2 "github.com/docker/compose/v2/pkg/utils" - "github.com/pkg/errors" apicontext "github.com/docker/compose-cli/api/context" "github.com/docker/compose-cli/api/context/store" @@ -72,11 +71,35 @@ func NewComposeService() (api.Service, error) { // Up executes the equivalent to a `compose up` func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { + if err := checkUnsupportedUpOptions(options); err != nil { + return err + } return progress.Run(ctx, func(ctx context.Context) error { return s.up(ctx, project) }) } +func checkUnsupportedUpOptions(o api.UpOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Create.Inherit, true, "renew-anon-volumes"}, + {o.Create.RemoveOrphans, false, "remove-orphans"}, + {o.Create.QuietPull, false, "quiet-pull"}, + {o.Create.Recreate, api.RecreateDiverged, "force-recreate"}, + {o.Create.RecreateDependencies, api.RecreateDiverged, "always-recreate-deps"}, + {len(o.Start.AttachTo), 0, "attach-dependencies"}, + {len(o.Start.ExitCodeFrom), 0, "exit-code-from"}, + {o.Create.Timeout, nil, "timeout"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "up", c.option) + } + return errs +} + func (s *composeService) up(ctx context.Context, project *types.Project) error { w := progress.ContextWriter(ctx) @@ -132,17 +155,30 @@ func (s *composeService) up(ctx context.Context, project *types.Project) error { // Down executes the equivalent to a `compose down` func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error { - if options.Volumes { - return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on Kubernetes") - } - if options.Images != "" { - return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on Kubernetes") + if err := checkUnsupportedDownOptions(options); err != nil { + return err } return progress.Run(ctx, func(ctx context.Context) error { return s.down(ctx, projectName, options) }) } +func checkUnsupportedDownOptions(o api.DownOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Volumes, false, "volumes"}, + {o.Images, "", "images"}, + {o.RemoveOrphans, false, "remove-orphans"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "down", c.option) + } + return errs +} + func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error { w := progress.ContextWriter(ctx) eventName := fmt.Sprintf("Remove %s", projectName) @@ -193,9 +229,16 @@ func (s *composeService) down(ctx context.Context, projectName string, options a // List executes the equivalent to a `docker stack ls` func (s *composeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) { + if err := checkUnsupportedListOptions(opts); err != nil { + return nil, err + } return s.sdk.ListReleases() } +func checkUnsupportedListOptions(o api.ListOptions) error { + return utils.CheckUnsupported(nil, o.All, false, "ls", "all") +} + // Build executes the equivalent to a `compose build` func (s *composeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { return api.ErrNotImplemented @@ -238,12 +281,31 @@ func (s *composeService) Copy(ctx context.Context, project *types.Project, optio // Logs executes the equivalent to a `compose logs` func (s *composeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { + if err := checkUnsupportedLogOptions(options); err != nil { + return err + } if len(options.Services) > 0 { consumer = utils.FilteredLogConsumer(consumer, options.Services) } return s.client.GetLogs(ctx, projectName, consumer, options.Follow) } +func checkUnsupportedLogOptions(o api.LogOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Since, "", "since"}, + {o.Timestamps, false, "timestamps"}, + {o.Until, "", "until"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "logs", c.option) + } + return errs +} + // Ps executes the equivalent to a `compose ps` func (s *composeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { return s.client.GetContainers(ctx, projectName, options.All) @@ -251,7 +313,6 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api // Convert translate compose model into backend's native format func (s *composeService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) { - chart, err := helm.GetChartInMemory(project) if err != nil { return nil, err @@ -287,9 +348,28 @@ func (s *composeService) Remove(ctx context.Context, project *types.Project, opt // Exec executes a command in a running service container func (s *composeService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { + if err := checkUnsupportedExecOptions(opts); err != nil { + return 0, err + } return 0, s.client.Exec(ctx, project.Name, opts) } +func checkUnsupportedExecOptions(o api.RunOptions) error { + var errs error + checks := []struct { + toCheck, expected interface{} + option string + }{ + {o.Index, 0, "index"}, + {o.Privileged, false, "privileged"}, + {o.WorkingDir, "", "workdir"}, + } + for _, c := range checks { + errs = utils.CheckUnsupported(errs, c.toCheck, c.expected, "exec", c.option) + } + return errs +} + func (s *composeService) Pause(ctx context.Context, project string, options api.PauseOptions) error { return api.ErrNotImplemented } diff --git a/utils/check.go b/utils/check.go new file mode 100644 index 00000000..c61d5bb8 --- /dev/null +++ b/utils/check.go @@ -0,0 +1,60 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package utils + +import ( + "fmt" + "strings" + "time" + + "github.com/docker/compose-cli/api/context/store" + "github.com/docker/compose-cli/cli/config" + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" +) + +var ErrUnsupportedFlag = errors.New("unsupported flag") + +func CheckUnsupported(errs error, toCheck, expectedValue interface{}, commandName, msg string) error { + ctype := store.DefaultContextType + currentContext := config.GetCurrentContext("", config.ConfDir(), nil) + s, err := store.New(config.ConfDir()) + if err != nil { + return err + } + cc, err := s.Get(currentContext) + if err != nil { + return err + } + if cc != nil { + ctype = cc.Type() + } + // Fixes type for posterior comparison + switch toCheck.(type) { + case *time.Duration: + if expectedValue == nil { + var nilDurationPtr *time.Duration + expectedValue = nilDurationPtr + } + } + if toCheck != expectedValue { + return multierror.Append(errs, errors.Wrap(ErrUnsupportedFlag, + fmt.Sprintf(`option "%s --%s" on context type %s.`, + commandName, msg, strings.ToUpper(ctype)))) + } + return errs +}