Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions cmd/docker/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strconv"
"strings"

"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/metadata"
Expand Down Expand Up @@ -99,22 +100,31 @@ func processCloud(ctx context.Context, dockerCli *command.DockerCli, cmd *cobra.
// the __resolve-context contract.
const cloudResolverFeature = "cloud-context-resolver"

const cloudPluginHelp = "to use --cloud, install the plugin. See https://docs.docker.com/go/cloud-flag/"

func cloudProvider(dockerCli config.Provider, rootCmd *cobra.Command) (*pluginmanager.Plugin, error) {
provider := dockerCli.ConfigFile().Features["cloud"]
if provider == "" {
provider = "offload"
}
var help string
if provider == "offload" {
help = "\n\n" + cloudPluginHelp
}

plugin, err := pluginmanager.GetPlugin(provider, dockerCli, rootCmd)
if err != nil {
return nil, fmt.Errorf("cloud resolver plugin %q unavailable: %w", provider, err)
if provider == "offload" && errdefs.IsNotFound(err) {
return nil, errors.New(cloudPluginHelp)
}
return nil, fmt.Errorf("plugin %q unavailable: %w%s", provider, err, help)
}
if plugin.Err != nil {
return nil, fmt.Errorf("invalid cloud resolver plugin %q: %w", provider, plugin.Err)
return nil, fmt.Errorf("invalid plugin %q: %w%s", provider, plugin.Err, help)
}

if supported, _ := plugin.Features[cloudResolverFeature].(bool); !supported {
return nil, fmt.Errorf("plugin %q does not support cloud context resolution", provider)
return nil, fmt.Errorf("plugin %q does not support --cloud%s", provider, help)
}

return plugin, nil
Expand Down
73 changes: 44 additions & 29 deletions cmd/docker/cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"cmp"
"context"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -175,19 +176,21 @@ func TestCloudResolution(t *testing.T) {
assert.NilError(t, err)

for _, tc := range []struct {
name string
args []string
response string
exit int
wantName string
wantErr string
plugin bool
skip bool
provider string
noProvider bool
legacyProvider bool
command []string
visible bool
name string
args []string
response string
exit int
wantName string
wantErr string
plugin bool
skip bool
provider string
noProvider bool
legacyProvider bool
invalidProvider bool
wantHelp bool
command []string
visible bool
}{
{
name: "default target",
Expand Down Expand Up @@ -282,28 +285,45 @@ func TestCloudResolution(t *testing.T) {
args: []string{"--cloud"},
noProvider: true,
legacyProvider: true,
wantErr: `plugin "offload" does not support cloud context resolution`,
wantErr: `plugin "offload" does not support --cloud`,
wantHelp: true,
skip: true,
},
{
name: "invalid default provider",
args: []string{"--cloud"},
noProvider: true,
invalidProvider: true,
wantErr: `invalid plugin "offload": plugin metadata does not define a vendor`,
wantHelp: true,
skip: true,
},
{
name: "invalid configured provider",
args: []string{"--cloud"},
invalidProvider: true,
wantErr: `invalid plugin "foobar": plugin metadata does not define a vendor`,
skip: true,
},
{
name: "legacy configured provider",
args: []string{"--cloud"},
legacyProvider: true,
wantErr: `plugin "foobar" does not support cloud context resolution`,
wantErr: `plugin "foobar" does not support --cloud`,
skip: true,
},
{
name: "configured provider unavailable",
args: []string{"--cloud"},
provider: "missing",
wantErr: `cloud resolver plugin "missing" unavailable`,
wantErr: `plugin "missing" unavailable`,
skip: true,
},
{
name: "provider cannot be a path",
args: []string{"--cloud"},
provider: "../foobar",
wantErr: `cloud resolver plugin "../foobar" unavailable`,
wantErr: `plugin "../foobar" unavailable`,
skip: true,
},
{
Expand Down Expand Up @@ -376,23 +396,20 @@ func TestCloudResolution(t *testing.T) {
if !tc.noProvider {
installedProvider = "foobar"
cfg := configfile.New(filepath.Join(configDir, config.ConfigFileName))
provider := tc.provider
if provider == "" {
provider = "foobar"
}
provider := cmp.Or(tc.provider, "foobar")
cfg.Features = map[string]string{"cloud": provider}
assert.NilError(t, cfg.Save())
}

pluginDir := filepath.Join(configDir, "cli-plugins")
assert.NilError(t, os.MkdirAll(pluginDir, 0o755))

response := tc.response
if response == "" {
response = `{"DOCKER_CONTEXT":"resolved"}`
}
response := cmp.Or(tc.response, `{"DOCKER_CONTEXT":"resolved"}`)

providerMetadata := metadata.Metadata{SchemaVersion: "0.1.0", Vendor: "test"}
if tc.invalidProvider {
providerMetadata.Vendor = ""
}
if !tc.legacyProvider {
providerMetadata.Features = map[string]any{cloudResolverFeature: true}
}
Expand Down Expand Up @@ -454,6 +471,7 @@ printf '%s\n' "$@" > "$CLOUD_TEST_ARGS"
if tc.wantErr != "" {
assert.Assert(t, err != nil)
assert.Check(t, is.Contains(stderr.String(), tc.wantErr))
assert.Equal(t, strings.Contains(stderr.String(), cloudPluginHelp), tc.wantHelp, stderr.String())
_, statErr := os.Stat(dispatchFile)
assert.Assert(t, os.IsNotExist(statErr))
assert.Equal(t, stdout.String(), "")
Expand All @@ -479,10 +497,7 @@ printf '%s\n' "$@" > "$CLOUD_TEST_ARGS"
} else {
assert.NilError(t, err)
assert.Check(t, is.Contains(stderr.String(), "provisioning"))
name := tc.wantName
if name == "" {
name = "default"
}
name := cmp.Or(tc.wantName, "default")
assert.Equal(t, string(invocation), "--config="+configDir+"\n"+installedProvider+"\n__resolve-context\n--\n"+name+"\n")
}
})
Expand Down
Loading