Refine how metadata dir is handled

This is a follow up PR to #1381 to address some of the review comments
we didn't get to.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
(cherry picked from commit c12e23a4c1)
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
This commit is contained in:
Daniel Hiltgen 2018-09-28 14:06:28 -07:00
parent ea5f4c4984
commit 4a888d3031
12 changed files with 305 additions and 212 deletions

View File

@ -3,6 +3,7 @@ package engine
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
@ -57,7 +58,7 @@ https://hub.docker.com/ then specify the file with the '--license' flag.
flags.StringVar(&options.licenseFile, "license", "", "License File") flags.StringVar(&options.licenseFile, "license", "", "License File")
flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)") flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)")
flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the default location where engine images are pulled") flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the default location where engine images are pulled")
flags.StringVar(&options.image, "engine-image", clitypes.EnterpriseEngineImage, "Specify engine image") flags.StringVar(&options.image, "engine-image", "", "Specify engine image")
flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template") flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
flags.BoolVar(&options.displayOnly, "display-only", false, "only display the available licenses and exit") flags.BoolVar(&options.displayOnly, "display-only", false, "only display the available licenses and exit")
flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID") flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
@ -102,10 +103,24 @@ func runActivate(cli command.Cli, options activateOptions) error {
if options.displayOnly { if options.displayOnly {
return nil return nil
} }
if err = licenseutils.ApplyLicense(ctx, cli.Client(), license); err != nil { dclient := cli.Client()
if err = licenseutils.ApplyLicense(ctx, dclient, license); err != nil {
return err return err
} }
// Short circuit if the user didn't specify a version and we're already running enterprise
if options.version == "" {
serverVersion, err := dclient.ServerVersion(ctx)
if err != nil {
return err
}
if strings.Contains(strings.ToLower(serverVersion.Platform.Name), "enterprise") {
fmt.Fprintln(cli.Out(), "Successfully activated engine license on existing enterprise engine.")
return nil
}
options.version = serverVersion.Version
}
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
RegistryPrefix: options.registryPrefix, RegistryPrefix: options.registryPrefix,
EngineImage: options.image, EngineImage: options.image,

View File

@ -53,7 +53,7 @@ func TestActivateExpiredLicenseDryRun(t *testing.T) {
defer dir.Remove() defer dir.Remove()
filename := dir.Join("docker.lic") filename := dir.Join("docker.lic")
isRoot = func() bool { return true } isRoot = func() bool { return true }
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil}) c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
c.SetContainerizedEngineClient( c.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) { func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil return &fakeContainerizedEngineClient{}, nil

View File

@ -16,6 +16,7 @@ import (
type checkOptions struct { type checkOptions struct {
registryPrefix string registryPrefix string
preReleases bool preReleases bool
engineImage string
downgrades bool downgrades bool
upgrades bool upgrades bool
format string format string
@ -38,6 +39,7 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the existing location where engine images are pulled") flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "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.StringVar(&options.engineImage, "engine-image", "", "Specify engine image (default uses the same image as currently running)")
flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades") flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades")
flags.StringVar(&options.format, "format", "", "Pretty-print updates using a Go template") flags.StringVar(&options.format, "format", "", "Pretty-print updates using a Go template")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display available versions") flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display available versions")
@ -57,7 +59,7 @@ func runCheck(dockerCli command.Cli, options checkOptions) error {
return err return err
} }
availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, serverVersion) availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, options.engineImage, serverVersion.Version)
if err != nil { if err != nil {
return err return err
} }

View File

@ -22,14 +22,20 @@ var (
type verClient struct { type verClient struct {
client.Client client.Client
ver types.Version ver types.Version
verErr error verErr error
info types.Info
infoErr error
} }
func (c *verClient) ServerVersion(ctx context.Context) (types.Version, error) { func (c *verClient) ServerVersion(ctx context.Context) (types.Version, error) {
return c.ver, c.verErr return c.ver, c.verErr
} }
func (c *verClient) Info(ctx context.Context) (types.Info, error) {
return c.info, c.infoErr
}
type testRegistryClient struct { type testRegistryClient struct {
tags []string tags []string
} }
@ -53,26 +59,28 @@ func (c testRegistryClient) GetTags(ctx context.Context, ref reference.Named) ([
func TestCheckForUpdatesNoCurrentVersion(t *testing.T) { func TestCheckForUpdatesNoCurrentVersion(t *testing.T) {
isRoot = func() bool { return true } isRoot = func() bool { return true }
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil}) c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
c.SetRegistryClient(testRegistryClient{}) c.SetRegistryClient(testRegistryClient{})
cmd := newCheckForUpdatesCommand(c) cmd := newCheckForUpdatesCommand(c)
cmd.SilenceUsage = true cmd.SilenceUsage = true
cmd.SilenceErrors = true cmd.SilenceErrors = true
err := cmd.Execute() err := cmd.Execute()
assert.ErrorContains(t, err, "alformed version") assert.ErrorContains(t, err, "no such file or directory")
} }
func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) { func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) {
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil}) c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
c.SetRegistryClient(testRegistryClient{[]string{ c.SetRegistryClient(testRegistryClient{[]string{
"1.0.1", "1.0.2", "1.0.3-beta1", "1.0.1", "1.0.2", "1.0.3-beta1",
"1.1.1", "1.1.2", "1.1.3-beta1", "1.1.1", "1.1.2", "1.1.3-beta1",
"1.2.0", "2.0.0", "2.1.0-beta1", "1.2.0", "2.0.0", "2.1.0-beta1",
}}) }})
isRoot = func() bool { return true } isRoot = func() bool { return true }
cmd := newCheckForUpdatesCommand(c) cmd := newCheckForUpdatesCommand(c)
cmd.Flags().Set("pre-releases", "true") cmd.Flags().Set("pre-releases", "true")
cmd.Flags().Set("downgrades", "true") cmd.Flags().Set("downgrades", "true")
cmd.Flags().Set("engine-image", "engine-community")
cmd.SilenceUsage = true cmd.SilenceUsage = true
cmd.SilenceErrors = true cmd.SilenceErrors = true
err := cmd.Execute() err := cmd.Execute()

View File

@ -25,7 +25,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version") flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version")
flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image") flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image (default uses the same image as currently running)")
flags.StringVar(&options.RegistryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the current location where engine images are pulled") flags.StringVar(&options.RegistryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the current location where engine images are pulled")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint") flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
@ -46,7 +46,6 @@ func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
if err != nil { if err != nil {
return err return err
} }
if err := client.DoUpdate(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig, if err := client.DoUpdate(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig,
func(ctx context.Context) error { func(ctx context.Context) error {
client := dockerCli.Client() client := dockerCli.Client()

View File

@ -4,7 +4,10 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/docker/cli/internal/test"
clitypes "github.com/docker/cli/types" clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"gotest.tools/assert" "gotest.tools/assert"
) )
@ -22,14 +25,16 @@ func TestUpdateNoContainerd(t *testing.T) {
} }
func TestUpdateHappy(t *testing.T) { func TestUpdateHappy(t *testing.T) {
testCli.SetContainerizedEngineClient( c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
c.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) { func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil return &fakeContainerizedEngineClient{}, nil
}, },
) )
cmd := newUpdateCommand(testCli) cmd := newUpdateCommand(c)
cmd.Flags().Set("registry-prefix", clitypes.RegistryPrefix) cmd.Flags().Set("registry-prefix", clitypes.RegistryPrefix)
cmd.Flags().Set("version", "someversion") cmd.Flags().Set("version", "someversion")
cmd.Flags().Set("engine-image", "someimage")
err := cmd.Execute() err := cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@ -12,10 +12,6 @@ import (
const ( const (
containerdSockPath = "/run/containerd/containerd.sock" containerdSockPath = "/run/containerd/containerd.sock"
engineNamespace = "com.docker" engineNamespace = "com.docker"
// runtimeMetadataName is the name of the runtime metadata file
// When stored as a label on the container it is prefixed by "com.docker."
runtimeMetadataName = "distribution_based_engine"
) )
var ( var (
@ -51,10 +47,3 @@ type containerdClient interface {
Install(context.Context, containerd.Image, ...containerd.InstallOpts) error Install(context.Context, containerd.Image, ...containerd.InstallOpts) error
Version(ctx context.Context) (containerd.Version, 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

@ -4,9 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os"
"path/filepath"
"strings" "strings"
"github.com/containerd/containerd" "github.com/containerd/containerd"
@ -14,6 +11,7 @@ import (
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/docker/cli/internal/versions"
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" "github.com/docker/docker/api/types"
@ -26,6 +24,25 @@ import (
func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream, func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
authConfig *types.AuthConfig, healthfn func(context.Context) error) error { authConfig *types.AuthConfig, healthfn func(context.Context) error) error {
// If the user didn't specify an image, determine the correct enterprise image to use
if opts.EngineImage == "" {
localMetadata, err := versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
if err != nil {
return errors.Wrap(err, "unable to determine the installed engine version. Specify which engine image to update with --engine-image")
}
engineImage := localMetadata.EngineImage
if engineImage == clitypes.EnterpriseEngineImage || engineImage == clitypes.CommunityEngineImage {
opts.EngineImage = clitypes.EnterpriseEngineImage
} else {
// Chop off the standard prefix and retain any trailing OS specific image details
// e.g., engine-community-dm -> engine-enterprise-dm
engineImage = strings.TrimPrefix(engineImage, clitypes.EnterpriseEngineImage)
engineImage = strings.TrimPrefix(engineImage, clitypes.CommunityEngineImage)
opts.EngineImage = clitypes.EnterpriseEngineImage + engineImage
}
}
ctx = namespaces.WithNamespace(ctx, engineNamespace) ctx = namespaces.WithNamespace(ctx, engineNamespace)
return c.DoUpdate(ctx, opts, out, authConfig, healthfn) return c.DoUpdate(ctx, opts, out, authConfig, healthfn)
} }
@ -43,19 +60,14 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio
// `docker engine update` // `docker engine update`
return fmt.Errorf("pick the version you want to update to with --version") return fmt.Errorf("pick the version you want to update to with --version")
} }
var localMetadata *clitypes.RuntimeMetadata
localMetadata, err := c.GetCurrentRuntimeMetadata(ctx, "")
if err == nil {
if opts.EngineImage == "" {
if strings.Contains(strings.ToLower(localMetadata.Platform), "community") {
opts.EngineImage = clitypes.CommunityEngineImage
} else {
opts.EngineImage = clitypes.EnterpriseEngineImage
}
}
}
if opts.EngineImage == "" { if opts.EngineImage == "" {
return fmt.Errorf("unable to determine the installed engine version. Specify which engine image to update with --engine-image set to 'engine-community' or 'engine-enterprise'") var err error
localMetadata, err = versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
if err != nil {
return errors.Wrap(err, "unable to determine the installed engine version. Specify which engine image to update with --engine-image set to 'engine-community' or 'engine-enterprise'")
}
opts.EngineImage = localMetadata.EngineImage
} }
imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion) imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
@ -78,7 +90,6 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio
if err != nil { if err != nil {
return err return err
} }
// Grab current metadata for comparison purposes
if localMetadata != nil { if localMetadata != nil {
if localMetadata.Platform != newMetadata.Platform { if localMetadata.Platform != newMetadata.Platform {
fmt.Fprintf(out, "\nNotice: you have switched to \"%s\". Refer to %s for update instructions.\n\n", newMetadata.Platform, getReleaseNotesURL(imageName)) fmt.Fprintf(out, "\nNotice: you have switched to \"%s\". Refer to %s for update instructions.\n\n", newMetadata.Platform, getReleaseNotesURL(imageName))
@ -89,50 +100,13 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio
return err return err
} }
return c.WriteRuntimeMetadata("", newMetadata) return versions.WriteRuntimeMetadata(opts.RuntimeMetadataDir, 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
}
// WriteRuntimeMetadata stores the metadata on the local system
func (c *baseClient) WriteRuntimeMetadata(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
}
os.Remove(filename)
return ioutil.WriteFile(filename, data, 0644)
} }
// PreflightCheck verifies the specified image is compatible with the local system before proceeding to update/activate // 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 // 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) { func (c *baseClient) PreflightCheck(ctx context.Context, image containerd.Image) (*clitypes.RuntimeMetadata, error) {
var metadata RuntimeMetadata var metadata clitypes.RuntimeMetadata
ic, err := image.Config(ctx) ic, err := image.Config(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -156,9 +130,9 @@ func (c *baseClient) PreflightCheck(ctx context.Context, image containerd.Image)
return nil, fmt.Errorf("unknown image %s config media type %s", image.Name(), ic.MediaType) return nil, fmt.Errorf("unknown image %s config media type %s", image.Name(), ic.MediaType)
} }
metadataString, ok := config.Labels["com.docker."+runtimeMetadataName] metadataString, ok := config.Labels["com.docker."+clitypes.RuntimeMetadataName]
if !ok { if !ok {
return nil, fmt.Errorf("image %s does not contain runtime metadata label %s", image.Name(), runtimeMetadataName) return nil, fmt.Errorf("image %s does not contain runtime metadata label %s", image.Name(), clitypes.RuntimeMetadataName)
} }
err = json.Unmarshal([]byte(metadataString), &metadata) err = json.Unmarshal([]byte(metadataString), &metadata)
if err != nil { if err != nil {

View File

@ -6,13 +6,13 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/cio" "github.com/containerd/containerd/cio"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/versions"
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" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -23,6 +23,51 @@ func healthfnHappy(ctx context.Context) error {
return nil return nil
} }
func TestActivateImagePermutations(t *testing.T) {
ctx := context.Background()
lookedup := "not called yet"
expectedError := fmt.Errorf("expected error")
client := baseClient{
cclient: &fakeContainerdClient{
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
lookedup = ref
return nil, expectedError
},
},
}
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
RuntimeMetadataDir: tmpdir,
}
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, expectedError.Error())
assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage, opts.EngineVersion))
metadata = clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, expectedError.Error())
assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage, opts.EngineVersion))
metadata = clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage + "-dm"}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, expectedError.Error())
assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage+"-dm", opts.EngineVersion))
}
func TestActivateConfigFailure(t *testing.T) { func TestActivateConfigFailure(t *testing.T) {
ctx := context.Background() ctx := context.Background()
registryPrefix := "registryprefixgoeshere" registryPrefix := "registryprefixgoeshere"
@ -55,14 +100,21 @@ func TestActivateConfigFailure(t *testing.T) {
}, },
}, },
} }
tmpdir, err := ioutil.TempDir("", "engindir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere", EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", ConfigFile: "/tmp/configfilegoeshere",
EngineImage: clitypes.EnterpriseEngineImage, EngineImage: clitypes.EnterpriseEngineImage,
RuntimeMetadataDir: tmpdir,
} }
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.ErrorContains(t, err, "config lookup failure") assert.ErrorContains(t, err, "config lookup failure")
} }
@ -90,38 +142,60 @@ func TestActivateDoUpdateFail(t *testing.T) {
}, },
}, },
} }
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere", EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", ConfigFile: "/tmp/configfilegoeshere",
EngineImage: clitypes.EnterpriseEngineImage, EngineImage: clitypes.EnterpriseEngineImage,
RuntimeMetadataDir: tmpdir,
} }
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.ErrorContains(t, err, "check for image") assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong") assert.ErrorContains(t, err, "something went wrong")
} }
func TestDoUpdateNoVersion(t *testing.T) { func TestDoUpdateNoVersion(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
ctx := context.Background() ctx := context.Background()
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "", EngineVersion: "",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", ConfigFile: "/tmp/configfilegoeshere",
EngineImage: clitypes.EnterpriseEngineImage, EngineImage: clitypes.EnterpriseEngineImage,
RuntimeMetadataDir: tmpdir,
} }
client := baseClient{} client := baseClient{}
err := client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) err = client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "pick the version you") assert.ErrorContains(t, err, "pick the version you")
} }
func TestDoUpdateImageMiscError(t *testing.T) { func TestDoUpdateImageMiscError(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere", EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", ConfigFile: "/tmp/configfilegoeshere",
EngineImage: "testnamegoeshere", EngineImage: "testnamegoeshere",
RuntimeMetadataDir: tmpdir,
} }
client := baseClient{ client := baseClient{
cclient: &fakeContainerdClient{ cclient: &fakeContainerdClient{
@ -131,18 +205,26 @@ func TestDoUpdateImageMiscError(t *testing.T) {
}, },
}, },
} }
err := client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
err = client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "check for image") assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong") assert.ErrorContains(t, err, "something went wrong")
} }
func TestDoUpdatePullFail(t *testing.T) { func TestDoUpdatePullFail(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere", EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", ConfigFile: "/tmp/configfilegoeshere",
EngineImage: "testnamegoeshere", EngineImage: "testnamegoeshere",
RuntimeMetadataDir: tmpdir,
} }
client := baseClient{ client := baseClient{
cclient: &fakeContainerdClient{ cclient: &fakeContainerdClient{
@ -155,7 +237,8 @@ func TestDoUpdatePullFail(t *testing.T) {
}, },
}, },
} }
err := client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
err = client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "unable to pull") assert.ErrorContains(t, err, "unable to pull")
assert.ErrorContains(t, err, "pull failure") assert.ErrorContains(t, err, "pull failure")
} }
@ -186,78 +269,26 @@ func TestActivateDoUpdateVerifyImageName(t *testing.T) {
}, },
}, },
} }
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere", EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", EngineImage: "testnamegoeshere",
ConfigFile: "/tmp/configfilegoeshere",
RuntimeMetadataDir: tmpdir,
} }
tmpdir, err := ioutil.TempDir("", "docker-root")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
tmpDockerRoot := defaultDockerRoot
defaultDockerRoot = tmpdir
defer func() {
defaultDockerRoot = tmpDockerRoot
}()
metadata := RuntimeMetadata{Platform: "platformgoeshere"}
err = client.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
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.ErrorContains(t, err, "check for image") assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong") assert.ErrorContains(t, err, "something went wrong")
expectedImage := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, "engine-enterprise", opts.EngineVersion) expectedImage := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
assert.Assert(t, requestedImage == expectedImage, "%s != %s", requestedImage, expectedImage) assert.Assert(t, requestedImage == expectedImage, "%s != %s", requestedImage, expectedImage)
// Redo with enterprise set
metadata = RuntimeMetadata{Platform: "Docker Engine - Enterprise"}
err = client.WriteRuntimeMetadata(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(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) { func TestGetReleaseNotesURL(t *testing.T) {

View File

@ -2,23 +2,38 @@ package versions
import ( import (
"context" "context"
"encoding/json"
"io/ioutil"
"os"
"path" "path"
"path/filepath"
"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"
) )
const (
// defaultRuntimeMetadataDir is the location where the metadata file is stored
defaultRuntimeMetadataDir = "/var/lib/docker-engine"
)
// GetEngineVersions reports the versions of the engine that are available // GetEngineVersions reports the versions of the engine that are available
func GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, registryPrefix string, serverVersion types.Version) (clitypes.AvailableVersions, error) { func GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, registryPrefix, imageName, versionString string) (clitypes.AvailableVersions, error) {
imageName := getEngineImage(registryPrefix, serverVersion)
imageRef, err := reference.ParseNormalizedNamed(imageName) if imageName == "" {
var err error
localMetadata, err := GetCurrentRuntimeMetadata("")
if err != nil {
return clitypes.AvailableVersions{}, err
}
imageName = localMetadata.EngineImage
}
imageRef, err := reference.ParseNormalizedNamed(path.Join(registryPrefix, imageName))
if err != nil { if err != nil {
return clitypes.AvailableVersions{}, err return clitypes.AvailableVersions{}, err
} }
@ -28,25 +43,7 @@ func GetEngineVersions(ctx context.Context, registryClient registryclient.Regist
return clitypes.AvailableVersions{}, err return clitypes.AvailableVersions{}, err
} }
return parseTags(tags, serverVersion.Version) return parseTags(tags, versionString)
}
func getEngineImage(registryPrefix string, serverVersion types.Version) string {
platform := strings.ToLower(serverVersion.Platform.Name)
if platform != "" {
if strings.Contains(platform, "enterprise") {
return path.Join(registryPrefix, clitypes.EnterpriseEngineImage)
}
return path.Join(registryPrefix, clitypes.CommunityEngineImage)
}
// TODO This check is only applicable for early 18.09 builds that had some packaging bugs
// and can be removed once we're no longer testing with them
if strings.Contains(serverVersion.Version, "ee") {
return path.Join(registryPrefix, clitypes.EnterpriseEngineImage)
}
return path.Join(registryPrefix, clitypes.CommunityEngineImage)
} }
func parseTags(tags []string, currentVersion string) (clitypes.AvailableVersions, error) { func parseTags(tags []string, currentVersion string) (clitypes.AvailableVersions, error) {
@ -93,3 +90,38 @@ func parseTags(tags []string, currentVersion string) (clitypes.AvailableVersions
ret.Upgrades = upgrades ret.Upgrades = upgrades
return ret, nil return ret, nil
} }
// GetCurrentRuntimeMetadata loads the current daemon runtime metadata information from the local host
func GetCurrentRuntimeMetadata(metadataDir string) (*clitypes.RuntimeMetadata, error) {
if metadataDir == "" {
metadataDir = defaultRuntimeMetadataDir
}
filename := filepath.Join(metadataDir, clitypes.RuntimeMetadataName+".json")
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
var res clitypes.RuntimeMetadata
err = json.Unmarshal(data, &res)
if err != nil {
return nil, errors.Wrapf(err, "malformed runtime metadata file %s", filename)
}
return &res, nil
}
// WriteRuntimeMetadata stores the metadata on the local system
func WriteRuntimeMetadata(metadataDir string, metadata *clitypes.RuntimeMetadata) error {
if metadataDir == "" {
metadataDir = defaultRuntimeMetadataDir
}
filename := filepath.Join(metadataDir, clitypes.RuntimeMetadataName+".json")
data, err := json.Marshal(metadata)
if err != nil {
return err
}
os.Remove(filename)
return ioutil.WriteFile(filename, data, 0644)
}

View File

@ -1,22 +1,15 @@
package versions package versions
import ( import (
"context" "io/ioutil"
"os"
"path/filepath"
"testing" "testing"
"github.com/docker/docker/api/types" clitypes "github.com/docker/cli/types"
"gotest.tools/assert" "gotest.tools/assert"
) )
func TestGetEngineVersionsBadImage(t *testing.T) {
ctx := context.Background()
registryPrefix := "this is an illegal image $%^&"
currentVersion := types.Version{Version: "currentversiongoeshere"}
_, err := GetEngineVersions(ctx, nil, registryPrefix, currentVersion)
assert.ErrorContains(t, err, "invalid reference format")
}
func TestParseTagsSimple(t *testing.T) { func TestParseTagsSimple(t *testing.T) {
tags := []string{"1.0.0", "1.1.2", "1.1.1", "1.2.2"} tags := []string{"1.0.0", "1.1.2", "1.1.1", "1.2.2"}
currentVersion := "1.1.0" currentVersion := "1.1.0"
@ -78,3 +71,35 @@ func TestParseBadCurrent2(t *testing.T) {
_, err := parseTags(tags, currentVersion) _, err := parseTags(tags, currentVersion)
assert.ErrorContains(t, err, "failed to parse existing") assert.ErrorContains(t, err, "failed to parse existing")
} }
func TestGetCurrentRuntimeMetadataNotPresent(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-root")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
_, err = GetCurrentRuntimeMetadata(tmpdir)
assert.ErrorType(t, err, os.IsNotExist)
}
func TestGetCurrentRuntimeMetadataBadJson(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-root")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
filename := filepath.Join(tmpdir, clitypes.RuntimeMetadataName+".json")
err = ioutil.WriteFile(filename, []byte("not json"), 0644)
assert.NilError(t, err)
_, err = GetCurrentRuntimeMetadata(tmpdir)
assert.ErrorContains(t, err, "malformed runtime metadata file")
}
func TestGetCurrentRuntimeMetadataHappyPath(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-root")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{Platform: "platformgoeshere"}
err = WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
res, err := GetCurrentRuntimeMetadata(tmpdir)
assert.NilError(t, err)
assert.Equal(t, res.Platform, "platformgoeshere")
}

View File

@ -20,6 +20,10 @@ const (
// ReleaseNotePrefix is where to point users to for release notes // ReleaseNotePrefix is where to point users to for release notes
ReleaseNotePrefix = "https://docs.docker.com/releasenotes" ReleaseNotePrefix = "https://docs.docker.com/releasenotes"
// RuntimeMetadataName is the name of the runtime metadata file
// When stored as a label on the container it is prefixed by "com.docker."
RuntimeMetadataName = "distribution_based_engine"
) )
// ContainerizedClient can be used to manage the lifecycle of // ContainerizedClient can be used to manage the lifecycle of
@ -41,10 +45,11 @@ type ContainerizedClient interface {
// EngineInitOptions contains the configuration settings // EngineInitOptions contains the configuration settings
// use during initialization of a containerized docker engine // use during initialization of a containerized docker engine
type EngineInitOptions struct { type EngineInitOptions struct {
RegistryPrefix string RegistryPrefix string
EngineImage string EngineImage string
EngineVersion string EngineVersion string
ConfigFile string ConfigFile string
RuntimeMetadataDir string
} }
// AvailableVersions groups the available versions which were discovered // AvailableVersions groups the available versions which were discovered
@ -75,3 +80,11 @@ type OutStream interface {
FD() uintptr FD() uintptr
IsTerminal() bool IsTerminal() bool
} }
// RuntimeMetadata holds platform information about the daemon
type RuntimeMetadata struct {
Platform string `json:"platform"`
ContainerdMinVersion string `json:"containerd_min_version"`
Runtime string `json:"runtime"`
EngineImage string `json:"engine_image"`
}