This commit is contained in:
Harald Albers 2024-10-19 21:58:57 +00:00 committed by GitHub
commit 2a3088b827
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 296 additions and 0 deletions

View File

@ -19,6 +19,7 @@ type fakeClient struct {
eventsFn func(context.Context, events.ListOptions) (<-chan events.Message, <-chan error) eventsFn func(context.Context, events.ListOptions) (<-chan events.Message, <-chan error)
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
containerListFunc func(context.Context, container.ListOptions) ([]container.Summary, error)
} }
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
@ -46,3 +47,10 @@ func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Ar
} }
return network.PruneReport{}, nil return network.PruneReport{}, nil
} }
func (cli *fakeClient) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) {
if cli.containerListFunc != nil {
return cli.containerListFunc(ctx, options)
}
return []container.Summary{}, nil
}

View File

@ -0,0 +1,241 @@
package system
import (
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/api/types/events"
"github.com/docker/cli/cli/command/completion"
"github.com/spf13/cobra"
)
var (
eventFilters = []string{"container", "daemon", "event", "image", "label", "network", "node", "scope", "type", "volume"}
// eventTypes is a list of all event types.
// This should be moved to the moby codebase once its usage is consolidated here.
eventTypes = []events.Type{
events.BuilderEventType,
events.ConfigEventType,
events.ContainerEventType,
events.DaemonEventType,
events.ImageEventType,
events.NetworkEventType,
events.NodeEventType,
events.PluginEventType,
events.SecretEventType,
events.ServiceEventType,
events.VolumeEventType,
}
// eventActions is a list of all event actions.
// This should be moved to the moby codebase once its usage is consolidated here.
eventActions = []events.Action{
events.ActionCreate,
events.ActionStart,
events.ActionRestart,
events.ActionStop,
events.ActionCheckpoint,
events.ActionPause,
events.ActionUnPause,
events.ActionAttach,
events.ActionDetach,
events.ActionResize,
events.ActionUpdate,
events.ActionRename,
events.ActionKill,
events.ActionDie,
events.ActionOOM,
events.ActionDestroy,
events.ActionRemove,
events.ActionCommit,
events.ActionTop,
events.ActionCopy,
events.ActionArchivePath,
events.ActionExtractToDir,
events.ActionExport,
events.ActionImport,
events.ActionSave,
events.ActionLoad,
events.ActionTag,
events.ActionUnTag,
events.ActionPush,
events.ActionPull,
events.ActionPrune,
events.ActionDelete,
events.ActionEnable,
events.ActionDisable,
events.ActionConnect,
events.ActionDisconnect,
events.ActionReload,
events.ActionMount,
events.ActionUnmount,
events.ActionExecCreate,
events.ActionExecStart,
events.ActionExecDie,
events.ActionExecDetach,
events.ActionHealthStatus,
events.ActionHealthStatusRunning,
events.ActionHealthStatusHealthy,
events.ActionHealthStatusUnhealthy,
}
)
// completeFilters provides completion for the filters that can be used with `--filter`.
func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if strings.HasPrefix(toComplete, "container=") {
return prefixWith("container=", containerNames(dockerCLI, cmd, args, toComplete)), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "daemon=") {
return prefixWith("daemon=", daemonNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "event=") {
return prefixWith("event=", validEventNames()), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "image=") {
return prefixWith("image=", imageNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "label=") {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "network=") {
return prefixWith("network=", networkNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "node=") {
return prefixWith("node=", nodeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "scope=") {
return prefixWith("scope=", []string{"local", "swarm"}), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "type=") {
return prefixWith("type=", eventTypeNames()), cobra.ShellCompDirectiveNoFileComp
}
if strings.HasPrefix(toComplete, "volume=") {
return prefixWith("volume=", volumeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp
}
return postfixWith("=", eventFilters), cobra.ShellCompDirectiveNoSpace
}
}
func prefixWith(prefix string, values []string) []string {
result := make([]string, len(values))
for i, v := range values {
result[i] = prefix + v
}
return result
}
func postfixWith(postfix string, values []string) []string {
result := make([]string, len(values))
for i, v := range values {
result[i] = v + postfix
}
return result
}
// eventTypeNames provides a list of all event types.
// The list is derived from eventTypes.
func eventTypeNames() []string {
names := make([]string, len(eventTypes))
for i, eventType := range eventTypes {
names[i] = string(eventType)
}
return names
}
// validEventNames provides a list of all event actions.
// The list is derived from eventActions.
// Actions that are not suitable for usage in completions are removed.
func validEventNames() []string {
names := []string{}
for _, eventAction := range eventActions {
if strings.Contains(string(eventAction), " ") {
continue
}
names = append(names, string(eventAction))
}
return names
}
// containerNames contacts the API to get names and optionally IDs of containers.
// In case of an error, an empty list is returned.
func containerNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command, args []string, toComplete string) []string {
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
if names == nil {
return []string{}
}
return names
}
// daemonNames contacts the API to get name and ID of the current docker daemon.
// In case of an error, an empty list is returned.
func daemonNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
info, err := dockerCLI.Client().Info(cmd.Context())
if err != nil {
return []string{}
}
return []string{info.Name, info.ID}
}
// imageNames contacts the API to get a list of image names.
// In case of an error, an empty list is returned.
func imageNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{})
if err != nil {
return []string{}
}
names := []string{}
for _, img := range list {
names = append(names, img.RepoTags...)
}
return names
}
// networkNames contacts the API to get a list of network names.
// In case of an error, an empty list is returned.
func networkNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{})
if err != nil {
return []string{}
}
names := []string{}
for _, nw := range list {
names = append(names, nw.Name)
}
return names
}
// nodeNames contacts the API to get a list of node names.
// In case of an error, an empty list is returned.
func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
list, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{})
if err != nil {
return []string{}
}
names := []string{}
for _, node := range list {
names = append(names, node.Description.Hostname)
}
return names
}
// volumeNames contacts the API to get a list of volume names.
// In case of an error, an empty list is returned.
func volumeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
list, err := dockerCLI.Client().VolumeList(cmd.Context(), volume.ListOptions{})
if err != nil {
return []string{}
}
names := []string{}
for _, v := range list.Volumes {
names = append(names, v.Name)
}
return names
}

View File

@ -0,0 +1,45 @@
package system
import (
"context"
"errors"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types/container"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
)
// Successful completion lists all container names, prefixed with "container=".
// Filtering the completions by the current word is delegated to the completion script.
func TestCompleteEventFilterContainer(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ context.Context, _ container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("foo"),
*builders.Container("bar"),
}, nil
},
})
completions, directive := completeFilters(cli)(NewEventsCommand(cli), nil, "container=")
assert.DeepEqual(t, completions, []string{"container=foo", "container=bar"})
assert.Equal(t, directive, cobra.ShellCompDirectiveNoFileComp)
}
// In case of API errors, no completions are returned.
func TestCompleteEventFilterContainerAPIError(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ context.Context, _ container.ListOptions) ([]container.Summary, error) {
return nil, errors.New("API error")
},
})
completions, directive := completeFilters(cli)(NewEventsCommand(cli), nil, "container=")
assert.DeepEqual(t, completions, []string{})
assert.Equal(t, directive, cobra.ShellCompDirectiveNoFileComp)
}

View File

@ -50,6 +50,8 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now. flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now.
_ = cmd.RegisterFlagCompletionFunc("filter", completeFilters(dockerCli))
return cmd return cmd
} }