mirror of https://github.com/docker/cli.git
Refined engine implementations
Adapt the CLI to the host install model for 18.09.
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
(cherry picked from commit 342afe44fb
)
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
This commit is contained in:
parent
eacb812c26
commit
f07f51f4c8
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
isRoot = func() bool {
|
||||||
|
return unix.Geteuid() == 0
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package engine
|
||||||
|
|
||||||
|
var (
|
||||||
|
isRoot = func() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
|
@ -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,48 +51,47 @@ 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()
|
ctx := context.Background()
|
||||||
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
client := dockerCli.Client()
|
||||||
|
serverVersion, err := client.ServerVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "unable to access local containerd")
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
|
||||||
versions, err := client.GetEngineVersions(ctx, dockerCli.RegistryClient(false), currentVersion, imageName)
|
availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, serverVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
||||||
|
@ -104,15 +105,13 @@ func runCheck(dockerCli command.Cli, options checkOptions) error {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
// TODO - this needs refactoring still to actually work properly
|
||||||
|
/*
|
||||||
err = client.RemoveEngine(ctx)
|
err = client.RemoveEngine(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Failed to remove engine: %s", err)
|
t.Logf("Failed to remove engine: %s", err)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/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/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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue