Refined engine implementations

Adapt the CLI to the host install model for 18.09.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
This commit is contained in:
Daniel Hiltgen 2018-09-14 10:53:56 -07:00
parent cfec8027ed
commit 342afe44fb
19 changed files with 482 additions and 438 deletions

View File

@ -12,7 +12,6 @@ import (
"github.com/docker/licensing/model" "github.com/docker/licensing/model"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/sys/unix"
) )
type activateOptions struct { type activateOptions struct {
@ -68,7 +67,7 @@ https://hub.docker.com/ then specify the file with the '--license' flag.
} }
func runActivate(cli command.Cli, options activateOptions) error { func runActivate(cli command.Cli, options activateOptions) error {
if unix.Geteuid() != 0 { if !isRoot() {
return errors.New("must be privileged to activate engine") return errors.New("must be privileged to activate engine")
} }
ctx := context.Background() ctx := context.Background()
@ -108,12 +107,17 @@ func runActivate(cli command.Cli, options activateOptions) error {
EngineVersion: options.version, EngineVersion: options.version,
} }
return client.ActivateEngine(ctx, opts, cli.Out(), authConfig, err = client.ActivateEngine(ctx, opts, cli.Out(), authConfig,
func(ctx context.Context) error { func(ctx context.Context) error {
client := cli.Client() client := cli.Client()
_, err := client.Ping(ctx) _, err := client.Ping(ctx)
return err return err
}) })
if err != nil {
return err
}
fmt.Fprintln(cli.Out(), "To complete the activation, please restart docker with 'systemctl restart docker'")
return nil
} }
func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) { func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {

View File

@ -14,6 +14,7 @@ func TestActivateNoContainerd(t *testing.T) {
return nil, fmt.Errorf("some error") return nil, fmt.Errorf("some error")
}, },
) )
isRoot = func() bool { return true }
cmd := newActivateCommand(testCli) cmd := newActivateCommand(testCli)
cmd.Flags().Set("license", "invalidpath") cmd.Flags().Set("license", "invalidpath")
cmd.SilenceUsage = true cmd.SilenceUsage = true
@ -28,6 +29,7 @@ func TestActivateBadLicense(t *testing.T) {
return &fakeContainerizedEngineClient{}, nil return &fakeContainerizedEngineClient{}, nil
}, },
) )
isRoot = func() bool { return true }
cmd := newActivateCommand(testCli) cmd := newActivateCommand(testCli)
cmd.SilenceUsage = true cmd.SilenceUsage = true
cmd.SilenceErrors = true cmd.SilenceErrors = true

View File

@ -0,0 +1,13 @@
// +build !windows
package engine
import (
"golang.org/x/sys/unix"
)
var (
isRoot = func() bool {
return unix.Geteuid() == 0
}
)

View File

@ -0,0 +1,9 @@
// +build windows
package engine
var (
isRoot = func() bool {
return true
}
)

View File

@ -1,14 +1,16 @@
package engine package engine
import ( import (
"context"
"fmt" "fmt"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/internal/versions"
clitypes "github.com/docker/cli/types" clitypes "github.com/docker/cli/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/sys/unix"
) )
const ( const (
@ -37,7 +39,7 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.StringVar(&options.registryPrefix, "registry-prefix", "", "Override the existing location where engine images are pulled") flags.StringVar(&options.registryPrefix, "registry-prefix", "docker.io/store/docker", "Override the existing location where engine images are pulled")
flags.BoolVar(&options.downgrades, "downgrades", false, "Report downgrades (default omits older versions)") flags.BoolVar(&options.downgrades, "downgrades", false, "Report downgrades (default omits older versions)")
flags.BoolVar(&options.preReleases, "pre-releases", false, "Include pre-release versions") flags.BoolVar(&options.preReleases, "pre-releases", false, "Include pre-release versions")
flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades") flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades")
@ -49,70 +51,67 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
} }
func runCheck(dockerCli command.Cli, options checkOptions) error { func runCheck(dockerCli command.Cli, options checkOptions) error {
if unix.Geteuid() != 0 { if !isRoot() {
return errors.New("must be privileged to activate engine") return errors.New("must be privileged to activate engine")
} }
ctx := context.Background()
client := dockerCli.Client()
serverVersion, err := client.ServerVersion(ctx)
if err != nil {
return err
}
/* availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, serverVersion)
ctx := context.Background() if err != nil {
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath) return err
if err != nil { }
return errors.Wrap(err, "unable to access local containerd")
}
defer client.Close()
versions, err := client.GetEngineVersions(ctx, dockerCli.RegistryClient(false), currentVersion, imageName)
if err != nil {
return err
}
availUpdates := []clitypes.Update{ availUpdates := []clitypes.Update{
{Type: "current", Version: currentVersion}, {Type: "current", Version: serverVersion.Version},
} }
if len(versions.Patches) > 0 { if len(availVersions.Patches) > 0 {
availUpdates = append(availUpdates, availUpdates = append(availUpdates,
processVersions( processVersions(
currentVersion, serverVersion.Version,
"patch", "patch",
options.preReleases, options.preReleases,
versions.Patches)...) availVersions.Patches)...)
} }
if options.upgrades { if options.upgrades {
availUpdates = append(availUpdates, availUpdates = append(availUpdates,
processVersions( processVersions(
currentVersion, serverVersion.Version,
"upgrade", "upgrade",
options.preReleases, options.preReleases,
versions.Upgrades)...) availVersions.Upgrades)...)
} }
if options.downgrades { if options.downgrades {
availUpdates = append(availUpdates, availUpdates = append(availUpdates,
processVersions( processVersions(
currentVersion, serverVersion.Version,
"downgrade", "downgrade",
options.preReleases, options.preReleases,
versions.Downgrades)...) availVersions.Downgrades)...)
} }
format := options.format format := options.format
if len(format) == 0 { if len(format) == 0 {
format = formatter.TableFormatKey format = formatter.TableFormatKey
} }
updatesCtx := formatter.Context{ updatesCtx := formatter.Context{
Output: dockerCli.Out(), Output: dockerCli.Out(),
Format: formatter.NewUpdatesFormat(format, options.quiet), Format: formatter.NewUpdatesFormat(format, options.quiet),
Trunc: false, Trunc: false,
} }
return formatter.UpdatesWrite(updatesCtx, availUpdates) return formatter.UpdatesWrite(updatesCtx, availUpdates)
*/
return nil
} }
func processVersions(currentVersion, verType string, func processVersions(currentVersion, verType string,
includePrerelease bool, includePrerelease bool,
versions []clitypes.DockerVersion) []clitypes.Update { availVersions []clitypes.DockerVersion) []clitypes.Update {
availUpdates := []clitypes.Update{} availUpdates := []clitypes.Update{}
for _, ver := range versions { for _, ver := range availVersions {
if !includePrerelease && ver.Prerelease() != "" { if !includePrerelease && ver.Prerelease() != "" {
continue continue
} }

View File

@ -5,11 +5,13 @@ import (
"fmt" "fmt"
"testing" "testing"
registryclient "github.com/docker/cli/cli/registry/client" manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
clitypes "github.com/docker/cli/types" "github.com/docker/distribution"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client" "github.com/docker/docker/client"
ver "github.com/hashicorp/go-version" "github.com/opencontainers/go-digest"
"gotest.tools/assert" "gotest.tools/assert"
"gotest.tools/golden" "gotest.tools/golden"
) )
@ -18,126 +20,87 @@ var (
testCli = test.NewFakeCli(&client.Client{}) testCli = test.NewFakeCli(&client.Client{})
) )
func TestCheckForUpdatesNoContainerd(t *testing.T) { type verClient struct {
testCli.SetContainerizedEngineClient( client.Client
func(string) (clitypes.ContainerizedClient, error) { ver types.Version
return nil, fmt.Errorf("some error") verErr error
}, }
)
cmd := newCheckForUpdatesCommand(testCli) func (c *verClient) ServerVersion(ctx context.Context) (types.Version, error) {
cmd.SilenceUsage = true return c.ver, c.verErr
cmd.SilenceErrors = true }
err := cmd.Execute()
assert.ErrorContains(t, err, "unable to access local containerd") type testRegistryClient struct {
tags []string
}
func (c testRegistryClient) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, nil
}
func (c testRegistryClient) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, nil
}
func (c testRegistryClient) MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error {
return nil
}
func (c testRegistryClient) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
return "", nil
}
func (c testRegistryClient) GetTags(ctx context.Context, ref reference.Named) ([]string, error) {
return c.tags, nil
} }
func TestCheckForUpdatesNoCurrentVersion(t *testing.T) { func TestCheckForUpdatesNoCurrentVersion(t *testing.T) {
retErr := fmt.Errorf("some failure") isRoot = func() bool { return true }
getCurrentEngineVersionFunc := func(ctx context.Context) (clitypes.EngineInitOptions, error) { c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil})
return clitypes.EngineInitOptions{}, retErr c.SetRegistryClient(testRegistryClient{})
} cmd := newCheckForUpdatesCommand(c)
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{
getCurrentEngineVersionFunc: getCurrentEngineVersionFunc,
}, nil
},
)
cmd := newCheckForUpdatesCommand(testCli)
cmd.SilenceUsage = true cmd.SilenceUsage = true
cmd.SilenceErrors = true cmd.SilenceErrors = true
err := cmd.Execute() err := cmd.Execute()
assert.Assert(t, err == retErr) assert.ErrorContains(t, err, "alformed version")
}
func TestCheckForUpdatesGetEngineVersionsFail(t *testing.T) {
retErr := fmt.Errorf("some failure")
getEngineVersionsFunc := func(ctx context.Context,
registryClient registryclient.RegistryClient,
currentVersion, imageName string) (clitypes.AvailableVersions, error) {
return clitypes.AvailableVersions{}, retErr
}
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{
getEngineVersionsFunc: getEngineVersionsFunc,
}, nil
},
)
cmd := newCheckForUpdatesCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.Assert(t, err == retErr)
} }
func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) { func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) {
getCurrentEngineVersionFunc := func(ctx context.Context) (clitypes.EngineInitOptions, error) { c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil})
return clitypes.EngineInitOptions{ c.SetRegistryClient(testRegistryClient{[]string{
EngineImage: "current engine", "1.0.1", "1.0.2", "1.0.3-beta1",
EngineVersion: "1.1.0", "1.1.1", "1.1.2", "1.1.3-beta1",
}, nil "1.2.0", "2.0.0", "2.1.0-beta1",
} }})
getEngineVersionsFunc := func(ctx context.Context, isRoot = func() bool { return true }
registryClient registryclient.RegistryClient, cmd := newCheckForUpdatesCommand(c)
currentVersion, imageName string) (clitypes.AvailableVersions, error) {
return clitypes.AvailableVersions{
Downgrades: parseVersions(t, "1.0.1", "1.0.2", "1.0.3-beta1"),
Patches: parseVersions(t, "1.1.1", "1.1.2", "1.1.3-beta1"),
Upgrades: parseVersions(t, "1.2.0", "2.0.0", "2.1.0-beta1"),
}, nil
}
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{
getEngineVersionsFunc: getEngineVersionsFunc,
getCurrentEngineVersionFunc: getCurrentEngineVersionFunc,
}, nil
},
)
cmd := newCheckForUpdatesCommand(testCli)
cmd.Flags().Set("pre-releases", "true") cmd.Flags().Set("pre-releases", "true")
cmd.Flags().Set("downgrades", "true") cmd.Flags().Set("downgrades", "true")
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute() err := cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
golden.Assert(t, testCli.OutBuffer().String(), "check-all.golden") golden.Assert(t, c.OutBuffer().String(), "check-all.golden")
testCli.OutBuffer().Reset() c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false") cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "true") cmd.Flags().Set("downgrades", "true")
err = cmd.Execute() err = cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
fmt.Println(testCli.OutBuffer().String()) fmt.Println(c.OutBuffer().String())
golden.Assert(t, testCli.OutBuffer().String(), "check-no-prerelease.golden") golden.Assert(t, c.OutBuffer().String(), "check-no-prerelease.golden")
testCli.OutBuffer().Reset() c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false") cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "false") cmd.Flags().Set("downgrades", "false")
err = cmd.Execute() err = cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
fmt.Println(testCli.OutBuffer().String()) fmt.Println(c.OutBuffer().String())
golden.Assert(t, testCli.OutBuffer().String(), "check-no-downgrades.golden") golden.Assert(t, c.OutBuffer().String(), "check-no-downgrades.golden")
testCli.OutBuffer().Reset() c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false") cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "false") cmd.Flags().Set("downgrades", "false")
cmd.Flags().Set("upgrades", "false") cmd.Flags().Set("upgrades", "false")
err = cmd.Execute() err = cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
fmt.Println(testCli.OutBuffer().String()) fmt.Println(c.OutBuffer().String())
golden.Assert(t, testCli.OutBuffer().String(), "check-patches-only.golden") golden.Assert(t, c.OutBuffer().String(), "check-patches-only.golden")
}
func makeVersion(t *testing.T, tag string) clitypes.DockerVersion {
v, err := ver.NewVersion(tag)
assert.NilError(t, err)
return clitypes.DockerVersion{Version: *v, Tag: tag}
}
func parseVersions(t *testing.T, tags ...string) []clitypes.DockerVersion {
ret := make([]clitypes.DockerVersion, len(tags))
for i, tag := range tags {
ret[i] = makeVersion(t, tag)
}
return ret
} }

View File

@ -10,5 +10,5 @@ func TestNewEngineCommand(t *testing.T) {
cmd := NewEngineCommand(testCli) cmd := NewEngineCommand(testCli)
subcommands := cmd.Commands() subcommands := cmd.Commands()
assert.Assert(t, len(subcommands) == 5) assert.Assert(t, len(subcommands) == 3)
} }

View File

@ -1,62 +1,10 @@
package engine package engine
import ( import (
"context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
clitypes "github.com/docker/cli/types" clitypes "github.com/docker/cli/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
) )
type extendedEngineInitOptions struct { type extendedEngineInitOptions struct {
clitypes.EngineInitOptions clitypes.EngineInitOptions
sockPath string sockPath string
} }
func newInitCommand(dockerCli command.Cli) *cobra.Command {
var options extendedEngineInitOptions
cmd := &cobra.Command{
Use: "init [OPTIONS]",
Short: "Initialize a local engine",
Long: `This command will initialize a local engine running on containerd.
Configuration of the engine is managed through the daemon.json configuration
file on the host and may be pre-created before running the 'init' command.
`,
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runInit(dockerCli, options)
},
Annotations: map[string]string{"experimentalCLI": ""},
}
flags := cmd.Flags()
flags.StringVar(&options.EngineVersion, "version", cli.Version, "Specify engine version")
flags.StringVar(&options.EngineImage, "engine-image", clitypes.CommunityEngineImage, "Specify engine image")
flags.StringVar(&options.RegistryPrefix, "registry-prefix", "docker.io/store/docker", "Override the default location where engine images are pulled")
flags.StringVar(&options.ConfigFile, "config-file", "/etc/docker/daemon.json", "Specify the location of the daemon configuration file on the host")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
return cmd
}
func runInit(dockerCli command.Cli, options extendedEngineInitOptions) error {
ctx := context.Background()
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
if err != nil {
return errors.Wrap(err, "unable to access local containerd")
}
defer client.Close()
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
if err != nil {
return err
}
return client.InitEngine(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig,
func(ctx context.Context) error {
client := dockerCli.Client()
_, err := client.Ping(ctx)
return err
})
}

View File

@ -1,33 +0,0 @@
package engine
import (
"fmt"
"testing"
clitypes "github.com/docker/cli/types"
"gotest.tools/assert"
)
func TestInitNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return nil, fmt.Errorf("some error")
},
)
cmd := newInitCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.ErrorContains(t, err, "unable to access local containerd")
}
func TestInitHappy(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
cmd := newInitCommand(testCli)
err := cmd.Execute()
assert.NilError(t, err)
}

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/sys/unix"
) )
func newUpdateCommand(dockerCli command.Cli) *cobra.Command { func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
@ -33,7 +32,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
} }
func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error { func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
if unix.Geteuid() != 0 { if !isRoot() {
return errors.New("must be privileged to activate engine") return errors.New("must be privileged to activate engine")
} }
ctx := context.Background() ctx := context.Background()
@ -43,11 +42,8 @@ func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
} }
defer client.Close() defer client.Close()
if options.EngineImage == "" || options.RegistryPrefix == "" { if options.EngineImage == "" || options.RegistryPrefix == "" {
if options.EngineImage == "" {
options.EngineImage = "docker/engine-community"
}
if options.RegistryPrefix == "" { if options.RegistryPrefix == "" {
options.RegistryPrefix = "docker.io" options.RegistryPrefix = "docker.io/store/docker"
} }
} }
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix) authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
@ -63,6 +59,6 @@ func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
}); err != nil { }); err != nil {
return err return err
} }
fmt.Fprintln(dockerCli.Out(), "Success! The docker engine is now running.") fmt.Fprintln(dockerCli.Out(), "To complete the update, please restart docker with 'systemctl restart docker'")
return nil return nil
} }

View File

@ -105,7 +105,7 @@ shellcheck: build_shell_validate_image ## run shellcheck validation
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck
.PHONY: test-e2e ## run e2e tests .PHONY: test-e2e ## run e2e tests
test-e2e: test-e2e-non-experimental test-e2e-experimental test-e2e-containerized test-e2e: test-e2e-non-experimental test-e2e-experimental
.PHONY: test-e2e-experimental .PHONY: test-e2e-experimental
test-e2e-experimental: build_e2e_image test-e2e-experimental: build_e2e_image
@ -115,14 +115,6 @@ test-e2e-experimental: build_e2e_image
test-e2e-non-experimental: build_e2e_image test-e2e-non-experimental: build_e2e_image
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME) docker run --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME)
.PHONY: test-e2e-containerized
test-e2e-containerized: build_e2e_image
docker run --rm --privileged \
-v /var/lib/docker \
-v /var/lib/containerd \
-v /lib/modules:/lib/modules \
$(E2E_IMAGE_NAME) /go/src/github.com/docker/cli/scripts/test/engine/entry
.PHONY: help .PHONY: help
help: ## print this help help: ## print this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

View File

@ -38,9 +38,13 @@ func CleanupEngine(t *testing.T) error {
return err return err
} }
// TODO Consider nuking the docker dir too so there's no cached content between test cases // TODO Consider nuking the docker dir too so there's no cached content between test cases
err = client.RemoveEngine(ctx)
if err != nil { // TODO - this needs refactoring still to actually work properly
t.Logf("Failed to remove engine: %s", err) /*
} err = client.RemoveEngine(ctx)
if err != nil {
t.Logf("Failed to remove engine: %s", err)
}
*/
return err return err
} }

View File

@ -24,6 +24,8 @@ type (
getImageFunc func(ctx context.Context, ref string) (containerd.Image, error) getImageFunc func(ctx context.Context, ref string) (containerd.Image, error)
contentStoreFunc func() content.Store contentStoreFunc func() content.Store
containerServiceFunc func() containers.Store containerServiceFunc func() containers.Store
installFunc func(context.Context, containerd.Image, ...containerd.InstallOpts) error
versionFunc func(ctx context.Context) (containerd.Version, error)
} }
fakeContainer struct { fakeContainer struct {
idFunc func() string idFunc func() string
@ -109,6 +111,18 @@ func (w *fakeContainerdClient) ContainerService() containers.Store {
func (w *fakeContainerdClient) Close() error { func (w *fakeContainerdClient) Close() error {
return nil return nil
} }
func (w *fakeContainerdClient) Install(ctx context.Context, image containerd.Image, args ...containerd.InstallOpts) error {
if w.installFunc != nil {
return w.installFunc(ctx, image, args...)
}
return nil
}
func (w *fakeContainerdClient) Version(ctx context.Context) (containerd.Version, error) {
if w.versionFunc != nil {
return w.versionFunc(ctx)
}
return containerd.Version{}, nil
}
func (c *fakeContainer) ID() string { func (c *fakeContainer) ID() string {
if c.idFunc != nil { if c.idFunc != nil {

View File

@ -64,6 +64,12 @@ var (
NoNewPrivileges: false, NoNewPrivileges: false,
}, },
} }
// RuntimeMetadataName is the name of the runtime metadata file
RuntimeMetadataName = "distribution_based_engine"
// ReleaseNotePrefix is where to point users to for release notes
ReleaseNotePrefix = "https://docs.docker.com/releasenotes"
) )
type baseClient struct { type baseClient struct {
@ -80,4 +86,12 @@ type containerdClient interface {
ContentStore() content.Store ContentStore() content.Store
ContainerService() containers.Store ContainerService() containers.Store
Install(context.Context, containerd.Image, ...containerd.InstallOpts) error Install(context.Context, containerd.Image, ...containerd.InstallOpts) error
Version(ctx context.Context) (containerd.Version, error)
}
// RuntimeMetadata holds platform information about the daemon
type RuntimeMetadata struct {
Platform string `json:"platform"`
ContainerdMinVersion string `json:"containerd_min_version"`
Runtime string `json:"runtime"`
} }

View File

@ -2,13 +2,22 @@ package containerizedengine
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
clitypes "github.com/docker/cli/types" clitypes "github.com/docker/cli/types"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
ver "github.com/hashicorp/go-version"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -34,6 +43,20 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio
return fmt.Errorf("please pick the version you want to update to") return fmt.Errorf("please pick the version you want to update to")
} }
localMetadata, err := c.GetCurrentRuntimeMetadata(ctx, "")
if err == nil {
if opts.EngineImage == "" {
if strings.Contains(strings.ToLower(localMetadata.Platform), "enterprise") {
opts.EngineImage = "engine-enterprise"
} else {
opts.EngineImage = "engine-community"
}
}
}
if opts.EngineImage == "" {
return fmt.Errorf("please pick the engine image to update with (engine-community or engine-enterprise)")
}
imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion) imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
// Look for desired image // Look for desired image
@ -48,5 +71,137 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio
return errors.Wrapf(err, "unable to check for image %s", imageName) return errors.Wrapf(err, "unable to check for image %s", imageName)
} }
} }
return c.cclient.Install(ctx, image, containerd.WithInstallReplace, containerd.WithInstallPath("/usr"))
// Make sure we're safe to proceed
newMetadata, err := c.PreflightCheck(ctx, image)
if err != nil {
return err
}
// Grab current metadata for comparison purposes
if localMetadata != nil {
if localMetadata.Platform != newMetadata.Platform {
fmt.Fprintf(out, "\nNotice: you have switched to \"%s\". Please refer to %s for update instructions.\n\n", newMetadata.Platform, c.GetReleaseNotesURL(imageName))
}
}
err = c.cclient.Install(ctx, image, containerd.WithInstallReplace, containerd.WithInstallPath("/usr"))
if err != nil {
return err
}
return c.WriteRuntimeMetadata(ctx, "", newMetadata)
}
var defaultDockerRoot = "/var/lib/docker"
// GetCurrentRuntimeMetadata loads the current daemon runtime metadata information from the local host
func (c *baseClient) GetCurrentRuntimeMetadata(_ context.Context, dockerRoot string) (*RuntimeMetadata, error) {
if dockerRoot == "" {
dockerRoot = defaultDockerRoot
}
filename := filepath.Join(dockerRoot, RuntimeMetadataName+".json")
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
var res RuntimeMetadata
err = json.Unmarshal(data, &res)
if err != nil {
return nil, errors.Wrapf(err, "malformed runtime metadata file %s", filename)
}
return &res, nil
}
func (c *baseClient) WriteRuntimeMetadata(_ context.Context, dockerRoot string, metadata *RuntimeMetadata) error {
if dockerRoot == "" {
dockerRoot = defaultDockerRoot
}
filename := filepath.Join(dockerRoot, RuntimeMetadataName+".json")
data, err := json.Marshal(metadata)
if err != nil {
return err
}
return ioutil.WriteFile(filename, data, 0644)
}
// PreflightCheck verifies the specified image is compatible with the local system before proceeding to update/activate
// If things look good, the RuntimeMetadata for the new image is returned and can be written out to the host
func (c *baseClient) PreflightCheck(ctx context.Context, image containerd.Image) (*RuntimeMetadata, error) {
var metadata RuntimeMetadata
ic, err := image.Config(ctx)
if err != nil {
return nil, err
}
var (
ociimage v1.Image
config v1.ImageConfig
)
switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil {
return nil, err
}
if err := json.Unmarshal(p, &ociimage); err != nil {
return nil, err
}
config = ociimage.Config
default:
return nil, fmt.Errorf("unknown image %s config media type %s", image.Name(), ic.MediaType)
}
metadataString, ok := config.Labels[RuntimeMetadataName]
if !ok {
return nil, fmt.Errorf("image %s does not contain runtime metadata label %s", image.Name(), RuntimeMetadataName)
}
err = json.Unmarshal([]byte(metadataString), &metadata)
if err != nil {
return nil, errors.Wrapf(err, "malformed runtime metadata file in %s", image.Name())
}
// Current CLI only supports host install runtime
if metadata.Runtime != "host_install" {
return nil, fmt.Errorf("unsupported runtime: %s\nPlease consult the release notes at %s for upgrade instructions", metadata.Runtime, c.GetReleaseNotesURL(image.Name()))
}
// Verify local containerd is new enough
localVersion, err := c.cclient.Version(ctx)
if err != nil {
return nil, err
}
if metadata.ContainerdMinVersion != "" {
lv, err := ver.NewVersion(localVersion.Version)
if err != nil {
return nil, err
}
mv, err := ver.NewVersion(metadata.ContainerdMinVersion)
if err != nil {
return nil, err
}
if lv.LessThan(mv) {
return nil, fmt.Errorf("local containerd is too old: %s - this engine version requires %s or newer.\nPlease consult the release notes at %s for upgrade instructions",
localVersion.Version, metadata.ContainerdMinVersion, c.GetReleaseNotesURL(image.Name()))
}
} // If omitted on metadata, no hard dependency on containerd version beyond 18.09 baseline
// All checks look OK, proceed with update
return &metadata, nil
}
// GetReleaseNotesURL returns a release notes url
// If the image name does not contain a version tag, the base release notes URL is returned
func (c *baseClient) GetReleaseNotesURL(imageName string) string {
versionTag := ""
distributionRef, err := reference.ParseNormalizedNamed(imageName)
if err == nil {
taggedRef, ok := distributionRef.(reference.NamedTagged)
if ok {
versionTag = taggedRef.Tag()
}
}
return fmt.Sprintf("%s/%s", ReleaseNotePrefix, versionTag)
} }

View File

@ -4,6 +4,9 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os"
"path/filepath"
"testing" "testing"
"github.com/containerd/containerd" "github.com/containerd/containerd"
@ -12,162 +15,20 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
clitypes "github.com/docker/cli/types" clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/assert" "gotest.tools/assert"
) )
func TestGetCurrentEngineVersionHappy(t *testing.T) { func TestActivateConfigFailure(t *testing.T) {
ctx := context.Background()
image := &fakeImage{
nameFunc: func() string {
return "acme.com/dockermirror/" + clitypes.CommunityEngineImage + ":engineversion"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
opts, err := client.GetCurrentEngineVersion(ctx)
assert.NilError(t, err)
assert.Equal(t, opts.EngineImage, clitypes.CommunityEngineImage)
assert.Equal(t, opts.RegistryPrefix, "acme.com/dockermirror")
assert.Equal(t, opts.EngineVersion, "engineversion")
}
func TestGetCurrentEngineVersionEnterpriseHappy(t *testing.T) {
ctx := context.Background()
image := &fakeImage{
nameFunc: func() string {
return "docker.io/store/docker/" + clitypes.EnterpriseEngineImage + ":engineversion"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
opts, err := client.GetCurrentEngineVersion(ctx)
assert.NilError(t, err)
assert.Equal(t, opts.EngineImage, clitypes.EnterpriseEngineImage)
assert.Equal(t, opts.EngineVersion, "engineversion")
assert.Equal(t, opts.RegistryPrefix, "docker.io/store/docker")
}
func TestGetCurrentEngineVersionNoEngine(t *testing.T) {
ctx := context.Background()
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
},
}
_, err := client.GetCurrentEngineVersion(ctx)
assert.ErrorContains(t, err, "failed to find existing engine")
}
func TestGetCurrentEngineVersionMiscEngineError(t *testing.T) {
ctx := context.Background()
expectedError := fmt.Errorf("some container lookup error")
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return nil, expectedError
},
},
}
_, err := client.GetCurrentEngineVersion(ctx)
assert.Assert(t, err == expectedError)
}
func TestGetCurrentEngineVersionImageFailure(t *testing.T) {
ctx := context.Background()
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return nil, fmt.Errorf("container image failure")
},
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
_, err := client.GetCurrentEngineVersion(ctx)
assert.ErrorContains(t, err, "container image failure")
}
func TestGetCurrentEngineVersionMalformed(t *testing.T) {
ctx := context.Background()
image := &fakeImage{
nameFunc: func() string {
return "imagename"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
_, err := client.GetCurrentEngineVersion(ctx)
assert.Assert(t, err == ErrEngineImageMissingTag)
}
func TestActivateNoEngine(t *testing.T) {
ctx := context.Background()
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
},
}
opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: clitypes.EnterpriseEngineImage,
}
err := client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "unable to find")
}
func TestActivateNoChange(t *testing.T) {
ctx := context.Background() ctx := context.Background()
registryPrefix := "registryprefixgoeshere" registryPrefix := "registryprefixgoeshere"
image := &fakeImage{ image := &fakeImage{
nameFunc: func() string { nameFunc: func() string {
return registryPrefix + "/" + clitypes.EnterpriseEngineImage + ":engineversion" return registryPrefix + "/" + clitypes.EnterpriseEngineImage + ":engineversion"
}, },
configFunc: func(ctx context.Context) (ocispec.Descriptor, error) {
return ocispec.Descriptor{}, fmt.Errorf("config lookup failure")
},
} }
container := &fakeContainer{ container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) { imageFunc: func(context.Context) (containerd.Image, error) {
@ -185,6 +46,9 @@ func TestActivateNoChange(t *testing.T) {
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil return []containerd.Container{container}, nil
}, },
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return image, nil
},
}, },
} }
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
@ -195,7 +59,7 @@ func TestActivateNoChange(t *testing.T) {
} }
err := client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) err := client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.NilError(t, err) assert.ErrorContains(t, err, "config lookup failure")
} }
func TestActivateDoUpdateFail(t *testing.T) { func TestActivateDoUpdateFail(t *testing.T) {
@ -292,30 +156,112 @@ func TestDoUpdatePullFail(t *testing.T) {
assert.ErrorContains(t, err, "pull failure") assert.ErrorContains(t, err, "pull failure")
} }
func TestDoUpdateEngineMissing(t *testing.T) { func TestActivateDoUpdateVerifyImageName(t *testing.T) {
ctx := context.Background() ctx := context.Background()
registryPrefix := "registryprefixgoeshere"
image := &fakeImage{
nameFunc: func() string {
return registryPrefix + "/ce-engine:engineversion"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
requestedImage := "unset"
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
requestedImage = ref
return nil, fmt.Errorf("something went wrong")
},
},
}
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere", EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", ConfigFile: "/tmp/configfilegoeshere",
EngineImage: "testnamegoeshere", //EngineImage: clitypes.EnterpriseEngineImage,
} }
image := &fakeImage{
nameFunc: func() string {
return "imagenamehere"
},
}
client := baseClient{
cclient: &fakeContainerdClient{
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return image, nil
}, tmpdir, err := ioutil.TempDir("", "docker-root")
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { assert.NilError(t, err)
return []containerd.Container{}, nil defer os.RemoveAll(tmpdir)
}, defaultDockerRoot = tmpdir
}, metadata := RuntimeMetadata{Platform: "platformgoeshere"}
} err = client.WriteRuntimeMetadata(ctx, tmpdir, &metadata)
err := client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) assert.NilError(t, err)
assert.ErrorContains(t, err, "unable to find existing engine")
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong")
expectedImage := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, "engine-community", opts.EngineVersion)
assert.Assert(t, requestedImage == expectedImage, "%s != %s", requestedImage, expectedImage)
// Redo with enterprise set
metadata = RuntimeMetadata{Platform: "Docker Engine - Enterprise"}
err = client.WriteRuntimeMetadata(ctx, tmpdir, &metadata)
assert.NilError(t, err)
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong")
expectedImage = fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, "engine-enterprise", opts.EngineVersion)
assert.Assert(t, requestedImage == expectedImage, "%s != %s", requestedImage, expectedImage)
}
func TestGetCurrentRuntimeMetadataNotPresent(t *testing.T) {
ctx := context.Background()
tmpdir, err := ioutil.TempDir("", "docker-root")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
client := baseClient{}
_, err = client.GetCurrentRuntimeMetadata(ctx, tmpdir)
assert.ErrorType(t, err, os.IsNotExist)
}
func TestGetCurrentRuntimeMetadataBadJson(t *testing.T) {
ctx := context.Background()
tmpdir, err := ioutil.TempDir("", "docker-root")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
filename := filepath.Join(tmpdir, RuntimeMetadataName+".json")
err = ioutil.WriteFile(filename, []byte("not json"), 0644)
assert.NilError(t, err)
client := baseClient{}
_, err = client.GetCurrentRuntimeMetadata(ctx, tmpdir)
assert.ErrorContains(t, err, "malformed runtime metadata file")
}
func TestGetCurrentRuntimeMetadataHappyPath(t *testing.T) {
ctx := context.Background()
tmpdir, err := ioutil.TempDir("", "docker-root")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
client := baseClient{}
metadata := RuntimeMetadata{Platform: "platformgoeshere"}
err = client.WriteRuntimeMetadata(ctx, tmpdir, &metadata)
assert.NilError(t, err)
res, err := client.GetCurrentRuntimeMetadata(ctx, tmpdir)
assert.NilError(t, err)
assert.Equal(t, res.Platform, "platformgoeshere")
}
func TestGetReleaseNotesURL(t *testing.T) {
client := baseClient{}
imageName := "bogus image name #$%&@!"
url := client.GetReleaseNotesURL(imageName)
assert.Equal(t, url, ReleaseNotePrefix+"/")
imageName = "foo.bar/valid/repowithouttag"
url = client.GetReleaseNotesURL(imageName)
assert.Equal(t, url, ReleaseNotePrefix+"/")
imageName = "foo.bar/valid/repowithouttag:tag123"
url = client.GetReleaseNotesURL(imageName)
assert.Equal(t, url, ReleaseNotePrefix+"/tag123")
} }

View File

@ -1,19 +1,23 @@
package containerizedengine package versions
import ( import (
"context" "context"
"path"
"sort" "sort"
"strings"
registryclient "github.com/docker/cli/cli/registry/client" registryclient "github.com/docker/cli/cli/registry/client"
clitypes "github.com/docker/cli/types" clitypes "github.com/docker/cli/types"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
ver "github.com/hashicorp/go-version" ver "github.com/hashicorp/go-version"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// GetEngineVersions reports the versions of the engine that are available // GetEngineVersions reports the versions of the engine that are available
func (c *baseClient) GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, currentVersion, imageName string) (clitypes.AvailableVersions, error) { func GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, registryPrefix string, serverVersion types.Version) (clitypes.AvailableVersions, error) {
imageName := getEngineImage(registryPrefix, serverVersion)
imageRef, err := reference.ParseNormalizedNamed(imageName) imageRef, err := reference.ParseNormalizedNamed(imageName)
if err != nil { if err != nil {
return clitypes.AvailableVersions{}, err return clitypes.AvailableVersions{}, err
@ -24,7 +28,23 @@ func (c *baseClient) GetEngineVersions(ctx context.Context, registryClient regis
return clitypes.AvailableVersions{}, err return clitypes.AvailableVersions{}, err
} }
return parseTags(tags, currentVersion) return parseTags(tags, serverVersion.Version)
}
func getEngineImage(registryPrefix string, serverVersion types.Version) string {
communityImage := "engine-community"
enterpriseImage := "engine-enterprise"
platform := strings.ToLower(serverVersion.Platform.Name)
if platform != "" {
if strings.Contains(platform, "enterprise") {
return path.Join(registryPrefix, enterpriseImage)
}
return path.Join(registryPrefix, communityImage)
}
if strings.Contains(serverVersion.Version, "ee") {
return path.Join(registryPrefix, enterpriseImage)
}
return path.Join(registryPrefix, communityImage)
} }
func parseTags(tags []string, currentVersion string) (clitypes.AvailableVersions, error) { func parseTags(tags []string, currentVersion string) (clitypes.AvailableVersions, error) {

View File

@ -1,19 +1,19 @@
package containerizedengine package versions
import ( import (
"context" "context"
"testing" "testing"
"github.com/docker/docker/api/types"
"gotest.tools/assert" "gotest.tools/assert"
) )
func TestGetEngineVersionsBadImage(t *testing.T) { func TestGetEngineVersionsBadImage(t *testing.T) {
ctx := context.Background() ctx := context.Background()
client := baseClient{}
currentVersion := "currentversiongoeshere" registryPrefix := "this is an illegal image $%^&"
imageName := "this is an illegal image $%^&" currentVersion := types.Version{Version: "currentversiongoeshere"}
_, err := client.GetEngineVersions(ctx, nil, currentVersion, imageName) _, err := GetEngineVersions(ctx, nil, registryPrefix, currentVersion)
assert.ErrorContains(t, err, "invalid reference format") assert.ErrorContains(t, err, "invalid reference format")
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"io" "io"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
ver "github.com/hashicorp/go-version" ver "github.com/hashicorp/go-version"
) )
@ -36,7 +35,6 @@ type ContainerizedClient interface {
out OutStream, out OutStream,
authConfig *types.AuthConfig, authConfig *types.AuthConfig,
healthfn func(context.Context) error) error healthfn func(context.Context) error) error
GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, currentVersion, imageName string) (AvailableVersions, error)
} }
// EngineInitOptions contains the configuration settings // EngineInitOptions contains the configuration settings