cli/command/completion: add Platforms
Add a utility for completing platform strings.
Platforms offers completion for platform-strings. It provides a non-exhaustive
list of platforms to be used for completion. Platform-strings are based on
[runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A
list of recognised os/arch combinations from the Go runtime can be obtained
through "go tool dist list".
Some noteworthy exclusions from this list:
- arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows.
- we don't (yet) include `os-variant` for completion (as can be used for Windows images)
- we don't (yet) include platforms for which we don't build binaries, such as
BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin).
- we currently exclude architectures that may have unofficial builds,
but don't have wide adoption (and no support), such as loong64, mipsXXX,
ppc64 (non-le) to prevent confusion.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-08 07:09:50 -04:00
|
|
|
package completion
|
|
|
|
|
|
|
|
import (
|
2024-10-13 11:57:46 -04:00
|
|
|
"context"
|
|
|
|
"errors"
|
2024-10-13 11:48:49 -04:00
|
|
|
"sort"
|
cli/command/completion: add Platforms
Add a utility for completing platform strings.
Platforms offers completion for platform-strings. It provides a non-exhaustive
list of platforms to be used for completion. Platform-strings are based on
[runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A
list of recognised os/arch combinations from the Go runtime can be obtained
through "go tool dist list".
Some noteworthy exclusions from this list:
- arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows.
- we don't (yet) include `os-variant` for completion (as can be used for Windows images)
- we don't (yet) include platforms for which we don't build binaries, such as
BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin).
- we currently exclude architectures that may have unofficial builds,
but don't have wide adoption (and no support), such as loong64, mipsXXX,
ppc64 (non-le) to prevent confusion.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-08 07:09:50 -04:00
|
|
|
"testing"
|
|
|
|
|
2024-10-13 11:57:46 -04:00
|
|
|
"github.com/docker/docker/api/types/container"
|
|
|
|
"github.com/docker/docker/api/types/filters"
|
2024-10-13 12:00:20 -04:00
|
|
|
"github.com/docker/docker/api/types/image"
|
2024-10-13 12:02:35 -04:00
|
|
|
"github.com/docker/docker/api/types/network"
|
2024-10-13 12:03:32 -04:00
|
|
|
"github.com/docker/docker/api/types/volume"
|
2024-10-13 11:57:46 -04:00
|
|
|
"github.com/docker/docker/client"
|
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
cli/command/completion: add Platforms
Add a utility for completing platform strings.
Platforms offers completion for platform-strings. It provides a non-exhaustive
list of platforms to be used for completion. Platform-strings are based on
[runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A
list of recognised os/arch combinations from the Go runtime can be obtained
through "go tool dist list".
Some noteworthy exclusions from this list:
- arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows.
- we don't (yet) include `os-variant` for completion (as can be used for Windows images)
- we don't (yet) include platforms for which we don't build binaries, such as
BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin).
- we currently exclude architectures that may have unofficial builds,
but don't have wide adoption (and no support), such as loong64, mipsXXX,
ppc64 (non-le) to prevent confusion.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-08 07:09:50 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
is "gotest.tools/v3/assert/cmp"
|
2024-10-13 11:48:49 -04:00
|
|
|
"gotest.tools/v3/env"
|
cli/command/completion: add Platforms
Add a utility for completing platform strings.
Platforms offers completion for platform-strings. It provides a non-exhaustive
list of platforms to be used for completion. Platform-strings are based on
[runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A
list of recognised os/arch combinations from the Go runtime can be obtained
through "go tool dist list".
Some noteworthy exclusions from this list:
- arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows.
- we don't (yet) include `os-variant` for completion (as can be used for Windows images)
- we don't (yet) include platforms for which we don't build binaries, such as
BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin).
- we currently exclude architectures that may have unofficial builds,
but don't have wide adoption (and no support), such as loong64, mipsXXX,
ppc64 (non-le) to prevent confusion.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-08 07:09:50 -04:00
|
|
|
)
|
|
|
|
|
2024-10-13 11:57:46 -04:00
|
|
|
type fakeCLI struct {
|
|
|
|
*fakeClient
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client implements [APIClientProvider].
|
|
|
|
func (c fakeCLI) Client() client.APIClient {
|
|
|
|
return c.fakeClient
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeClient struct {
|
|
|
|
client.Client
|
|
|
|
containerListFunc func(options container.ListOptions) ([]container.Summary, error)
|
2024-10-13 12:00:20 -04:00
|
|
|
imageListFunc func(options image.ListOptions) ([]image.Summary, error)
|
2024-10-13 12:02:35 -04:00
|
|
|
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
2024-10-13 12:03:32 -04:00
|
|
|
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
|
2024-10-13 11:57:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]container.Summary, error) {
|
|
|
|
if c.containerListFunc != nil {
|
|
|
|
return c.containerListFunc(options)
|
|
|
|
}
|
|
|
|
return []container.Summary{}, nil
|
|
|
|
}
|
|
|
|
|
2024-10-13 12:00:20 -04:00
|
|
|
func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) {
|
|
|
|
if c.imageListFunc != nil {
|
|
|
|
return c.imageListFunc(options)
|
|
|
|
}
|
|
|
|
return []image.Summary{}, nil
|
|
|
|
}
|
|
|
|
|
2024-10-13 12:02:35 -04:00
|
|
|
func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
|
|
|
if c.networkListFunc != nil {
|
|
|
|
return c.networkListFunc(ctx, options)
|
|
|
|
}
|
|
|
|
return []network.Inspect{}, nil
|
|
|
|
}
|
|
|
|
|
2024-10-13 12:03:32 -04:00
|
|
|
func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
|
|
|
if c.volumeListFunc != nil {
|
|
|
|
return c.volumeListFunc(options.Filters)
|
|
|
|
}
|
|
|
|
return volume.ListResponse{}, nil
|
|
|
|
}
|
|
|
|
|
2024-10-13 11:57:46 -04:00
|
|
|
func TestCompleteContainerNames(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
doc string
|
|
|
|
showAll, showIDs bool
|
|
|
|
filters []func(container.Summary) bool
|
|
|
|
containers []container.Summary
|
|
|
|
expOut []string
|
|
|
|
expOpts container.ListOptions
|
|
|
|
expDirective cobra.ShellCompDirective
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
doc: "no results",
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "all containers",
|
|
|
|
showAll: true,
|
|
|
|
containers: []container.Summary{
|
|
|
|
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
|
|
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
|
|
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
|
|
|
|
},
|
|
|
|
expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"},
|
|
|
|
expOpts: container.ListOptions{All: true},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "all containers with ids",
|
|
|
|
showAll: true,
|
|
|
|
showIDs: true,
|
|
|
|
containers: []container.Summary{
|
|
|
|
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
|
|
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
|
|
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
|
|
|
|
},
|
|
|
|
expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"},
|
|
|
|
expOpts: container.ListOptions{All: true},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "only running containers",
|
|
|
|
showAll: false,
|
|
|
|
containers: []container.Summary{
|
|
|
|
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
|
|
},
|
|
|
|
expOut: []string{"container-c", "container-c/link-b"},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with filter",
|
|
|
|
showAll: true,
|
|
|
|
filters: []func(container.Summary) bool{
|
|
|
|
func(container container.Summary) bool { return container.State == "created" },
|
|
|
|
},
|
|
|
|
containers: []container.Summary{
|
|
|
|
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
|
|
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
|
|
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
|
|
|
|
},
|
|
|
|
expOut: []string{"container-b"},
|
|
|
|
expOpts: container.ListOptions{All: true},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "multiple filters",
|
|
|
|
showAll: true,
|
|
|
|
filters: []func(container.Summary) bool{
|
|
|
|
func(container container.Summary) bool { return container.ID == "id-a" },
|
|
|
|
func(container container.Summary) bool { return container.State == "created" },
|
|
|
|
},
|
|
|
|
containers: []container.Summary{
|
|
|
|
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
|
|
|
|
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
|
|
|
|
{ID: "id-a", State: "created", Names: []string{"/container-a"}},
|
|
|
|
},
|
|
|
|
expOut: []string{"container-a"},
|
|
|
|
expOpts: container.ListOptions{All: true},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with error",
|
|
|
|
expDirective: cobra.ShellCompDirectiveError,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
|
|
if tc.showIDs {
|
|
|
|
t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes")
|
|
|
|
}
|
|
|
|
comp := ContainerNames(fakeCLI{&fakeClient{
|
|
|
|
containerListFunc: func(opts container.ListOptions) ([]container.Summary, error) {
|
|
|
|
assert.Check(t, is.DeepEqual(opts, tc.expOpts, cmpopts.IgnoreUnexported(container.ListOptions{}, filters.Args{})))
|
|
|
|
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
|
|
return nil, errors.New("some error occurred")
|
|
|
|
}
|
|
|
|
return tc.containers, nil
|
|
|
|
},
|
|
|
|
}}, tc.showAll, tc.filters...)
|
|
|
|
|
|
|
|
containers, directives := comp(&cobra.Command{}, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
|
|
assert.Check(t, is.DeepEqual(containers, tc.expOut))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-13 11:48:49 -04:00
|
|
|
func TestCompleteEnvVarNames(t *testing.T) {
|
|
|
|
env.PatchAll(t, map[string]string{
|
|
|
|
"ENV_A": "hello-a",
|
|
|
|
"ENV_B": "hello-b",
|
|
|
|
})
|
|
|
|
values, directives := EnvVarNames(nil, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
|
|
|
|
|
|
|
sort.Strings(values)
|
|
|
|
expected := []string{"ENV_A", "ENV_B"}
|
|
|
|
assert.Check(t, is.DeepEqual(values, expected))
|
|
|
|
}
|
|
|
|
|
2024-10-13 11:52:50 -04:00
|
|
|
func TestCompleteFileNames(t *testing.T) {
|
|
|
|
values, directives := FileNames(nil, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveDefault))
|
|
|
|
assert.Check(t, is.Len(values, 0))
|
|
|
|
}
|
|
|
|
|
2024-10-13 11:50:29 -04:00
|
|
|
func TestCompleteFromList(t *testing.T) {
|
|
|
|
expected := []string{"one", "two", "three"}
|
|
|
|
|
|
|
|
values, directives := FromList(expected...)(nil, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
|
|
|
assert.Check(t, is.DeepEqual(values, expected))
|
|
|
|
}
|
|
|
|
|
2024-10-13 12:00:20 -04:00
|
|
|
func TestCompleteImageNames(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
doc string
|
|
|
|
images []image.Summary
|
|
|
|
expOut []string
|
|
|
|
expDirective cobra.ShellCompDirective
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
doc: "no results",
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with results",
|
|
|
|
images: []image.Summary{
|
|
|
|
{RepoTags: []string{"image-c:latest", "image-c:other"}},
|
|
|
|
{RepoTags: []string{"image-b:latest", "image-b:other"}},
|
|
|
|
{RepoTags: []string{"image-a:latest", "image-a:other"}},
|
|
|
|
},
|
|
|
|
expOut: []string{"image-c:latest", "image-c:other", "image-b:latest", "image-b:other", "image-a:latest", "image-a:other"},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with error",
|
|
|
|
expDirective: cobra.ShellCompDirectiveError,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
|
|
comp := ImageNames(fakeCLI{&fakeClient{
|
|
|
|
imageListFunc: func(options image.ListOptions) ([]image.Summary, error) {
|
|
|
|
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
|
|
return nil, errors.New("some error occurred")
|
|
|
|
}
|
|
|
|
return tc.images, nil
|
|
|
|
},
|
|
|
|
}})
|
|
|
|
|
|
|
|
volumes, directives := comp(&cobra.Command{}, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
|
|
assert.Check(t, is.DeepEqual(volumes, tc.expOut))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-13 12:02:35 -04:00
|
|
|
func TestCompleteNetworkNames(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
doc string
|
|
|
|
networks []network.Summary
|
|
|
|
expOut []string
|
|
|
|
expDirective cobra.ShellCompDirective
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
doc: "no results",
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with results",
|
|
|
|
networks: []network.Summary{
|
|
|
|
{ID: "nw-c", Name: "network-c"},
|
|
|
|
{ID: "nw-b", Name: "network-b"},
|
|
|
|
{ID: "nw-a", Name: "network-a"},
|
|
|
|
},
|
|
|
|
expOut: []string{"network-c", "network-b", "network-a"},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with error",
|
|
|
|
expDirective: cobra.ShellCompDirectiveError,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
|
|
comp := NetworkNames(fakeCLI{&fakeClient{
|
|
|
|
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
|
|
|
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
|
|
return nil, errors.New("some error occurred")
|
|
|
|
}
|
|
|
|
return tc.networks, nil
|
|
|
|
},
|
|
|
|
}})
|
|
|
|
|
|
|
|
volumes, directives := comp(&cobra.Command{}, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
|
|
assert.Check(t, is.DeepEqual(volumes, tc.expOut))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-13 11:54:40 -04:00
|
|
|
func TestCompleteNoComplete(t *testing.T) {
|
|
|
|
values, directives := NoComplete(nil, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
|
|
|
assert.Check(t, is.Len(values, 0))
|
|
|
|
}
|
|
|
|
|
cli/command/completion: add Platforms
Add a utility for completing platform strings.
Platforms offers completion for platform-strings. It provides a non-exhaustive
list of platforms to be used for completion. Platform-strings are based on
[runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A
list of recognised os/arch combinations from the Go runtime can be obtained
through "go tool dist list".
Some noteworthy exclusions from this list:
- arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows.
- we don't (yet) include `os-variant` for completion (as can be used for Windows images)
- we don't (yet) include platforms for which we don't build binaries, such as
BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin).
- we currently exclude architectures that may have unofficial builds,
but don't have wide adoption (and no support), such as loong64, mipsXXX,
ppc64 (non-le) to prevent confusion.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-08 07:09:50 -04:00
|
|
|
func TestCompletePlatforms(t *testing.T) {
|
|
|
|
values, directives := Platforms(nil, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
|
|
|
assert.Check(t, is.DeepEqual(values, commonPlatforms))
|
|
|
|
}
|
2024-10-13 12:03:32 -04:00
|
|
|
|
|
|
|
func TestCompleteVolumeNames(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
doc string
|
|
|
|
volumes []*volume.Volume
|
|
|
|
expOut []string
|
|
|
|
expDirective cobra.ShellCompDirective
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
doc: "no results",
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with results",
|
|
|
|
volumes: []*volume.Volume{
|
|
|
|
{Name: "volume-c"},
|
|
|
|
{Name: "volume-b"},
|
|
|
|
{Name: "volume-a"},
|
|
|
|
},
|
|
|
|
expOut: []string{"volume-c", "volume-b", "volume-a"},
|
|
|
|
expDirective: cobra.ShellCompDirectiveNoFileComp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "with error",
|
|
|
|
expDirective: cobra.ShellCompDirectiveError,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
|
|
comp := VolumeNames(fakeCLI{&fakeClient{
|
|
|
|
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
|
|
|
if tc.expDirective == cobra.ShellCompDirectiveError {
|
|
|
|
return volume.ListResponse{}, errors.New("some error occurred")
|
|
|
|
}
|
|
|
|
return volume.ListResponse{Volumes: tc.volumes}, nil
|
|
|
|
},
|
|
|
|
}})
|
|
|
|
|
|
|
|
volumes, directives := comp(&cobra.Command{}, nil, "")
|
|
|
|
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
|
|
|
|
assert.Check(t, is.DeepEqual(volumes, tc.expOut))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|