diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go index 1ed1d85a7..360a11b03 100644 --- a/cli/cmd/compose/compose.go +++ b/cli/cmd/compose/compose.go @@ -94,8 +94,9 @@ func (o *projectOptions) toProjectOptions() (*cli.ProjectOptions, error) { func Command(contextType string) *cobra.Command { opts := projectOptions{} command := &cobra.Command{ - Short: "Docker Compose", - Use: "compose", + Short: "Docker Compose", + Use: "compose", + TraverseChildren: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if contextType == store.DefaultContextType || contextType == store.LocalContextType { fmt.Println("The new 'docker compose' command is currently experimental. To provide feedback or request new features please open issues at https://github.com/docker/compose-cli") @@ -126,6 +127,6 @@ func Command(contextType string) *cobra.Command { ) } command.Flags().SetInterspersed(false) - opts.addProjectFlags(command.PersistentFlags()) + opts.addProjectFlags(command.Flags()) return command } diff --git a/cli/cmd/login/login.go b/cli/cmd/login/login.go index 7163dc965..e672e01be 100644 --- a/cli/cmd/login/login.go +++ b/cli/cmd/login/login.go @@ -25,7 +25,6 @@ import ( "github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/errdefs" - "github.com/docker/compose-cli/cli/cmd/mobyflags" "github.com/docker/compose-cli/cli/mobycli" ) @@ -43,7 +42,6 @@ func Command() *cobra.Command { flags.StringP("username", "u", "", "username") flags.StringP("password", "p", "", "password") flags.BoolP("password-stdin", "", false, "Take the password from stdin") - mobyflags.AddMobyFlagsForRetrocompatibility(flags) cmd.AddCommand(AzureLoginCommand()) return cmd diff --git a/cli/cmd/mobyflags/mobyflags.go b/cli/cmd/mobyflags/mobyflags.go deleted file mode 100644 index c116ec28f..000000000 --- a/cli/cmd/mobyflags/mobyflags.go +++ /dev/null @@ -1,37 +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 mobyflags - -import ( - "log" - - flag "github.com/spf13/pflag" -) - -// AddMobyFlagsForRetrocompatibility adds retrocompatibility flags to our commands -func AddMobyFlagsForRetrocompatibility(flags *flag.FlagSet) { - const logLevelFlag = "log-level" - flags.StringP(logLevelFlag, "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`) - markHidden(flags, logLevelFlag) -} - -func markHidden(flags *flag.FlagSet, flagName string) { - err := flags.MarkHidden(flagName) - if err != nil { - log.Fatal(err) - } -} diff --git a/cli/cmd/version.go b/cli/cmd/version.go index 83372aa34..89ea64770 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -23,7 +23,6 @@ import ( "github.com/spf13/cobra" - "github.com/docker/compose-cli/cli/cmd/mobyflags" "github.com/docker/compose-cli/cli/formatter" "github.com/docker/compose-cli/cli/mobycli" "github.com/docker/compose-cli/internal" @@ -45,7 +44,6 @@ func VersionCommand() *cobra.Command { flags := cmd.Flags() flags.StringP(formatOpt, "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)") flags.String("kubeconfig", "", "Kubernetes config file") - mobyflags.AddMobyFlagsForRetrocompatibility(flags) return cmd } diff --git a/cli/main.go b/cli/main.go index e479004a7..d05720ca3 100644 --- a/cli/main.go +++ b/cli/main.go @@ -102,9 +102,10 @@ func isContextAgnosticCommand(cmd *cobra.Command) bool { func main() { var opts cliopts.GlobalOpts root := &cobra.Command{ - Use: "docker", - SilenceErrors: true, - SilenceUsage: true, + Use: "docker", + SilenceErrors: true, + SilenceUsage: true, + TraverseChildren: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if !isContextAgnosticCommand(cmd) { mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root()) @@ -112,7 +113,10 @@ func main() { return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() + if len(args) == 0 { + return cmd.Help() + } + return fmt.Errorf("unknown command %q", args[0]) }, } @@ -146,19 +150,27 @@ func main() { helpFunc(cmd, args) }) - root.PersistentFlags().BoolVarP(&opts.Debug, "debug", "D", false, "Enable debug output in the logs") - root.PersistentFlags().StringVarP(&opts.Host, "host", "H", "", "Daemon socket(s) to connect to") - opts.AddContextFlags(root.PersistentFlags()) - opts.AddConfigFlags(root.PersistentFlags()) - root.Flags().BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") + flags := root.Flags() + flags.StringVarP(&opts.LogLevel, "log-level", "l", "info", "Set the logging level (\"debug\"|\"info\"|\"warn\"|\"error\"|\"fatal\")") + flags.BoolVarP(&opts.Debug, "debug", "D", false, "Enable debug output in the logs") + flags.StringVarP(&opts.Host, "host", "H", "", "Daemon socket(s) to connect to") + opts.AddContextFlags(flags) + opts.AddConfigFlags(flags) + flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") walk(root, func(c *cobra.Command) { c.Flags().BoolP("help", "h", false, "Help for "+c.Name()) }) // populate the opts with the global flags - _ = root.PersistentFlags().Parse(os.Args[1:]) - _ = root.Flags().Parse(os.Args[1:]) + flags.Parse(os.Args[1:]) //nolint: errcheck + + level, err := logrus.ParseLevel(opts.LogLevel) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", opts.LogLevel) + os.Exit(1) + } + logrus.SetLevel(level) if opts.Debug { logrus.SetLevel(logrus.DebugLevel) } @@ -200,30 +212,34 @@ func main() { ctx = store.WithContextStore(ctx, s) if err = root.ExecuteContext(ctx); err != nil { - // if user canceled request, simply exit without any error message - if errdefs.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) { - metrics.Track(ctype, os.Args[1:], metrics.CanceledStatus) - os.Exit(130) - } - if ctype == store.AwsContextType { - exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running: -$ docker context create %s `, cc.Type(), store.EcsContextType), ctype) - } - - // Context should always be handled by new CLI - requiredCmd, _, _ := root.Find(os.Args[1:]) - if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) { - exit(currentContext, err, ctype) - } - mobycli.ExecIfDefaultCtxType(ctx, root) - - checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype) - - exit(currentContext, err, ctype) + handleError(ctx, err, ctype, currentContext, cc, root) } metrics.Track(ctype, os.Args[1:], metrics.SuccessStatus) } +func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) { + // if user canceled request, simply exit without any error message + if errdefs.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) { + metrics.Track(ctype, os.Args[1:], metrics.CanceledStatus) + os.Exit(130) + } + if ctype == store.AwsContextType { + exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running: +$ docker context create %s `, cc.Type(), store.EcsContextType), ctype) + } + + // Context should always be handled by new CLI + requiredCmd, _, _ := root.Find(os.Args[1:]) + if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) { + exit(currentContext, err, ctype) + } + mobycli.ExecIfDefaultCtxType(ctx, root) + + checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype) + + exit(currentContext, err, ctype) +} + func exit(ctx string, err error, ctype string) { metrics.Track(ctype, os.Args[1:], metrics.FailureStatus) diff --git a/cli/options/options.go b/cli/options/options.go index 0d4aac187..764c9cb57 100644 --- a/cli/options/options.go +++ b/cli/options/options.go @@ -25,7 +25,8 @@ import ( type GlobalOpts struct { apicontext.ContextFlags cliconfig.ConfigFlags - Debug bool - Version bool - Host string + Debug bool + LogLevel string + Version bool + Host string } diff --git a/local/e2e/cli-only/e2e_test.go b/local/e2e/cli-only/e2e_test.go index a8e0455c5..40faf6f93 100644 --- a/local/e2e/cli-only/e2e_test.go +++ b/local/e2e/cli-only/e2e_test.go @@ -450,11 +450,6 @@ func TestLegacyLogin(t *testing.T) { Err: "WARNING! Using --password via the CLI is insecure", }) }) - - t.Run("login help global flags", func(t *testing.T) { - res := c.RunDockerCmd("login", "--help") - assert.Assert(t, !strings.Contains(res.Combined(), "--log-level")) - }) } func TestUnsupportedCommand(t *testing.T) { diff --git a/local/e2e/compose/logs_test.go b/local/e2e/compose/logs_test.go index 02d3399ef..33d120fa7 100644 --- a/local/e2e/compose/logs_test.go +++ b/local/e2e/compose/logs_test.go @@ -33,28 +33,28 @@ func TestLocalComposeLogs(t *testing.T) { const projectName = "compose-e2e-logs" t.Run("up", func(t *testing.T) { - c.RunDockerCmd("compose", "up", "-d", "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "-d") + c.RunDockerCmd("compose", "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d") }) t.Run("logs", func(t *testing.T) { - res := c.RunDockerCmd("compose", "logs", "--project-name", projectName) + res := c.RunDockerCmd("compose", "--project-name", projectName, "logs") res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`}) res.Assert(t, icmd.Expected{Out: `hello`}) }) t.Run("logs ping", func(t *testing.T) { - res := c.RunDockerCmd("compose", "logs", "--project-name", projectName, "ping") + res := c.RunDockerCmd("compose", "--project-name", projectName, "logs", "ping") res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`}) assert.Assert(t, !strings.Contains(res.Stdout(), "hello")) }) t.Run("logs hello", func(t *testing.T) { - res := c.RunDockerCmd("compose", "logs", "--project-name", projectName, "hello", "ping") + res := c.RunDockerCmd("compose", "--project-name", projectName, "logs", "hello", "ping") res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`}) res.Assert(t, icmd.Expected{Out: `hello`}) }) t.Run("down", func(t *testing.T) { - _ = c.RunDockerCmd("compose", "down", "--project-name", projectName) + _ = c.RunDockerCmd("compose", "--project-name", projectName, "down") }) }