From f61e2bb6f15e44791cf4165ba04835d56f799f69 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 6 May 2025 14:05:32 +0200 Subject: [PATCH 1/5] inspect: add consts / enum for object-types Signed-off-by: Sebastiaan van Stijn --- cli/command/system/inspect.go | 58 ++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 68e41969cb..4aa4c64b0d 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -22,11 +22,26 @@ import ( "github.com/spf13/cobra" ) +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" +) + 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` @@ -45,7 +60,7 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { 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", "", "Return JSON for specified type") flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes if the type is container") return cmd @@ -53,11 +68,12 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { 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("%q is not a valid value for --type", opts.objectType) } return inspect.Inspect(dockerCli.Out(), opts.ids, opts.format, elementSearcher) } @@ -128,56 +144,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), }, From 877ea1ce3549efd7882d3097e336eefff32b5783 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 6 May 2025 14:23:15 +0200 Subject: [PATCH 2/5] inspect: disable default (file) completion Before this patch, flags and arguments would complete using filenames from the current directory; docker inspect --type AUTHORS CONTRIBUTING.md docs/ Makefile SECURITY.md ... docker inspect With this patch, no completion is provided; docker inspect --type # no results docker inspect # no results Signed-off-by: Sebastiaan van Stijn --- cli/command/system/inspect.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 4aa4c64b0d..7b0d84a55f 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,6 +21,7 @@ import ( "github.com/docker/docker/errdefs" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) type objectType = string @@ -56,6 +58,8 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { opts.ids = args 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() @@ -63,6 +67,12 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&opts.objectType, "type", "", "Return JSON for specified type") flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes if the type is container") + 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 } From 7203340f532588aecb42790c07309c9254ea12b0 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 6 May 2025 14:27:53 +0200 Subject: [PATCH 3/5] inspect: add shell-completion for "--type" flag With this patch: docker inspect --type config image node secret task container network plugin service volume Signed-off-by: Sebastiaan van Stijn --- cli/command/system/inspect.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 7b0d84a55f..ea1cb0aca5 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -39,6 +39,19 @@ const ( typeVolume objectType = "volume" ) +var allTypes = []objectType{ + typeConfig, + typeContainer, + typeImage, + typeNetwork, + typeNode, + typePlugin, + typeSecret, + typeService, + typeTask, + typeVolume, +} + type inspectOptions struct { format string objectType objectType @@ -67,6 +80,7 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&opts.objectType, "type", "", "Return JSON for specified 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 From 8c5aaff57f4ca3f173d23d3d0f85a8403cdac948 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 6 May 2025 14:49:26 +0200 Subject: [PATCH 4/5] inspect: update flag description of "--type" flag Before this patch: docker inspect --help | grep '\-\-type' --type string Return JSON for specified type With this patch: docker inspect --help | grep '\-\-type' --type string Only inspect objects of the given type Signed-off-by: Sebastiaan van Stijn --- cli/command/system/inspect.go | 2 +- docs/reference/commandline/inspect.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index ea1cb0aca5..389edf148a 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -77,7 +77,7 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) - flags.StringVar(&opts.objectType, "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...)) diff --git a/docs/reference/commandline/inspect.md b/docs/reference/commandline/inspect.md index b566c5ae4d..f8d2849766 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 | From 52752f3aa246d4a84a4da6768119fd0a95b52d68 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 6 May 2025 15:05:06 +0200 Subject: [PATCH 5/5] inspect: improve (flag) validation Produce an error if the `--type` flag was set, but an empty value was passed. Before this patch: docker inspect --type "" foo # json output docker inspect --type unknown foo "unknown" is not a valid value for --type With this patch: docker inspect --type "" foo type is empty: must be one of "config", "container", "image", "network", "node", "plugin", "secret", "service", "task", "volume" docker inspect --type unknown foo unknown type: "unknown": must be one of "config", "container", "image", "network", "node", "plugin", "secret", "service", "task", "volume" Signed-off-by: Sebastiaan van Stijn --- cli/command/system/inspect.go | 5 +++- cli/command/system/inspect_test.go | 48 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 cli/command/system/inspect_test.go diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 389edf148a..9cbca4c421 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -69,6 +69,9 @@ 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?) @@ -97,7 +100,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) 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.objectType) + 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) } 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)) + } + }) + } +}