diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 68e41969cb..9cbca4c421 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/inspect" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/docker/api/types" @@ -20,13 +21,42 @@ import ( "github.com/docker/docker/errdefs" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) +type objectType = string + +const ( + typeConfig objectType = "config" + typeContainer objectType = "container" + typeImage objectType = "image" + typeNetwork objectType = "network" + typeNode objectType = "node" + typePlugin objectType = "plugin" + typeSecret objectType = "secret" + typeService objectType = "service" + typeTask objectType = "task" + typeVolume objectType = "volume" +) + +var allTypes = []objectType{ + typeConfig, + typeContainer, + typeImage, + typeNetwork, + typeNode, + typePlugin, + typeSecret, + typeService, + typeTask, + typeVolume, +} + type inspectOptions struct { - format string - inspectType string - size bool - ids []string + format string + objectType objectType + size bool + ids []string } // NewInspectCommand creates a new cobra.Command for `docker inspect` @@ -39,25 +69,38 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.ids = args + if cmd.Flags().Changed("type") && opts.objectType == "" { + return fmt.Errorf(`type is empty: must be one of "%s"`, strings.Join(allTypes, `", "`)) + } return runInspect(cmd.Context(), dockerCli, opts) }, + // TODO(thaJeztah): should we consider adding completion for common object-types? (images, containers?) + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() flags.StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) - flags.StringVar(&opts.inspectType, "type", "", "Return JSON for specified type") + flags.StringVar(&opts.objectType, "type", "", "Only inspect objects of the given type") flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes if the type is container") + _ = cmd.RegisterFlagCompletionFunc("type", completion.FromList(allTypes...)) + flags.VisitAll(func(flag *pflag.Flag) { + // Set a default completion function if none was set. We don't look + // up if it does already have one set, because Cobra does this for + // us, and returns an error (which we ignore for this reason). + _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + }) return cmd } func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error { var elementSearcher inspect.GetRefFunc - switch opts.inspectType { - case "", "config", "container", "image", "network", "node", "plugin", "secret", "service", "task", "volume": - elementSearcher = inspectAll(ctx, dockerCli, opts.size, opts.inspectType) + switch opts.objectType { + case "", typeConfig, typeContainer, typeImage, typeNetwork, typeNode, + typePlugin, typeSecret, typeService, typeTask, typeVolume: + elementSearcher = inspectAll(ctx, dockerCli, opts.size, opts.objectType) default: - return errors.Errorf("%q is not a valid value for --type", opts.inspectType) + return errors.Errorf(`unknown type: %q: must be one of "%s"`, opts.objectType, strings.Join(allTypes, `", "`)) } return inspect.Inspect(dockerCli.Out(), opts.ids, opts.format, elementSearcher) } @@ -128,56 +171,56 @@ func inspectConfig(ctx context.Context, dockerCLI command.Cli) inspect.GetRefFun } } -func inspectAll(ctx context.Context, dockerCLI command.Cli, getSize bool, typeConstraint string) inspect.GetRefFunc { +func inspectAll(ctx context.Context, dockerCLI command.Cli, getSize bool, typeConstraint objectType) inspect.GetRefFunc { inspectAutodetect := []struct { - objectType string + objectType objectType isSizeSupported bool isSwarmObject bool objectInspector func(string) (any, []byte, error) }{ { - objectType: "container", + objectType: typeContainer, isSizeSupported: true, objectInspector: inspectContainers(ctx, dockerCLI, getSize), }, { - objectType: "image", + objectType: typeImage, objectInspector: inspectImages(ctx, dockerCLI), }, { - objectType: "network", + objectType: typeNetwork, objectInspector: inspectNetwork(ctx, dockerCLI), }, { - objectType: "volume", + objectType: typeVolume, objectInspector: inspectVolume(ctx, dockerCLI), }, { - objectType: "service", + objectType: typeService, isSwarmObject: true, objectInspector: inspectService(ctx, dockerCLI), }, { - objectType: "task", + objectType: typeTask, isSwarmObject: true, objectInspector: inspectTasks(ctx, dockerCLI), }, { - objectType: "node", + objectType: typeNode, isSwarmObject: true, objectInspector: inspectNode(ctx, dockerCLI), }, { - objectType: "plugin", + objectType: typePlugin, objectInspector: inspectPlugin(ctx, dockerCLI), }, { - objectType: "secret", + objectType: typeSecret, isSwarmObject: true, objectInspector: inspectSecret(ctx, dockerCLI), }, { - objectType: "config", + objectType: typeConfig, isSwarmObject: true, objectInspector: inspectConfig(ctx, dockerCLI), }, diff --git a/cli/command/system/inspect_test.go b/cli/command/system/inspect_test.go new file mode 100644 index 0000000000..56ad5fd5b5 --- /dev/null +++ b/cli/command/system/inspect_test.go @@ -0,0 +1,48 @@ +package system + +import ( + "io" + "testing" + + "github.com/docker/cli/internal/test" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestInspectValidateFlagsAndArgs(t *testing.T) { + for _, tc := range []struct { + name string + args []string + expectedErr string + }{ + { + name: "empty type", + args: []string{"--type", "", "something"}, + expectedErr: `type is empty: must be one of "config", "container", "image", "network", "node", "plugin", "secret", "service", "task", "volume"`, + }, + { + name: "unknown type", + args: []string{"--type", "unknown", "something"}, + expectedErr: `unknown type: "unknown": must be one of "config", "container", "image", "network", "node", "plugin", "secret", "service", "task", "volume"`, + }, + { + name: "no arg", + args: []string{}, + expectedErr: `inspect: 'inspect' requires at least 1 argument`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cmd := NewInspectCommand(test.NewFakeCli(&fakeClient{})) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.SetArgs(tc.args) + + err := cmd.Execute() + if tc.expectedErr != "" { + assert.Check(t, is.ErrorContains(err, tc.expectedErr)) + } else { + assert.Check(t, is.Nil(err)) + } + }) + } +} diff --git a/docs/reference/commandline/inspect.md b/docs/reference/commandline/inspect.md index ce25827e85..c41cc05576 100644 --- a/docs/reference/commandline/inspect.md +++ b/docs/reference/commandline/inspect.md @@ -9,7 +9,7 @@ Return low-level information on Docker objects |:---------------------------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [`-f`](#format), [`--format`](#format) | `string` | | Format output using a custom template:
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates | | [`-s`](#size), [`--size`](#size) | `bool` | | Display total file sizes if the type is container | -| [`--type`](#type) | `string` | | Return JSON for specified type | +| [`--type`](#type) | `string` | | Only inspect objects of the given type |