diff --git a/Makefile b/Makefile index 982d953eb4..51b168dcaa 100644 --- a/Makefile +++ b/Makefile @@ -12,14 +12,14 @@ clean: ## remove build artifacts .PHONY: test-unit test-unit: ## run unit test - ./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/') + ./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/') .PHONY: test test: test-unit ## run tests .PHONY: test-coverage test-coverage: ## run test coverage - ./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/') + ./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/') .PHONY: lint lint: ## run all the lint tools diff --git a/cli/command/engine/activate.go b/cli/command/engine/activate.go index 270daff720..ba98eed72d 100644 --- a/cli/command/engine/activate.go +++ b/cli/command/engine/activate.go @@ -56,7 +56,7 @@ https://hub.docker.com/ then specify the file with the '--license' flag. 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.registryPrefix, "registry-prefix", "docker.io/docker", "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.format, "format", "", "Pretty-print licenses using a Go template") flags.BoolVar(&options.displayOnly, "display-only", false, "only display the available licenses and exit") @@ -67,6 +67,9 @@ https://hub.docker.com/ then specify the file with the '--license' flag. } func runActivate(cli command.Cli, options activateOptions) error { + if !isRoot() { + return errors.New("this command must be run as a privileged user") + } ctx := context.Background() client, err := cli.NewContainerizedEngineClient(options.sockPath) if err != nil { @@ -104,12 +107,17 @@ func runActivate(cli command.Cli, options activateOptions) error { EngineVersion: options.version, } - return client.ActivateEngine(ctx, opts, cli.Out(), authConfig, + if err := client.ActivateEngine(ctx, opts, cli.Out(), authConfig, func(ctx context.Context) error { client := cli.Client() _, err := client.Ping(ctx) return err - }) + }); err != nil { + return err + } + fmt.Fprintln(cli.Out(), `Successfully activated engine. +Restart docker with 'systemctl restart docker' to complete the activation.`) + return nil } func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) { diff --git a/cli/command/engine/activate_test.go b/cli/command/engine/activate_test.go index 6936161eef..6fe552c7da 100644 --- a/cli/command/engine/activate_test.go +++ b/cli/command/engine/activate_test.go @@ -14,6 +14,7 @@ func TestActivateNoContainerd(t *testing.T) { return nil, fmt.Errorf("some error") }, ) + isRoot = func() bool { return true } cmd := newActivateCommand(testCli) cmd.Flags().Set("license", "invalidpath") cmd.SilenceUsage = true @@ -28,6 +29,7 @@ func TestActivateBadLicense(t *testing.T) { return &fakeContainerizedEngineClient{}, nil }, ) + isRoot = func() bool { return true } cmd := newActivateCommand(testCli) cmd.SilenceUsage = true cmd.SilenceErrors = true diff --git a/cli/command/engine/activate_unix.go b/cli/command/engine/activate_unix.go new file mode 100644 index 0000000000..ed4777ae2e --- /dev/null +++ b/cli/command/engine/activate_unix.go @@ -0,0 +1,13 @@ +// +build !windows + +package engine + +import ( + "golang.org/x/sys/unix" +) + +var ( + isRoot = func() bool { + return unix.Geteuid() == 0 + } +) diff --git a/cli/command/engine/activate_windows.go b/cli/command/engine/activate_windows.go new file mode 100644 index 0000000000..35a4e88a36 --- /dev/null +++ b/cli/command/engine/activate_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package engine + +var ( + isRoot = func() bool { + return true + } +) diff --git a/cli/command/engine/auth.go b/cli/command/engine/auth.go index fdf04594a8..fee3e7b2c5 100644 --- a/cli/command/engine/auth.go +++ b/cli/command/engine/auth.go @@ -5,6 +5,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/trust" + clitypes "github.com/docker/cli/types" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" registrytypes "github.com/docker/docker/api/types/registry" @@ -13,7 +14,7 @@ import ( func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) { if registryPrefix == "" { - registryPrefix = "docker.io/docker" + registryPrefix = clitypes.RegistryPrefix } distributionRef, err := reference.ParseNormalizedNamed(registryPrefix) if err != nil { diff --git a/cli/command/engine/check.go b/cli/command/engine/check.go index 0ba19cc0e6..587d46adb9 100644 --- a/cli/command/engine/check.go +++ b/cli/command/engine/check.go @@ -7,15 +7,12 @@ import ( "github.com/docker/cli/cli" "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" "github.com/pkg/errors" "github.com/spf13/cobra" ) -const ( - releaseNotePrefix = "https://docs.docker.com/releasenotes" -) - type checkOptions struct { registryPrefix string preReleases bool @@ -38,7 +35,7 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command { }, } flags := cmd.Flags() - flags.StringVar(&options.registryPrefix, "registry-prefix", "", "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.preReleases, "pre-releases", false, "Include pre-release versions") flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades") @@ -50,54 +47,47 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command { } func runCheck(dockerCli command.Cli, options checkOptions) error { - ctx := context.Background() - client, err := dockerCli.NewContainerizedEngineClient(options.sockPath) - if err != nil { - return errors.Wrap(err, "unable to access local containerd") + if !isRoot() { + return errors.New("this command must be run as a privileged user") } - defer client.Close() - currentOpts, err := client.GetCurrentEngineVersion(ctx) + ctx := context.Background() + client := dockerCli.Client() + serverVersion, err := client.ServerVersion(ctx) if err != nil { return err } - // override with user provided prefix if specified - if options.registryPrefix != "" { - currentOpts.RegistryPrefix = options.registryPrefix - } - imageName := currentOpts.RegistryPrefix + "/" + currentOpts.EngineImage - currentVersion := currentOpts.EngineVersion - versions, err := client.GetEngineVersions(ctx, dockerCli.RegistryClient(false), currentVersion, imageName) + availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, serverVersion) if err != nil { return err } 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, processVersions( - currentVersion, + serverVersion.Version, "patch", options.preReleases, - versions.Patches)...) + availVersions.Patches)...) } if options.upgrades { availUpdates = append(availUpdates, processVersions( - currentVersion, + serverVersion.Version, "upgrade", options.preReleases, - versions.Upgrades)...) + availVersions.Upgrades)...) } if options.downgrades { availUpdates = append(availUpdates, processVersions( - currentVersion, + serverVersion.Version, "downgrade", options.preReleases, - versions.Downgrades)...) + availVersions.Downgrades)...) } format := options.format @@ -115,9 +105,9 @@ func runCheck(dockerCli command.Cli, options checkOptions) error { func processVersions(currentVersion, verType string, includePrerelease bool, - versions []clitypes.DockerVersion) []clitypes.Update { + availVersions []clitypes.DockerVersion) []clitypes.Update { availUpdates := []clitypes.Update{} - for _, ver := range versions { + for _, ver := range availVersions { if !includePrerelease && ver.Prerelease() != "" { continue } @@ -125,7 +115,7 @@ func processVersions(currentVersion, verType string, availUpdates = append(availUpdates, clitypes.Update{ Type: verType, Version: ver.Tag, - Notes: fmt.Sprintf("%s/%s", releaseNotePrefix, ver.Tag), + Notes: fmt.Sprintf("%s/%s", clitypes.ReleaseNotePrefix, ver.Tag), }) } } diff --git a/cli/command/engine/check_test.go b/cli/command/engine/check_test.go index 14c6eddb27..89450e67f9 100644 --- a/cli/command/engine/check_test.go +++ b/cli/command/engine/check_test.go @@ -5,11 +5,13 @@ import ( "fmt" "testing" - registryclient "github.com/docker/cli/cli/registry/client" + manifesttypes "github.com/docker/cli/cli/manifest/types" "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" - ver "github.com/hashicorp/go-version" + "github.com/opencontainers/go-digest" "gotest.tools/assert" "gotest.tools/golden" ) @@ -18,126 +20,87 @@ var ( testCli = test.NewFakeCli(&client.Client{}) ) -func TestCheckForUpdatesNoContainerd(t *testing.T) { - testCli.SetContainerizedEngineClient( - func(string) (clitypes.ContainerizedClient, error) { - return nil, fmt.Errorf("some error") - }, - ) - cmd := newCheckForUpdatesCommand(testCli) - cmd.SilenceUsage = true - cmd.SilenceErrors = true - err := cmd.Execute() - assert.ErrorContains(t, err, "unable to access local containerd") +type verClient struct { + client.Client + ver types.Version + verErr error +} + +func (c *verClient) ServerVersion(ctx context.Context) (types.Version, error) { + return c.ver, c.verErr +} + +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) { - retErr := fmt.Errorf("some failure") - getCurrentEngineVersionFunc := func(ctx context.Context) (clitypes.EngineInitOptions, error) { - return clitypes.EngineInitOptions{}, retErr - } - testCli.SetContainerizedEngineClient( - func(string) (clitypes.ContainerizedClient, error) { - return &fakeContainerizedEngineClient{ - getCurrentEngineVersionFunc: getCurrentEngineVersionFunc, - }, nil - }, - ) - cmd := newCheckForUpdatesCommand(testCli) + isRoot = func() bool { return true } + c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil}) + c.SetRegistryClient(testRegistryClient{}) + cmd := newCheckForUpdatesCommand(c) cmd.SilenceUsage = true cmd.SilenceErrors = true err := cmd.Execute() - assert.Assert(t, err == retErr) -} - -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) + assert.ErrorContains(t, err, "alformed version") } func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) { - getCurrentEngineVersionFunc := func(ctx context.Context) (clitypes.EngineInitOptions, error) { - return clitypes.EngineInitOptions{ - EngineImage: "current engine", - EngineVersion: "1.1.0", - }, nil - } - getEngineVersionsFunc := func(ctx context.Context, - registryClient registryclient.RegistryClient, - 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) + c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil}) + c.SetRegistryClient(testRegistryClient{[]string{ + "1.0.1", "1.0.2", "1.0.3-beta1", + "1.1.1", "1.1.2", "1.1.3-beta1", + "1.2.0", "2.0.0", "2.1.0-beta1", + }}) + isRoot = func() bool { return true } + cmd := newCheckForUpdatesCommand(c) cmd.Flags().Set("pre-releases", "true") cmd.Flags().Set("downgrades", "true") + cmd.SilenceUsage = true + cmd.SilenceErrors = true err := cmd.Execute() 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("downgrades", "true") err = cmd.Execute() assert.NilError(t, err) - fmt.Println(testCli.OutBuffer().String()) - golden.Assert(t, testCli.OutBuffer().String(), "check-no-prerelease.golden") + fmt.Println(c.OutBuffer().String()) + 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("downgrades", "false") err = cmd.Execute() assert.NilError(t, err) - fmt.Println(testCli.OutBuffer().String()) - golden.Assert(t, testCli.OutBuffer().String(), "check-no-downgrades.golden") + fmt.Println(c.OutBuffer().String()) + 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("downgrades", "false") cmd.Flags().Set("upgrades", "false") err = cmd.Execute() assert.NilError(t, err) - fmt.Println(testCli.OutBuffer().String()) - golden.Assert(t, testCli.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 + fmt.Println(c.OutBuffer().String()) + golden.Assert(t, c.OutBuffer().String(), "check-patches-only.golden") } diff --git a/cli/command/engine/cmd.go b/cli/command/engine/cmd.go index 7c4ab76b70..66e56631f0 100644 --- a/cli/command/engine/cmd.go +++ b/cli/command/engine/cmd.go @@ -15,11 +15,9 @@ func NewEngineCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), } cmd.AddCommand( - newInitCommand(dockerCli), newActivateCommand(dockerCli), newCheckForUpdatesCommand(dockerCli), newUpdateCommand(dockerCli), - newRmCommand(dockerCli), ) return cmd } diff --git a/cli/command/engine/cmd_test.go b/cli/command/engine/cmd_test.go index 9378f0aa63..30639cbf3c 100644 --- a/cli/command/engine/cmd_test.go +++ b/cli/command/engine/cmd_test.go @@ -10,5 +10,5 @@ func TestNewEngineCommand(t *testing.T) { cmd := NewEngineCommand(testCli) subcommands := cmd.Commands() - assert.Assert(t, len(subcommands) == 5) + assert.Assert(t, len(subcommands) == 3) } diff --git a/cli/command/engine/init.go b/cli/command/engine/init.go index 9d1ff3e34e..f29001d086 100644 --- a/cli/command/engine/init.go +++ b/cli/command/engine/init.go @@ -1,62 +1,10 @@ package engine import ( - "context" - - "github.com/docker/cli/cli" - "github.com/docker/cli/cli/command" clitypes "github.com/docker/cli/types" - "github.com/pkg/errors" - "github.com/spf13/cobra" ) type extendedEngineInitOptions struct { clitypes.EngineInitOptions 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 - }) -} diff --git a/cli/command/engine/init_test.go b/cli/command/engine/init_test.go deleted file mode 100644 index 5d6c91b1f4..0000000000 --- a/cli/command/engine/init_test.go +++ /dev/null @@ -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) -} diff --git a/cli/command/engine/rm.go b/cli/command/engine/rm.go deleted file mode 100644 index c27e9d2136..0000000000 --- a/cli/command/engine/rm.go +++ /dev/null @@ -1,49 +0,0 @@ -package engine - -import ( - "context" - - "github.com/docker/cli/cli" - "github.com/docker/cli/cli/command" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -// TODO - consider adding a "purge" flag that also removes -// configuration files and the docker root dir. - -type rmOptions struct { - sockPath string -} - -func newRmCommand(dockerCli command.Cli) *cobra.Command { - var options rmOptions - cmd := &cobra.Command{ - Use: "rm [OPTIONS]", - Short: "Remove the local engine", - Long: `This command will remove the local engine running on containerd. - -No state files will be removed from the host filesystem. -`, - Args: cli.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return runRm(dockerCli, options) - }, - Annotations: map[string]string{"experimentalCLI": ""}, - } - flags := cmd.Flags() - flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint") - - return cmd -} - -func runRm(dockerCli command.Cli, options rmOptions) 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() - - return client.RemoveEngine(ctx) -} diff --git a/cli/command/engine/rm_test.go b/cli/command/engine/rm_test.go deleted file mode 100644 index 25d3d290f4..0000000000 --- a/cli/command/engine/rm_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package engine - -import ( - "fmt" - "testing" - - clitypes "github.com/docker/cli/types" - "gotest.tools/assert" -) - -func TestRmNoContainerd(t *testing.T) { - testCli.SetContainerizedEngineClient( - func(string) (clitypes.ContainerizedClient, error) { - return nil, fmt.Errorf("some error") - }, - ) - cmd := newRmCommand(testCli) - cmd.SilenceUsage = true - cmd.SilenceErrors = true - err := cmd.Execute() - assert.ErrorContains(t, err, "unable to access local containerd") -} - -func TestRmHappy(t *testing.T) { - testCli.SetContainerizedEngineClient( - func(string) (clitypes.ContainerizedClient, error) { - return &fakeContainerizedEngineClient{}, nil - }, - ) - cmd := newRmCommand(testCli) - err := cmd.Execute() - assert.NilError(t, err) -} diff --git a/cli/command/engine/update.go b/cli/command/engine/update.go index ad42f323ef..041ef0d763 100644 --- a/cli/command/engine/update.go +++ b/cli/command/engine/update.go @@ -6,6 +6,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + clitypes "github.com/docker/cli/types" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -25,31 +26,22 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version") flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image") - flags.StringVar(&options.RegistryPrefix, "registry-prefix", "", "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") return cmd } func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error { + if !isRoot() { + return errors.New("this command must be run as a privileged user") + } ctx := context.Background() client, err := dockerCli.NewContainerizedEngineClient(options.sockPath) if err != nil { return errors.Wrap(err, "unable to access local containerd") } defer client.Close() - if options.EngineImage == "" || options.RegistryPrefix == "" { - currentOpts, err := client.GetCurrentEngineVersion(ctx) - if err != nil { - return err - } - if options.EngineImage == "" { - options.EngineImage = currentOpts.EngineImage - } - if options.RegistryPrefix == "" { - options.RegistryPrefix = currentOpts.RegistryPrefix - } - } authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix) if err != nil { return err @@ -63,6 +55,7 @@ func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error { }); err != nil { return err } - fmt.Fprintln(dockerCli.Out(), "Success! The docker engine is now running.") + fmt.Fprintln(dockerCli.Out(), `Successfully updated engine. +Restart docker with 'systemctl restart docker' to complete the update.`) return nil } diff --git a/cli/command/engine/update_test.go b/cli/command/engine/update_test.go index a609113296..99dcbdc8ef 100644 --- a/cli/command/engine/update_test.go +++ b/cli/command/engine/update_test.go @@ -28,7 +28,7 @@ func TestUpdateHappy(t *testing.T) { }, ) cmd := newUpdateCommand(testCli) - cmd.Flags().Set("registry-prefix", "docker.io/docker") + cmd.Flags().Set("registry-prefix", clitypes.RegistryPrefix) cmd.Flags().Set("version", "someversion") err := cmd.Execute() assert.NilError(t, err) diff --git a/docker.Makefile b/docker.Makefile index 4955be6088..69135e97a6 100644 --- a/docker.Makefile +++ b/docker.Makefile @@ -105,7 +105,7 @@ shellcheck: build_shell_validate_image ## run shellcheck validation docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck .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 test-e2e-experimental: build_e2e_image @@ -115,14 +115,6 @@ test-e2e-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) -.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 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) diff --git a/dockerfiles/Dockerfile.e2e b/dockerfiles/Dockerfile.e2e index 581f505e24..bed83e0e18 100644 --- a/dockerfiles/Dockerfile.e2e +++ b/dockerfiles/Dockerfile.e2e @@ -15,28 +15,6 @@ RUN apt-get update && apt-get install -y \ iptables \ && rm -rf /var/lib/apt/lists/* -# TODO - consider replacing with an official image and a multi-stage build to pluck the binaries out -#ARG CONTAINERD_VERSION=v1.1.2 -#ARG CONTAINERD_VERSION=47a128d -#ARG CONTAINERD_VERSION=6c3e782f -ARG CONTAINERD_VERSION=65839a47a88b0a1c5dc34981f1741eccefc9f2b0 -RUN git clone https://github.com/containerd/containerd.git /go/src/github.com/containerd/containerd && \ - cd /go/src/github.com/containerd/containerd && \ - git checkout ${CONTAINERD_VERSION} && \ - make && \ - make install -COPY e2eengine/config.toml /etc/containerd/config.toml -COPY --from=containerd-shim-process /bin/containerd-shim-process-v1 /bin/ - - -# TODO - consider replacing with an official image and a multi-stage build to pluck the binaries out -ARG RUNC_VERSION=v1.0.0-rc5 -RUN git clone https://github.com/opencontainers/runc.git /go/src/github.com/opencontainers/runc && \ - cd /go/src/github.com/opencontainers/runc && \ - git checkout ${RUNC_VERSION} && \ - make && \ - make install - ARG COMPOSE_VERSION=1.21.2 RUN curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose \ && chmod +x /usr/local/bin/docker-compose diff --git a/e2eengine/altroot/altroot_test.go b/e2eengine/altroot/altroot_test.go deleted file mode 100644 index 0ea803e284..0000000000 --- a/e2eengine/altroot/altroot_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package check - -import ( - "testing" - - "github.com/docker/cli/e2eengine" - - "gotest.tools/icmd" -) - -func TestDockerEngineOnContainerdAltRootConfig(t *testing.T) { - defer func() { - err := e2eengine.CleanupEngine(t) - if err != nil { - t.Errorf("Failed to cleanup engine: %s", err) - } - }() - - // Use a fixed version to prevent failures when development of the next version starts, and no image is available yet. - targetVersion := "18.09.0-dev" - - t.Log("First engine init") - // First init - result := icmd.RunCmd(icmd.Command("docker", "engine", "init", "--config-file", "/tmp/etc/docker/daemon.json", "--version", targetVersion), - func(c *icmd.Cmd) { - c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled") - }) - result.Assert(t, icmd.Expected{ - Out: "Success! The docker engine is now running.", - Err: "", - ExitCode: 0, - }) - - // Make sure update doesn't blow up with alternate config path - t.Log("perform update") - // Now update and succeed - result = icmd.RunCmd(icmd.Command("docker", "engine", "update", "--version", targetVersion)) - result.Assert(t, icmd.Expected{ - Out: "Success! The docker engine is now running.", - Err: "", - ExitCode: 0, - }) -} diff --git a/e2eengine/config.toml b/e2eengine/config.toml deleted file mode 100644 index 4713f87a28..0000000000 --- a/e2eengine/config.toml +++ /dev/null @@ -1,14 +0,0 @@ -root = "/var/lib/containerd" -state = "/run/containerd" -oom_score = 0 - -[grpc] - address = "/run/containerd/containerd.sock" - uid = 0 - gid = 0 - -[debug] - address = "/run/containerd/debug.sock" - uid = 0 - gid = 0 - level = "debug" diff --git a/e2eengine/multi/multi_test.go b/e2eengine/multi/multi_test.go deleted file mode 100644 index b7994c8019..0000000000 --- a/e2eengine/multi/multi_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package multi - -import ( - "testing" - - "github.com/docker/cli/e2eengine" - - "gotest.tools/icmd" -) - -func TestDockerEngineOnContainerdMultiTest(t *testing.T) { - defer func() { - err := e2eengine.CleanupEngine(t) - if err != nil { - t.Errorf("Failed to cleanup engine: %s", err) - } - }() - - // Use a fixed version to prevent failures when development of the next version starts, and no image is available yet. - targetVersion := "18.09.0-dev" - - t.Log("Attempt engine init without experimental") - // First init - result := icmd.RunCmd(icmd.Command("docker", "engine", "init", "--version", targetVersion), - func(c *icmd.Cmd) { - c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=disabled") - }) - result.Assert(t, icmd.Expected{ - Out: "", - Err: "docker engine init is only supported", - ExitCode: 1, - }) - - t.Log("First engine init") - // First init - result = icmd.RunCmd(icmd.Command("docker", "engine", "init", "--version", targetVersion), - func(c *icmd.Cmd) { - c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled") - }) - result.Assert(t, icmd.Expected{ - Out: "Success! The docker engine is now running.", - Err: "", - ExitCode: 0, - }) - - t.Log("checking for updates") - // Check for updates - result = icmd.RunCmd(icmd.Command("docker", "engine", "check", "--downgrades", "--pre-releases")) - result.Assert(t, icmd.Expected{ - Out: "VERSION", - Err: "", - ExitCode: 0, - }) - - t.Log("attempt second init (should fail)") - // Attempt to init a second time and fail - result = icmd.RunCmd(icmd.Command("docker", "engine", "init"), - func(c *icmd.Cmd) { - c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled") - }) - result.Assert(t, icmd.Expected{ - Out: "", - Err: "engine already present", - ExitCode: 1, - }) - - t.Log("perform update") - // Now update and succeed - result = icmd.RunCmd(icmd.Command("docker", "engine", "update", "--version", targetVersion)) - result.Assert(t, icmd.Expected{ - Out: "Success! The docker engine is now running.", - Err: "", - ExitCode: 0, - }) - - t.Log("remove engine") - result = icmd.RunCmd(icmd.Command("docker", "engine", "rm"), - func(c *icmd.Cmd) { - c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled") - }) - result.Assert(t, icmd.Expected{ - Out: "", - Err: "", - ExitCode: 0, - }) -} diff --git a/e2eengine/utils.go b/e2eengine/utils.go deleted file mode 100644 index 3f109f73db..0000000000 --- a/e2eengine/utils.go +++ /dev/null @@ -1,46 +0,0 @@ -package e2eengine - -import ( - "context" - "strings" - "testing" - - "github.com/containerd/containerd" - "github.com/docker/cli/internal/containerizedengine" - "github.com/docker/cli/types" -) - -type containerizedclient interface { - types.ContainerizedClient - GetEngine(context.Context) (containerd.Container, error) -} - -// CleanupEngine ensures the local engine has been removed between testcases -func CleanupEngine(t *testing.T) error { - t.Log("doing engine cleanup") - ctx := context.Background() - - client, err := containerizedengine.NewClient("") - if err != nil { - return err - } - - // See if the engine exists first - _, err = client.(containerizedclient).GetEngine(ctx) - if err != nil { - if strings.Contains(err.Error(), "not present") { - t.Log("engine was not detected, no cleanup to perform") - // Nothing to do, it's not defined - return nil - } - t.Logf("failed to lookup engine: %s", err) - // Any other error is not good... - return err - } - // TODO Consider nuking the docker dir too so there's no cached content between test cases - err = client.RemoveEngine(ctx) - if err != nil { - t.Logf("Failed to remove engine: %s", err) - } - return err -} diff --git a/internal/containerizedengine/client_test.go b/internal/containerizedengine/client_test.go index cc5c5770b2..bb3fae6a5f 100644 --- a/internal/containerizedengine/client_test.go +++ b/internal/containerizedengine/client_test.go @@ -2,10 +2,8 @@ package containerizedengine import ( "context" - "syscall" "github.com/containerd/containerd" - containerdtypes "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/cio" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" @@ -13,7 +11,6 @@ import ( prototypes "github.com/gogo/protobuf/types" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/opencontainers/runtime-spec/specs-go" ) type ( @@ -24,6 +21,8 @@ type ( getImageFunc func(ctx context.Context, ref string) (containerd.Image, error) contentStoreFunc func() content.Store containerServiceFunc func() containers.Store + installFunc func(context.Context, containerd.Image, ...containerd.InstallOpts) error + versionFunc func(ctx context.Context) (containerd.Version, error) } fakeContainer struct { idFunc func() string @@ -48,26 +47,6 @@ type ( isUnpackedFunc func(context.Context, string) (bool, error) contentStoreFunc func() content.Store } - fakeTask struct { - idFunc func() string - pidFunc func() uint32 - startFunc func(context.Context) error - deleteFunc func(context.Context, ...containerd.ProcessDeleteOpts) (*containerd.ExitStatus, error) - killFunc func(context.Context, syscall.Signal, ...containerd.KillOpts) error - waitFunc func(context.Context) (<-chan containerd.ExitStatus, error) - closeIOFunc func(context.Context, ...containerd.IOCloserOpts) error - resizeFunc func(ctx context.Context, w, h uint32) error - ioFunc func() cio.IO - statusFunc func(context.Context) (containerd.Status, error) - pauseFunc func(context.Context) error - resumeFunc func(context.Context) error - execFunc func(context.Context, string, *specs.Process, cio.Creator) (containerd.Process, error) - pidsFunc func(context.Context) ([]containerd.ProcessInfo, error) - checkpointFunc func(context.Context, ...containerd.CheckpointTaskOpts) (containerd.Image, error) - updateFunc func(context.Context, ...containerd.UpdateTaskOpts) error - loadProcessFunc func(context.Context, string, cio.Attach) (containerd.Process, error) - metricsFunc func(context.Context) (*containerdtypes.Metric, error) - } ) func (w *fakeContainerdClient) Containers(ctx context.Context, filters ...string) ([]containerd.Container, error) { @@ -109,6 +88,18 @@ func (w *fakeContainerdClient) ContainerService() containers.Store { func (w *fakeContainerdClient) Close() error { 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 { if c.idFunc != nil { @@ -225,112 +216,3 @@ func (i *fakeImage) ContentStore() content.Store { } return nil } - -func (t *fakeTask) ID() string { - if t.idFunc != nil { - return t.idFunc() - } - return "" -} -func (t *fakeTask) Pid() uint32 { - if t.pidFunc != nil { - return t.pidFunc() - } - return 0 -} -func (t *fakeTask) Start(ctx context.Context) error { - if t.startFunc != nil { - return t.startFunc(ctx) - } - return nil -} -func (t *fakeTask) Delete(ctx context.Context, opts ...containerd.ProcessDeleteOpts) (*containerd.ExitStatus, error) { - if t.deleteFunc != nil { - return t.deleteFunc(ctx, opts...) - } - return nil, nil -} -func (t *fakeTask) Kill(ctx context.Context, signal syscall.Signal, opts ...containerd.KillOpts) error { - if t.killFunc != nil { - return t.killFunc(ctx, signal, opts...) - } - return nil -} -func (t *fakeTask) Wait(ctx context.Context) (<-chan containerd.ExitStatus, error) { - if t.waitFunc != nil { - return t.waitFunc(ctx) - } - return nil, nil -} -func (t *fakeTask) CloseIO(ctx context.Context, opts ...containerd.IOCloserOpts) error { - if t.closeIOFunc != nil { - return t.closeIOFunc(ctx, opts...) - } - return nil -} -func (t *fakeTask) Resize(ctx context.Context, w, h uint32) error { - if t.resizeFunc != nil { - return t.resizeFunc(ctx, w, h) - } - return nil -} -func (t *fakeTask) IO() cio.IO { - if t.ioFunc != nil { - return t.ioFunc() - } - return nil -} -func (t *fakeTask) Status(ctx context.Context) (containerd.Status, error) { - if t.statusFunc != nil { - return t.statusFunc(ctx) - } - return containerd.Status{}, nil -} -func (t *fakeTask) Pause(ctx context.Context) error { - if t.pauseFunc != nil { - return t.pauseFunc(ctx) - } - return nil -} -func (t *fakeTask) Resume(ctx context.Context) error { - if t.resumeFunc != nil { - return t.resumeFunc(ctx) - } - return nil -} -func (t *fakeTask) Exec(ctx context.Context, cmd string, proc *specs.Process, ioc cio.Creator) (containerd.Process, error) { - if t.execFunc != nil { - return t.execFunc(ctx, cmd, proc, ioc) - } - return nil, nil -} -func (t *fakeTask) Pids(ctx context.Context) ([]containerd.ProcessInfo, error) { - if t.pidsFunc != nil { - return t.pidsFunc(ctx) - } - return nil, nil -} -func (t *fakeTask) Checkpoint(ctx context.Context, opts ...containerd.CheckpointTaskOpts) (containerd.Image, error) { - if t.checkpointFunc != nil { - return t.checkpointFunc(ctx, opts...) - } - return nil, nil -} -func (t *fakeTask) Update(ctx context.Context, opts ...containerd.UpdateTaskOpts) error { - if t.updateFunc != nil { - return t.updateFunc(ctx, opts...) - } - return nil -} -func (t *fakeTask) LoadProcess(ctx context.Context, name string, attach cio.Attach) (containerd.Process, error) { - if t.loadProcessFunc != nil { - return t.loadProcessFunc(ctx, name, attach) - } - return nil, nil -} -func (t *fakeTask) Metrics(ctx context.Context) (*containerdtypes.Metric, error) { - if t.metricsFunc != nil { - return t.metricsFunc(ctx) - } - return nil, nil -} diff --git a/internal/containerizedengine/engine.go b/internal/containerizedengine/engine.go deleted file mode 100644 index 8356fc3fc5..0000000000 --- a/internal/containerizedengine/engine.go +++ /dev/null @@ -1,242 +0,0 @@ -package containerizedengine - -import ( - "context" - "fmt" - "io" - "syscall" - "time" - - "github.com/containerd/containerd" - "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/namespaces" - "github.com/containerd/containerd/runtime/restart" - "github.com/docker/cli/internal/pkg/containerized" - clitypes "github.com/docker/cli/types" - "github.com/docker/docker/api/types" - "github.com/pkg/errors" -) - -var _ clitypes.ContainerizedClient = &baseClient{} - -// InitEngine is the main entrypoint for `docker engine init` -func (c *baseClient) InitEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream, - authConfig *types.AuthConfig, healthfn func(context.Context) error) error { - - ctx = namespaces.WithNamespace(ctx, engineNamespace) - // Verify engine isn't already running - _, err := c.GetEngine(ctx) - if err == nil { - return ErrEngineAlreadyPresent - } else if err != ErrEngineNotPresent { - return err - } - - imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion) - // Look for desired image - _, err = c.cclient.GetImage(ctx, imageName) - if err != nil { - if errdefs.IsNotFound(err) { - _, err = c.pullWithAuth(ctx, imageName, out, authConfig) - if err != nil { - return errors.Wrapf(err, "unable to pull image %s", imageName) - } - } else { - return errors.Wrapf(err, "unable to check for image %s", imageName) - } - } - - // Spin up the engine - err = c.startEngineOnContainerd(ctx, imageName, opts.ConfigFile) - if err != nil { - return errors.Wrap(err, "failed to create docker daemon") - } - - // Wait for the daemon to start, verify it's responsive - fmt.Fprintf(out, "Waiting for engine to start... ") - ctx, cancel := context.WithTimeout(ctx, engineWaitTimeout) - defer cancel() - if err := c.waitForEngine(ctx, out, healthfn); err != nil { - // TODO once we have the logging strategy sorted out - // this should likely gather the last few lines of logs to report - // why the daemon failed to initialize - return errors.Wrap(err, "failed to start docker daemon") - } - fmt.Fprintf(out, "Success! The docker engine is now running.\n") - - return nil - -} - -// GetEngine will return the containerd container running the engine (or error) -func (c *baseClient) GetEngine(ctx context.Context) (containerd.Container, error) { - ctx = namespaces.WithNamespace(ctx, engineNamespace) - containers, err := c.cclient.Containers(ctx, "id=="+engineContainerName) - if err != nil { - return nil, err - } - if len(containers) == 0 { - return nil, ErrEngineNotPresent - } - return containers[0], nil -} - -// getEngineImage will return the current image used by the engine -func (c *baseClient) getEngineImage(engine containerd.Container) (string, error) { - ctx := namespaces.WithNamespace(context.Background(), engineNamespace) - image, err := engine.Image(ctx) - if err != nil { - return "", err - } - return image.Name(), nil -} - -var ( - engineWaitInterval = 500 * time.Millisecond - engineWaitTimeout = 60 * time.Second -) - -// waitForEngine will wait for the engine to start -func (c *baseClient) waitForEngine(ctx context.Context, out io.Writer, healthfn func(context.Context) error) error { - ticker := time.NewTicker(engineWaitInterval) - defer ticker.Stop() - defer func() { - fmt.Fprintf(out, "\n") - }() - - err := c.waitForEngineContainer(ctx, ticker) - if err != nil { - return err - } - fmt.Fprintf(out, "waiting for engine to be responsive... ") - for { - select { - case <-ticker.C: - err = healthfn(ctx) - if err == nil { - fmt.Fprintf(out, "engine is online.") - return nil - } - case <-ctx.Done(): - return errors.Wrap(err, "timeout waiting for engine to be responsive") - } - } -} - -func (c *baseClient) waitForEngineContainer(ctx context.Context, ticker *time.Ticker) error { - var ret error - for { - select { - case <-ticker.C: - engine, err := c.GetEngine(ctx) - if engine != nil { - return nil - } - ret = err - case <-ctx.Done(): - return errors.Wrap(ret, "timeout waiting for engine to be responsive") - } - } -} - -// RemoveEngine gracefully unwinds the current engine -func (c *baseClient) RemoveEngine(ctx context.Context) error { - engine, err := c.GetEngine(ctx) - if err != nil { - return err - } - return c.removeEngine(ctx, engine) -} - -func (c *baseClient) removeEngine(ctx context.Context, engine containerd.Container) error { - ctx = namespaces.WithNamespace(ctx, engineNamespace) - - // Make sure the container isn't being restarted while we unwind it - stopLabel := map[string]string{} - stopLabel[restart.StatusLabel] = string(containerd.Stopped) - engine.SetLabels(ctx, stopLabel) - - // Wind down the existing engine - task, err := engine.Task(ctx, nil) - if err != nil { - if !errdefs.IsNotFound(err) { - return err - } - } else { - status, err := task.Status(ctx) - if err != nil { - return err - } - if status.Status == containerd.Running { - // It's running, so kill it - err := task.Kill(ctx, syscall.SIGTERM, []containerd.KillOpts{}...) - if err != nil { - return errors.Wrap(err, "task kill error") - } - - ch, err := task.Wait(ctx) - if err != nil { - return err - } - timeout := time.NewTimer(engineWaitTimeout) - select { - case <-timeout.C: - // TODO - consider a force flag in the future to allow a more aggressive - // kill of the engine via - // task.Kill(ctx, syscall.SIGKILL, containerd.WithKillAll) - return ErrEngineShutdownTimeout - case <-ch: - } - } - if _, err := task.Delete(ctx); err != nil { - return err - } - } - deleteOpts := []containerd.DeleteOpts{containerd.WithSnapshotCleanup} - err = engine.Delete(ctx, deleteOpts...) - if err != nil && errdefs.IsNotFound(err) { - return nil - } - return errors.Wrap(err, "failed to remove existing engine container") -} - -// startEngineOnContainerd creates a new docker engine running on containerd -func (c *baseClient) startEngineOnContainerd(ctx context.Context, imageName, configFile string) error { - ctx = namespaces.WithNamespace(ctx, engineNamespace) - image, err := c.cclient.GetImage(ctx, imageName) - if err != nil { - if errdefs.IsNotFound(err) { - return fmt.Errorf("engine image missing: %s", imageName) - } - return errors.Wrap(err, "failed to check for engine image") - } - - // Make sure we have a valid config file - err = c.verifyDockerConfig(configFile) - if err != nil { - return err - } - - engineSpec.Process.Args = append(engineSpec.Process.Args, - "--config-file", configFile, - ) - - cOpts := []containerd.NewContainerOpts{ - containerized.WithNewSnapshot(image), - restart.WithStatus(containerd.Running), - restart.WithLogPath("/var/log/engine.log"), // TODO - better! - genSpec(), - containerd.WithRuntime("io.containerd.runtime.process.v1", nil), - } - - _, err = c.cclient.NewContainer( - ctx, - engineContainerName, - cOpts..., - ) - if err != nil { - return errors.Wrap(err, "failed to create engine container") - } - - return nil -} diff --git a/internal/containerizedengine/engine_test.go b/internal/containerizedengine/engine_test.go deleted file mode 100644 index 21ab83d68a..0000000000 --- a/internal/containerizedengine/engine_test.go +++ /dev/null @@ -1,570 +0,0 @@ -package containerizedengine - -import ( - "bytes" - "context" - "fmt" - "strings" - "syscall" - "testing" - "time" - - "github.com/containerd/containerd" - "github.com/containerd/containerd/cio" - "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/oci" - "github.com/docker/cli/cli/command" - clitypes "github.com/docker/cli/types" - "github.com/docker/docker/api/types" - "github.com/opencontainers/runtime-spec/specs-go" - "gotest.tools/assert" -) - -func healthfnHappy(ctx context.Context) error { - return nil -} -func healthfnError(ctx context.Context) error { - return fmt.Errorf("ping failure") -} - -func TestInitGetEngineFail(t *testing.T) { - ctx := context.Background() - opts := clitypes.EngineInitOptions{ - EngineVersion: "engineversiongoeshere", - RegistryPrefix: "registryprefixgoeshere", - ConfigFile: "/tmp/configfilegoeshere", - EngineImage: clitypes.CommunityEngineImage, - } - container := &fakeContainer{} - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{container}, nil - }, - }, - } - - err := client.InitEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) - assert.Assert(t, err == ErrEngineAlreadyPresent) -} - -func TestInitCheckImageFail(t *testing.T) { - ctx := context.Background() - opts := clitypes.EngineInitOptions{ - EngineVersion: "engineversiongoeshere", - RegistryPrefix: "registryprefixgoeshere", - ConfigFile: "/tmp/configfilegoeshere", - EngineImage: clitypes.CommunityEngineImage, - } - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{}, nil - }, - getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) { - return nil, fmt.Errorf("something went wrong") - - }, - }, - } - - err := client.InitEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) - assert.ErrorContains(t, err, "unable to check for image") - assert.ErrorContains(t, err, "something went wrong") -} - -func TestInitPullFail(t *testing.T) { - ctx := context.Background() - opts := clitypes.EngineInitOptions{ - EngineVersion: "engineversiongoeshere", - RegistryPrefix: "registryprefixgoeshere", - ConfigFile: "/tmp/configfilegoeshere", - EngineImage: clitypes.CommunityEngineImage, - } - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{}, nil - }, - getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) { - return nil, errdefs.ErrNotFound - - }, - pullFunc: func(ctx context.Context, ref string, opts ...containerd.RemoteOpt) (containerd.Image, error) { - return nil, fmt.Errorf("pull failure") - }, - }, - } - - err := client.InitEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) - assert.ErrorContains(t, err, "unable to pull image") - assert.ErrorContains(t, err, "pull failure") -} - -func TestInitStartFail(t *testing.T) { - ctx := context.Background() - opts := clitypes.EngineInitOptions{ - EngineVersion: "engineversiongoeshere", - RegistryPrefix: "registryprefixgoeshere", - ConfigFile: "/tmp/configfilegoeshere", - EngineImage: clitypes.CommunityEngineImage, - } - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{}, nil - }, - getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) { - return nil, errdefs.ErrNotFound - - }, - pullFunc: func(ctx context.Context, ref string, opts ...containerd.RemoteOpt) (containerd.Image, error) { - return nil, nil - }, - }, - } - - err := client.InitEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) - assert.ErrorContains(t, err, "failed to create docker daemon") -} - -func TestGetEngineFail(t *testing.T) { - ctx := context.Background() - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return nil, fmt.Errorf("container failure") - }, - }, - } - - _, err := client.GetEngine(ctx) - assert.ErrorContains(t, err, "failure") -} - -func TestGetEngineNotPresent(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.GetEngine(ctx) - assert.Assert(t, err == ErrEngineNotPresent) -} - -func TestGetEngineFound(t *testing.T) { - ctx := context.Background() - container := &fakeContainer{} - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{container}, nil - }, - }, - } - - c, err := client.GetEngine(ctx) - assert.NilError(t, err) - assert.Equal(t, c, container) -} - -func TestGetEngineImageFail(t *testing.T) { - client := baseClient{} - container := &fakeContainer{ - imageFunc: func(context.Context) (containerd.Image, error) { - return nil, fmt.Errorf("failure") - }, - } - - _, err := client.getEngineImage(container) - assert.ErrorContains(t, err, "failure") -} - -func TestGetEngineImagePass(t *testing.T) { - client := baseClient{} - image := &fakeImage{ - nameFunc: func() string { - return "imagenamehere" - }, - } - container := &fakeContainer{ - imageFunc: func(context.Context) (containerd.Image, error) { - return image, nil - }, - } - - name, err := client.getEngineImage(container) - assert.NilError(t, err) - assert.Equal(t, name, "imagenamehere") -} - -func TestWaitForEngineNeverShowsUp(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) - defer cancel() - engineWaitInterval = 1 * time.Millisecond - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{}, nil - }, - }, - } - - err := client.waitForEngine(ctx, command.NewOutStream(&bytes.Buffer{}), healthfnError) - assert.ErrorContains(t, err, "timeout waiting") -} - -func TestWaitForEnginePingFail(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) - defer cancel() - engineWaitInterval = 1 * time.Millisecond - container := &fakeContainer{} - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{container}, nil - }, - }, - } - - err := client.waitForEngine(ctx, command.NewOutStream(&bytes.Buffer{}), healthfnError) - assert.ErrorContains(t, err, "ping fail") -} - -func TestWaitForEngineHealthy(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) - defer cancel() - engineWaitInterval = 1 * time.Millisecond - container := &fakeContainer{} - client := baseClient{ - cclient: &fakeContainerdClient{ - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{container}, nil - }, - }, - } - - err := client.waitForEngine(ctx, command.NewOutStream(&bytes.Buffer{}), healthfnHappy) - assert.NilError(t, err) -} - -func TestRemoveEngineBadTaskBadDelete(t *testing.T) { - ctx := context.Background() - client := baseClient{} - container := &fakeContainer{ - deleteFunc: func(context.Context, ...containerd.DeleteOpts) error { - return fmt.Errorf("delete failure") - }, - taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) { - return nil, errdefs.ErrNotFound - }, - } - - err := client.removeEngine(ctx, container) - assert.ErrorContains(t, err, "failed to remove existing engine") - assert.ErrorContains(t, err, "delete failure") -} - -func TestRemoveEngineTaskNoStatus(t *testing.T) { - ctx := context.Background() - client := baseClient{} - task := &fakeTask{ - statusFunc: func(context.Context) (containerd.Status, error) { - return containerd.Status{}, fmt.Errorf("task status failure") - }, - } - container := &fakeContainer{ - taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) { - return task, nil - }, - } - - err := client.removeEngine(ctx, container) - assert.ErrorContains(t, err, "task status failure") -} - -func TestRemoveEngineTaskNotRunningDeleteFail(t *testing.T) { - ctx := context.Background() - client := baseClient{} - task := &fakeTask{ - statusFunc: func(context.Context) (containerd.Status, error) { - return containerd.Status{Status: containerd.Unknown}, nil - }, - deleteFunc: func(context.Context, ...containerd.ProcessDeleteOpts) (*containerd.ExitStatus, error) { - return nil, fmt.Errorf("task delete failure") - }, - } - container := &fakeContainer{ - taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) { - return task, nil - }, - } - - err := client.removeEngine(ctx, container) - assert.ErrorContains(t, err, "task delete failure") -} - -func TestRemoveEngineTaskRunningKillFail(t *testing.T) { - ctx := context.Background() - client := baseClient{} - task := &fakeTask{ - statusFunc: func(context.Context) (containerd.Status, error) { - return containerd.Status{Status: containerd.Running}, nil - }, - killFunc: func(context.Context, syscall.Signal, ...containerd.KillOpts) error { - return fmt.Errorf("task kill failure") - }, - } - container := &fakeContainer{ - taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) { - return task, nil - }, - } - - err := client.removeEngine(ctx, container) - assert.ErrorContains(t, err, "task kill failure") -} - -func TestRemoveEngineTaskRunningWaitFail(t *testing.T) { - ctx := context.Background() - client := baseClient{} - task := &fakeTask{ - statusFunc: func(context.Context) (containerd.Status, error) { - return containerd.Status{Status: containerd.Running}, nil - }, - waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) { - return nil, fmt.Errorf("task wait failure") - }, - } - container := &fakeContainer{ - taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) { - return task, nil - }, - } - - err := client.removeEngine(ctx, container) - assert.ErrorContains(t, err, "task wait failure") -} - -func TestRemoveEngineTaskRunningHappyPath(t *testing.T) { - ctx := context.Background() - client := baseClient{} - ch := make(chan containerd.ExitStatus, 1) - task := &fakeTask{ - statusFunc: func(context.Context) (containerd.Status, error) { - return containerd.Status{Status: containerd.Running}, nil - }, - waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) { - ch <- containerd.ExitStatus{} - return ch, nil - }, - } - container := &fakeContainer{ - taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) { - return task, nil - }, - } - - err := client.removeEngine(ctx, container) - assert.NilError(t, err) -} - -func TestRemoveEngineTaskKillTimeout(t *testing.T) { - ctx := context.Background() - ch := make(chan containerd.ExitStatus, 1) - client := baseClient{} - engineWaitTimeout = 10 * time.Millisecond - task := &fakeTask{ - statusFunc: func(context.Context) (containerd.Status, error) { - return containerd.Status{Status: containerd.Running}, nil - }, - waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) { - //ch <- containerd.ExitStatus{} // let it timeout - return ch, nil - }, - } - container := &fakeContainer{ - taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) { - return task, nil - }, - } - - err := client.removeEngine(ctx, container) - assert.Assert(t, err == ErrEngineShutdownTimeout) -} - -func TestStartEngineOnContainerdImageErr(t *testing.T) { - ctx := context.Background() - imageName := "testnamegoeshere" - configFile := "/tmp/configfilegoeshere" - client := baseClient{ - cclient: &fakeContainerdClient{ - getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) { - return nil, fmt.Errorf("some image lookup failure") - - }, - }, - } - err := client.startEngineOnContainerd(ctx, imageName, configFile) - assert.ErrorContains(t, err, "some image lookup failure") -} - -func TestStartEngineOnContainerdImageNotFound(t *testing.T) { - ctx := context.Background() - imageName := "testnamegoeshere" - configFile := "/tmp/configfilegoeshere" - client := baseClient{ - cclient: &fakeContainerdClient{ - getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) { - return nil, errdefs.ErrNotFound - - }, - }, - } - err := client.startEngineOnContainerd(ctx, imageName, configFile) - assert.ErrorContains(t, err, "engine image missing") -} - -func TestStartEngineOnContainerdHappy(t *testing.T) { - ctx := context.Background() - imageName := "testnamegoeshere" - configFile := "/tmp/configfilegoeshere" - ch := make(chan containerd.ExitStatus, 1) - streams := cio.Streams{} - task := &fakeTask{ - statusFunc: func(context.Context) (containerd.Status, error) { - return containerd.Status{Status: containerd.Running}, nil - }, - waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) { - ch <- containerd.ExitStatus{} - return ch, nil - }, - } - container := &fakeContainer{ - newTaskFunc: func(ctx context.Context, creator cio.Creator, opts ...containerd.NewTaskOpts) (containerd.Task, error) { - if streams.Stdout != nil { - streams.Stdout.Write([]byte("{}")) - } - return task, nil - }, - } - client := baseClient{ - cclient: &fakeContainerdClient{ - getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) { - return nil, nil - - }, - newContainerFunc: func(ctx context.Context, id string, opts ...containerd.NewContainerOpts) (containerd.Container, error) { - return container, nil - }, - }, - } - err := client.startEngineOnContainerd(ctx, imageName, configFile) - assert.NilError(t, err) -} - -func TestGetEngineConfigFilePathBadSpec(t *testing.T) { - ctx := context.Background() - client := baseClient{} - container := &fakeContainer{ - specFunc: func(context.Context) (*oci.Spec, error) { - return nil, fmt.Errorf("spec error") - }, - } - _, err := client.getEngineConfigFilePath(ctx, container) - assert.ErrorContains(t, err, "spec error") -} - -func TestGetEngineConfigFilePathDistinct(t *testing.T) { - ctx := context.Background() - client := baseClient{} - container := &fakeContainer{ - specFunc: func(context.Context) (*oci.Spec, error) { - return &oci.Spec{ - Process: &specs.Process{ - Args: []string{ - "--another-flag", - "foo", - "--config-file", - "configpath", - }, - }, - }, nil - }, - } - configFile, err := client.getEngineConfigFilePath(ctx, container) - assert.NilError(t, err) - assert.Assert(t, err, configFile == "configpath") -} - -func TestGetEngineConfigFilePathEquals(t *testing.T) { - ctx := context.Background() - client := baseClient{} - container := &fakeContainer{ - specFunc: func(context.Context) (*oci.Spec, error) { - return &oci.Spec{ - Process: &specs.Process{ - Args: []string{ - "--another-flag=foo", - "--config-file=configpath", - }, - }, - }, nil - }, - } - configFile, err := client.getEngineConfigFilePath(ctx, container) - assert.NilError(t, err) - assert.Assert(t, err, configFile == "configpath") -} - -func TestGetEngineConfigFilePathMalformed1(t *testing.T) { - ctx := context.Background() - client := baseClient{} - container := &fakeContainer{ - specFunc: func(context.Context) (*oci.Spec, error) { - return &oci.Spec{ - Process: &specs.Process{ - Args: []string{ - "--another-flag", - "--config-file", - }, - }, - }, nil - }, - } - _, err := client.getEngineConfigFilePath(ctx, container) - assert.Assert(t, err == ErrMalformedConfigFileParam) -} - -// getEngineConfigFilePath will extract the config file location from the engine flags -func (c baseClient) getEngineConfigFilePath(ctx context.Context, engine containerd.Container) (string, error) { - spec, err := engine.Spec(ctx) - configFile := "" - if err != nil { - return configFile, err - } - for i := 0; i < len(spec.Process.Args); i++ { - arg := spec.Process.Args[i] - if strings.HasPrefix(arg, "--config-file") { - if strings.Contains(arg, "=") { - split := strings.SplitN(arg, "=", 2) - configFile = split[1] - } else { - if i+1 >= len(spec.Process.Args) { - return configFile, ErrMalformedConfigFileParam - } - configFile = spec.Process.Args[i+1] - } - } - } - - if configFile == "" { - // TODO - any more diagnostics to offer? - return configFile, ErrEngineConfigLookupFailure - } - return configFile, nil -} diff --git a/internal/containerizedengine/engine_unix.go b/internal/containerizedengine/engine_unix.go deleted file mode 100644 index e49581c3ad..0000000000 --- a/internal/containerizedengine/engine_unix.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build !windows - -package containerizedengine - -import ( - "github.com/containerd/containerd" - "github.com/containerd/containerd/oci" - "github.com/docker/cli/internal/pkg/containerized" -) - -func genSpec() containerd.NewContainerOpts { - return containerd.WithSpec(&engineSpec, - containerized.WithAllCapabilities, - oci.WithParentCgroupDevices, - ) -} diff --git a/internal/containerizedengine/engine_windows.go b/internal/containerizedengine/engine_windows.go deleted file mode 100644 index b41e09d098..0000000000 --- a/internal/containerizedengine/engine_windows.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build windows - -package containerizedengine - -import ( - "github.com/containerd/containerd" - "github.com/docker/cli/internal/pkg/containerized" -) - -func genSpec() containerd.NewContainerOpts { - return containerd.WithSpec(&engineSpec, - containerized.WithAllCapabilities, - ) -} diff --git a/internal/containerizedengine/hostpaths.go b/internal/containerizedengine/hostpaths.go deleted file mode 100644 index df28a69e79..0000000000 --- a/internal/containerizedengine/hostpaths.go +++ /dev/null @@ -1,35 +0,0 @@ -package containerizedengine - -import ( - "os" - "path" -) - -func (c baseClient) verifyDockerConfig(configFile string) error { - - // TODO - in the future consider leveraging containerd and a host runtime - // to create the file. For now, just create it locally since we have to be - // local to talk to containerd - - configDir := path.Dir(configFile) - err := os.MkdirAll(configDir, 0644) - if err != nil { - return err - } - - fd, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE, 0755) - if err != nil { - return err - } - defer fd.Close() - - info, err := fd.Stat() - if err != nil { - return err - } - if info.Size() == 0 { - _, err := fd.Write([]byte("{}")) - return err - } - return nil -} diff --git a/internal/containerizedengine/signal_unix.go b/internal/containerizedengine/signal_unix.go deleted file mode 100644 index 983336fe3d..0000000000 --- a/internal/containerizedengine/signal_unix.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !windows - -package containerizedengine - -import ( - "golang.org/x/sys/unix" -) - -var ( - // SIGKILL maps to unix.SIGKILL - SIGKILL = unix.SIGKILL -) diff --git a/internal/containerizedengine/signal_windows.go b/internal/containerizedengine/signal_windows.go deleted file mode 100644 index 93c4cbb940..0000000000 --- a/internal/containerizedengine/signal_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build windows - -package containerizedengine - -import ( - "syscall" -) - -var ( - // SIGKILL all signals are ignored by containerd kill windows - SIGKILL = syscall.Signal(0) -) diff --git a/internal/containerizedengine/types.go b/internal/containerizedengine/types.go index d86a9c3338..017e9e7ed4 100644 --- a/internal/containerizedengine/types.go +++ b/internal/containerizedengine/types.go @@ -7,16 +7,15 @@ import ( "github.com/containerd/containerd" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" - specs "github.com/opencontainers/runtime-spec/specs-go" ) const ( - containerdSockPath = "/run/containerd/containerd.sock" - engineContainerName = "dockerd" - engineNamespace = "docker" + containerdSockPath = "/run/containerd/containerd.sock" + engineNamespace = "com.docker" - // Used to signal the containerd-proxy if it should manage - proxyLabel = "com.docker/containerd-proxy.scope" + // 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 ( @@ -34,39 +33,6 @@ var ( // ErrEngineShutdownTimeout returned if the engine failed to shutdown in time ErrEngineShutdownTimeout = errors.New("timeout waiting for engine to exit") - - // ErrEngineImageMissingTag returned if the engine image is missing the version tag - ErrEngineImageMissingTag = errors.New("malformed engine image missing tag") - - engineSpec = specs.Spec{ - Root: &specs.Root{ - Path: "rootfs", - }, - Process: &specs.Process{ - Cwd: "/", - Args: []string{ - // In general, configuration should be driven by the config file, not these flags - // TODO - consider moving more of these to the config file, and make sure the defaults are set if not present. - "/sbin/dockerd", - "-s", - "overlay2", - "--containerd", - "/run/containerd/containerd.sock", - "--default-runtime", - "containerd", - "--add-runtime", - "containerd=runc", - }, - User: specs.User{ - UID: 0, - GID: 0, - }, - Env: []string{ - "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin", - }, - NoNewPrivileges: false, - }, - } ) type baseClient struct { @@ -82,4 +48,13 @@ type containerdClient interface { Close() error ContentStore() content.Store ContainerService() containers.Store + 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"` } diff --git a/internal/containerizedengine/update.go b/internal/containerizedengine/update.go index 0150688372..0d5bab225e 100644 --- a/internal/containerizedengine/update.go +++ b/internal/containerizedengine/update.go @@ -2,74 +2,31 @@ package containerizedengine import ( "context" + "encoding/json" "fmt" - "path" + "io/ioutil" + "os" + "path/filepath" "strings" + "github.com/containerd/containerd" + "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" - "github.com/docker/cli/internal/pkg/containerized" clitypes "github.com/docker/cli/types" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" + ver "github.com/hashicorp/go-version" + "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) -// GetCurrentEngineVersion determines the current type of engine (image) and version -func (c *baseClient) GetCurrentEngineVersion(ctx context.Context) (clitypes.EngineInitOptions, error) { - ctx = namespaces.WithNamespace(ctx, engineNamespace) - ret := clitypes.EngineInitOptions{} - currentEngine := clitypes.CommunityEngineImage - engine, err := c.GetEngine(ctx) - if err != nil { - if err == ErrEngineNotPresent { - return ret, errors.Wrap(err, "failed to find existing engine") - } - return ret, err - } - imageName, err := c.getEngineImage(engine) - if err != nil { - return ret, err - } - distributionRef, err := reference.ParseNormalizedNamed(imageName) - if err != nil { - return ret, errors.Wrapf(err, "failed to parse image name: %s", imageName) - } - - if strings.Contains(distributionRef.Name(), clitypes.EnterpriseEngineImage) { - currentEngine = clitypes.EnterpriseEngineImage - } - taggedRef, ok := distributionRef.(reference.NamedTagged) - if !ok { - return ret, ErrEngineImageMissingTag - } - ret.EngineImage = currentEngine - ret.EngineVersion = taggedRef.Tag() - ret.RegistryPrefix = reference.Domain(taggedRef) + "/" + path.Dir(reference.Path(taggedRef)) - return ret, nil -} - // ActivateEngine will switch the image from the CE to EE image func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream, authConfig *types.AuthConfig, healthfn func(context.Context) error) error { - // set the proxy scope to "ee" for activate flows - opts.Scope = "ee" - ctx = namespaces.WithNamespace(ctx, engineNamespace) - - // If version is unspecified, use the existing engine version - if opts.EngineVersion == "" { - currentOpts, err := c.GetCurrentEngineVersion(ctx) - if err != nil { - return err - } - opts.EngineVersion = currentOpts.EngineVersion - if currentOpts.EngineImage == clitypes.EnterpriseEngineImage { - // This is a "no-op" activation so the only change would be the license - don't update the engine itself - return nil - } - } return c.DoUpdate(ctx, opts, out, authConfig, healthfn) } @@ -84,7 +41,21 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio // current engine version and automatically apply it so users // could stay in sync by simply having a scheduled // `docker engine update` - return fmt.Errorf("please pick the version you want to update to") + return fmt.Errorf("pick the version you want to update to with --version") + } + + 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 == "" { + 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'") } imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion) @@ -102,30 +73,137 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio } } - // Gather information about the existing engine so we can recreate it - engine, err := c.GetEngine(ctx) + // Make sure we're safe to proceed + newMetadata, err := c.PreflightCheck(ctx, image) if err != nil { - if err == ErrEngineNotPresent { - return errors.Wrap(err, "unable to find existing engine - please use init") + 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\". Refer to %s for update instructions.\n\n", newMetadata.Platform, getReleaseNotesURL(imageName)) } + } + + if err := c.cclient.Install(ctx, image, containerd.WithInstallReplace, containerd.WithInstallPath("/usr")); err != nil { return err } - // TODO verify the image has changed and don't update if nothing has changed - - err = containerized.AtomicImageUpdate(ctx, engine, image, func() error { - ctx, cancel := context.WithTimeout(ctx, engineWaitTimeout) - defer cancel() - return c.waitForEngine(ctx, out, healthfn) - }) - if err == nil && opts.Scope != "" { - var labels map[string]string - labels, err = engine.Labels(ctx) - if err != nil { - return err - } - labels[proxyLabel] = opts.Scope - _, err = engine.SetLabels(ctx, labels) - } - return err + return c.WriteRuntimeMetadata("", 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 +// 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["com.docker."+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 daemon image: %s\nConsult the release notes at %s for upgrade instructions", metadata.Runtime, 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.\nConsult the release notes at %s for upgrade instructions", + localVersion.Version, metadata.ContainerdMinVersion, 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 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", clitypes.ReleaseNotePrefix, versionTag) } diff --git a/internal/containerizedengine/update_test.go b/internal/containerizedengine/update_test.go index cb1e06dd68..75615df271 100644 --- a/internal/containerizedengine/update_test.go +++ b/internal/containerizedengine/update_test.go @@ -4,6 +4,9 @@ import ( "bytes" "context" "fmt" + "io/ioutil" + "os" + "path/filepath" "testing" "github.com/containerd/containerd" @@ -12,162 +15,24 @@ import ( "github.com/docker/cli/cli/command" clitypes "github.com/docker/cli/types" "github.com/docker/docker/api/types" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/assert" ) -func TestGetCurrentEngineVersionHappy(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 healthfnHappy(ctx context.Context) error { + return nil } -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) { +func TestActivateConfigFailure(t *testing.T) { ctx := context.Background() registryPrefix := "registryprefixgoeshere" image := &fakeImage{ nameFunc: func() string { return registryPrefix + "/" + clitypes.EnterpriseEngineImage + ":engineversion" }, + configFunc: func(ctx context.Context) (ocispec.Descriptor, error) { + return ocispec.Descriptor{}, fmt.Errorf("config lookup failure") + }, } container := &fakeContainer{ imageFunc: func(context.Context) (containerd.Image, error) { @@ -185,6 +50,9 @@ func TestActivateNoChange(t *testing.T) { 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) { + return image, nil + }, }, } opts := clitypes.EngineInitOptions{ @@ -195,7 +63,7 @@ func TestActivateNoChange(t *testing.T) { } 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) { @@ -244,7 +112,7 @@ func TestDoUpdateNoVersion(t *testing.T) { } client := baseClient{} err := client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) - assert.ErrorContains(t, err, "please pick the version you") + assert.ErrorContains(t, err, "pick the version you") } func TestDoUpdateImageMiscError(t *testing.T) { @@ -292,30 +160,114 @@ func TestDoUpdatePullFail(t *testing.T) { assert.ErrorContains(t, err, "pull failure") } -func TestDoUpdateEngineMissing(t *testing.T) { +func TestActivateDoUpdateVerifyImageName(t *testing.T) { 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{ EngineVersion: "engineversiongoeshere", RegistryPrefix: "registryprefixgoeshere", ConfigFile: "/tmp/configfilegoeshere", - EngineImage: "testnamegoeshere", } - image := &fakeImage{ - nameFunc: func() string { - return "imagenamehere" - }, - } - client := baseClient{ - cclient: &fakeContainerdClient{ - getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) { - return image, nil - }, - containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { - return []containerd.Container{}, nil - }, - }, - } - err := client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{}, healthfnHappy) - assert.ErrorContains(t, err, "unable to find existing engine") + 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) + 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) + + // 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) { + imageName := "bogus image name #$%&@!" + url := getReleaseNotesURL(imageName) + assert.Equal(t, url, clitypes.ReleaseNotePrefix+"/") + imageName = "foo.bar/valid/repowithouttag" + url = getReleaseNotesURL(imageName) + assert.Equal(t, url, clitypes.ReleaseNotePrefix+"/") + imageName = "foo.bar/valid/repowithouttag:tag123" + url = getReleaseNotesURL(imageName) + assert.Equal(t, url, clitypes.ReleaseNotePrefix+"/tag123") } diff --git a/internal/containerizedengine/versions.go b/internal/versions/versions.go similarity index 66% rename from internal/containerizedengine/versions.go rename to internal/versions/versions.go index 63ac4a2dc2..160d00c55c 100644 --- a/internal/containerizedengine/versions.go +++ b/internal/versions/versions.go @@ -1,19 +1,23 @@ -package containerizedengine +package versions import ( "context" + "path" "sort" + "strings" registryclient "github.com/docker/cli/cli/registry/client" clitypes "github.com/docker/cli/types" "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" ver "github.com/hashicorp/go-version" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // 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) if err != nil { return clitypes.AvailableVersions{}, err @@ -24,7 +28,25 @@ func (c *baseClient) GetEngineVersions(ctx context.Context, registryClient regis return clitypes.AvailableVersions{}, err } - return parseTags(tags, currentVersion) + return parseTags(tags, serverVersion.Version) +} + +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) { diff --git a/internal/containerizedengine/versions_test.go b/internal/versions/versions_test.go similarity index 91% rename from internal/containerizedengine/versions_test.go rename to internal/versions/versions_test.go index eec782471b..1828ad0249 100644 --- a/internal/containerizedengine/versions_test.go +++ b/internal/versions/versions_test.go @@ -1,19 +1,19 @@ -package containerizedengine +package versions import ( "context" "testing" + "github.com/docker/docker/api/types" "gotest.tools/assert" ) func TestGetEngineVersionsBadImage(t *testing.T) { ctx := context.Background() - client := baseClient{} - currentVersion := "currentversiongoeshere" - imageName := "this is an illegal image $%^&" - _, err := client.GetEngineVersions(ctx, nil, currentVersion, imageName) + registryPrefix := "this is an illegal image $%^&" + currentVersion := types.Version{Version: "currentversiongoeshere"} + _, err := GetEngineVersions(ctx, nil, registryPrefix, currentVersion) assert.ErrorContains(t, err, "invalid reference format") } diff --git a/scripts/test/engine/entry b/scripts/test/engine/entry deleted file mode 100755 index 90248e4179..0000000000 --- a/scripts/test/engine/entry +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -eu -o pipefail - -# TODO fetch images? -./scripts/test/engine/wrapper diff --git a/scripts/test/engine/run b/scripts/test/engine/run deleted file mode 100755 index e19d164559..0000000000 --- a/scripts/test/engine/run +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env bash -# Run engine specific integration tests against the latest containerd-in-docker -set -eu -o pipefail - -function container_ip { - local cid=$1 - local network=$2 - docker inspect \ - -f "{{.NetworkSettings.Networks.${network}.IPAddress}}" "$cid" -} - -function fetch_images { - ## TODO - not yet implemented - ./scripts/test/engine/load-image fetch-only -} - -function setup { - ### start containerd and log to a file - echo "Starting containerd in the background" - containerd 2&> /tmp/containerd.err & - echo "Waiting for containerd to be responsive" - # shellcheck disable=SC2034 - for i in $(seq 1 60); do - if ctr namespace ls > /dev/null; then - break - fi - sleep 1 - done - ctr namespace ls > /dev/null - echo "containerd is ready" - - # TODO Once https://github.com/moby/moby/pull/33355 or equivalent - # is merged, then this can be optimized to preload the image - # saved during the build phase -} - -function cleanup { - #### if testexit is non-zero dump the containerd logs with a banner - if [ "${testexit}" -ne 0 ] ; then - echo "FAIL: dumping containerd logs" - echo "" - cat /tmp/containerd.err - if [ -f /var/log/engine.log ] ; then - echo "" - echo "FAIL: dumping engine log" - echo "" - else - echo "" - echo "FAIL: engine log missing" - echo "" - fi - echo "FAIL: remaining namespaces" - ctr namespace ls || /bin/tru - echo "FAIL: remaining containers" - ctr --namespace docker container ls || /bin/tru - echo "FAIL: remaining tasks" - ctr --namespace docker task ls || /bin/tru - echo "FAIL: remaining snapshots" - ctr --namespace docker snapshots ls || /bin/tru - echo "FAIL: remaining images" - ctr --namespace docker image ls || /bin/tru - fi -} - -function runtests { - # shellcheck disable=SC2086 - env -i \ - GOPATH="$GOPATH" \ - PATH="$PWD/build/:${PATH}" \ - VERSION=${VERSION} \ - "$(which go)" test -p 1 -parallel 1 -v ./e2eengine/... ${TESTFLAGS-} -} - -cmd=${1-} - -case "$cmd" in - setup) - setup - exit - ;; - cleanup) - cleanup - exit - ;; - fetch-images) - fetch_images - exit - ;; - test) - runtests - ;; - run|"") - testexit=0 - runtests || testexit=$? - cleanup - exit $testexit - ;; - shell) - $SHELL - ;; - *) - echo "Unknown command: $cmd" - echo "Usage: " - echo " $0 [setup | cleanup | test | run]" - exit 1 - ;; -esac diff --git a/scripts/test/engine/wrapper b/scripts/test/engine/wrapper deleted file mode 100755 index b4d9a2a4d7..0000000000 --- a/scripts/test/engine/wrapper +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# Setup, run and teardown engine test suite in containers. -set -eu -o pipefail - -./scripts/test/engine/run setup - -testexit=0 - -test_cmd="test" -if [[ -n "${TEST_DEBUG-}" ]]; then - test_cmd="shell" -fi - -./scripts/test/engine/run "$test_cmd" || testexit="$?" - -export testexit -./scripts/test/engine/run cleanup -exit "$testexit" diff --git a/types/types.go b/types/types.go index 0a376e0579..61cbcd4b55 100644 --- a/types/types.go +++ b/types/types.go @@ -4,7 +4,6 @@ import ( "context" "io" - registryclient "github.com/docker/cli/cli/registry/client" "github.com/docker/docker/api/types" ver "github.com/hashicorp/go-version" ) @@ -15,6 +14,12 @@ const ( // EnterpriseEngineImage is the repo name for the enterprise engine EnterpriseEngineImage = "engine-enterprise" + + // RegistryPrefix is the default prefix used to pull engine images + RegistryPrefix = "docker.io/store/docker" + + // ReleaseNotePrefix is where to point users to for release notes + ReleaseNotePrefix = "https://docs.docker.com/releasenotes" ) // ContainerizedClient can be used to manage the lifecycle of @@ -26,19 +31,11 @@ type ContainerizedClient interface { out OutStream, authConfig *types.AuthConfig, healthfn func(context.Context) error) error - InitEngine(ctx context.Context, - opts EngineInitOptions, - out OutStream, - authConfig *types.AuthConfig, - healthfn func(context.Context) error) error DoUpdate(ctx context.Context, opts EngineInitOptions, out OutStream, authConfig *types.AuthConfig, healthfn func(context.Context) error) error - GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, currentVersion, imageName string) (AvailableVersions, error) - GetCurrentEngineVersion(ctx context.Context) (EngineInitOptions, error) - RemoveEngine(ctx context.Context) error } // EngineInitOptions contains the configuration settings @@ -48,7 +45,6 @@ type EngineInitOptions struct { EngineImage string EngineVersion string ConfigFile string - Scope string } // AvailableVersions groups the available versions which were discovered diff --git a/vendor.conf b/vendor.conf index 471c5c55a3..26d5367e7e 100755 --- a/vendor.conf +++ b/vendor.conf @@ -3,7 +3,7 @@ github.com/asaskevich/govalidator f9ffefc3facfbe0caee3fea233cbb6e8208f4541 github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 github.com/beorn7/perks 3a771d992973f24aa725d07868b467d1ddfceafb github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 -github.com/containerd/containerd v1.2.0-beta.2 +github.com/containerd/containerd bb0f83ab6eec47c3316bb763d5c20a82c7750c31 github.com/containerd/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371 github.com/containerd/fifo 3d5202a github.com/containerd/typeurl f694355 diff --git a/vendor/github.com/containerd/containerd/README.md b/vendor/github.com/containerd/containerd/README.md index d068fa3afd..d3e4aa4357 100644 --- a/vendor/github.com/containerd/containerd/README.md +++ b/vendor/github.com/containerd/containerd/README.md @@ -2,6 +2,7 @@ [![GoDoc](https://godoc.org/github.com/containerd/containerd?status.svg)](https://godoc.org/github.com/containerd/containerd) [![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/containerd/containerd?branch=master&svg=true)](https://ci.appveyor.com/project/mlaventure/containerd-3g73f?branch=master) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield) [![Go Report Card](https://goreportcard.com/badge/github.com/containerd/containerd)](https://goreportcard.com/report/github.com/containerd/containerd) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1271/badge)](https://bestpractices.coreinfrastructure.org/projects/1271) diff --git a/vendor/github.com/containerd/containerd/api/services/events/v1/events.pb.go b/vendor/github.com/containerd/containerd/api/services/events/v1/events.pb.go index 0173f394ea..d6a7b38a87 100644 --- a/vendor/github.com/containerd/containerd/api/services/events/v1/events.pb.go +++ b/vendor/github.com/containerd/containerd/api/services/events/v1/events.pb.go @@ -141,7 +141,7 @@ type EventsClient interface { // Forward sends an event that has already been packaged into an envelope // with a timestamp and namespace. // - // This is useful if earlier timestamping is required or when fowarding on + // This is useful if earlier timestamping is required or when forwarding on // behalf of another component, namespace or publisher. Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*google_protobuf2.Empty, error) // Subscribe to a stream of events, possibly returning only that match any @@ -223,7 +223,7 @@ type EventsServer interface { // Forward sends an event that has already been packaged into an envelope // with a timestamp and namespace. // - // This is useful if earlier timestamping is required or when fowarding on + // This is useful if earlier timestamping is required or when forwarding on // behalf of another component, namespace or publisher. Forward(context.Context, *ForwardRequest) (*google_protobuf2.Empty, error) // Subscribe to a stream of events, possibly returning only that match any diff --git a/vendor/github.com/containerd/containerd/api/services/events/v1/events.proto b/vendor/github.com/containerd/containerd/api/services/events/v1/events.proto index 58f2dadeb5..1959c8e395 100644 --- a/vendor/github.com/containerd/containerd/api/services/events/v1/events.proto +++ b/vendor/github.com/containerd/containerd/api/services/events/v1/events.proto @@ -20,7 +20,7 @@ service Events { // Forward sends an event that has already been packaged into an envelope // with a timestamp and namespace. // - // This is useful if earlier timestamping is required or when fowarding on + // This is useful if earlier timestamping is required or when forwarding on // behalf of another component, namespace or publisher. rpc Forward(ForwardRequest) returns (google.protobuf.Empty); diff --git a/vendor/github.com/containerd/containerd/cio/io.go b/vendor/github.com/containerd/containerd/cio/io.go index 10ad36ba87..a9c6d2b156 100644 --- a/vendor/github.com/containerd/containerd/cio/io.go +++ b/vendor/github.com/containerd/containerd/cio/io.go @@ -141,6 +141,15 @@ func NewCreator(opts ...Opt) Creator { if err != nil { return nil, err } + if streams.Stdin == nil { + fifos.Stdin = "" + } + if streams.Stdout == nil { + fifos.Stdout = "" + } + if streams.Stderr == nil { + fifos.Stderr = "" + } return copyIO(fifos, streams) } } diff --git a/vendor/github.com/containerd/containerd/container_opts_unix.go b/vendor/github.com/containerd/containerd/container_opts_unix.go index a4935b2b45..c0622f67fe 100644 --- a/vendor/github.com/containerd/containerd/container_opts_unix.go +++ b/vendor/github.com/containerd/containerd/container_opts_unix.go @@ -20,25 +20,21 @@ package containerd import ( "context" - "encoding/json" "fmt" "os" "path/filepath" "syscall" - "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/platforms" - "github.com/containerd/containerd/runtime/linux/runctypes" "github.com/gogo/protobuf/proto" protobuf "github.com/gogo/protobuf/types" "github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/specs-go/v1" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -105,44 +101,6 @@ func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts { } } -// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a -// previous checkpoint. Additional software such as CRIU may be required to -// restore a task from a checkpoint -func WithTaskCheckpoint(im Image) NewTaskOpts { - return func(ctx context.Context, c *Client, info *TaskInfo) error { - desc := im.Target() - id := desc.Digest - index, err := decodeIndex(ctx, c.ContentStore(), desc) - if err != nil { - return err - } - for _, m := range index.Manifests { - if m.MediaType == images.MediaTypeContainerd1Checkpoint { - info.Checkpoint = &types.Descriptor{ - MediaType: m.MediaType, - Size_: m.Size, - Digest: m.Digest, - } - return nil - } - } - return fmt.Errorf("checkpoint not found in index %s", id) - } -} - -func decodeIndex(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (*v1.Index, error) { - var index v1.Index - p, err := content.ReadBlob(ctx, store, desc) - if err != nil { - return nil, err - } - if err := json.Unmarshal(p, &index); err != nil { - return nil, err - } - - return &index, nil -} - // WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the // filesystem to be used by a container with user namespaces func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts { @@ -221,19 +179,3 @@ func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc { return os.Lchown(path, u, g) } } - -// WithNoPivotRoot instructs the runtime not to you pivot_root -func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error { - if info.Options == nil { - info.Options = &runctypes.CreateOptions{ - NoPivotRoot: true, - } - return nil - } - copts, ok := info.Options.(*runctypes.CreateOptions) - if !ok { - return errors.New("invalid options type, expected runctypes.CreateOptions") - } - copts.NoPivotRoot = true - return nil -} diff --git a/vendor/github.com/containerd/containerd/events/exchange/exchange.go b/vendor/github.com/containerd/containerd/events/exchange/exchange.go index 51c760f045..95d21b7df6 100644 --- a/vendor/github.com/containerd/containerd/events/exchange/exchange.go +++ b/vendor/github.com/containerd/containerd/events/exchange/exchange.go @@ -52,7 +52,7 @@ var _ events.Subscriber = &Exchange{} // Forward accepts an envelope to be direcly distributed on the exchange. // -// This is useful when an event is forwaded on behalf of another namespace or +// This is useful when an event is forwarded on behalf of another namespace or // when the event is propagated on behalf of another publisher. func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) { if err := validateEnvelope(envelope); err != nil { diff --git a/vendor/github.com/containerd/containerd/export.go b/vendor/github.com/containerd/containerd/export.go new file mode 100644 index 0000000000..7aac309ba0 --- /dev/null +++ b/vendor/github.com/containerd/containerd/export.go @@ -0,0 +1,57 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containerd + +import ( + "context" + "io" + + "github.com/containerd/containerd/images" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +type exportOpts struct { +} + +// ExportOpt allows the caller to specify export-specific options +type ExportOpt func(c *exportOpts) error + +func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) { + var eopts exportOpts + for _, o := range opts { + if err := o(&eopts); err != nil { + return eopts, err + } + } + return eopts, nil +} + +// Export exports an image to a Tar stream. +// OCI format is used by default. +// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc. +// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream. +func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) { + _, err := resolveExportOpt(opts...) // unused now + if err != nil { + return nil, err + } + pr, pw := io.Pipe() + go func() { + pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw)) + }() + return pr, nil +} diff --git a/vendor/github.com/containerd/containerd/import.go b/vendor/github.com/containerd/containerd/import.go index e4ac00cee3..7a69f1d45a 100644 --- a/vendor/github.com/containerd/containerd/import.go +++ b/vendor/github.com/containerd/containerd/import.go @@ -22,7 +22,6 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) type importOpts struct { @@ -84,35 +83,3 @@ func (c *Client) Import(ctx context.Context, importer images.Importer, reader io } return images, nil } - -type exportOpts struct { -} - -// ExportOpt allows the caller to specify export-specific options -type ExportOpt func(c *exportOpts) error - -func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) { - var eopts exportOpts - for _, o := range opts { - if err := o(&eopts); err != nil { - return eopts, err - } - } - return eopts, nil -} - -// Export exports an image to a Tar stream. -// OCI format is used by default. -// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc. -// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream. -func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) { - _, err := resolveExportOpt(opts...) // unused now - if err != nil { - return nil, err - } - pr, pw := io.Pipe() - go func() { - pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw)) - }() - return pr, nil -} diff --git a/vendor/github.com/containerd/containerd/install.go b/vendor/github.com/containerd/containerd/install.go index 2aa8b03946..5e4c6a2c8d 100644 --- a/vendor/github.com/containerd/containerd/install.go +++ b/vendor/github.com/containerd/containerd/install.go @@ -33,25 +33,14 @@ import ( // Install a binary image into the opt service func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) error { - resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{ - Filters: []string{ - "id==opt", - }, - }) - if err != nil { - return err - } - if len(resp.Plugins) != 1 { - return errors.New("opt service not enabled") - } - path := resp.Plugins[0].Exports["path"] - if path == "" { - return errors.New("opt path not exported") - } var config InstallConfig for _, o := range opts { o(&config) } + path, err := c.getInstallPath(ctx, config) + if err != nil { + return err + } var ( cs = image.ContentStore() platform = platforms.Default() @@ -89,3 +78,25 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) } return nil } + +func (c *Client) getInstallPath(ctx context.Context, config InstallConfig) (string, error) { + if config.Path != "" { + return config.Path, nil + } + resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{ + Filters: []string{ + "id==opt", + }, + }) + if err != nil { + return "", err + } + if len(resp.Plugins) != 1 { + return "", errors.New("opt service not enabled") + } + path := resp.Plugins[0].Exports["path"] + if path == "" { + return "", errors.New("opt path not exported") + } + return path, nil +} diff --git a/vendor/github.com/containerd/containerd/install_opts.go b/vendor/github.com/containerd/containerd/install_opts.go index b11e7f3d6d..b0c9213cb2 100644 --- a/vendor/github.com/containerd/containerd/install_opts.go +++ b/vendor/github.com/containerd/containerd/install_opts.go @@ -25,6 +25,8 @@ type InstallConfig struct { Libs bool // Replace will overwrite existing binaries or libs in the opt directory Replace bool + // Path to install libs and binaries to + Path string } // WithInstallLibs installs libs from the image @@ -36,3 +38,10 @@ func WithInstallLibs(c *InstallConfig) { func WithInstallReplace(c *InstallConfig) { c.Replace = true } + +// WithInstallPath sets the optional install path +func WithInstallPath(path string) InstallOpts { + return func(c *InstallConfig) { + c.Path = path + } +} diff --git a/vendor/github.com/containerd/containerd/mount/mount_windows.go b/vendor/github.com/containerd/containerd/mount/mount_windows.go index f7c97894b4..5de25c4e0a 100644 --- a/vendor/github.com/containerd/containerd/mount/mount_windows.go +++ b/vendor/github.com/containerd/containerd/mount/mount_windows.go @@ -32,6 +32,10 @@ var ( // Mount to the provided target func (m *Mount) Mount(target string) error { + if m.Type != "windows-layer" { + return errors.Errorf("invalid windows mount type: '%s'", m.Type) + } + home, layerID := filepath.Split(m.Source) parentLayerPaths, err := m.GetParentPaths() diff --git a/vendor/github.com/containerd/containerd/oci/spec.go b/vendor/github.com/containerd/containerd/oci/spec.go index ffd0bffca0..6fb31e454c 100644 --- a/vendor/github.com/containerd/containerd/oci/spec.go +++ b/vendor/github.com/containerd/containerd/oci/spec.go @@ -18,11 +18,27 @@ package oci import ( "context" + "path/filepath" + "runtime" + + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/containers" specs "github.com/opencontainers/runtime-spec/specs-go" ) +const ( + rwm = "rwm" + defaultRootfsPath = "rootfs" +) + +var ( + defaultUnixEnv = []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + } +) + // Spec is a type alias to the OCI runtime spec to allow third part SpecOpts // to be created without the "issues" with go vendoring and package imports type Spec = specs.Spec @@ -30,12 +46,36 @@ type Spec = specs.Spec // GenerateSpec will generate a default spec from the provided image // for use as a containerd container func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) { - s, err := createDefaultSpec(ctx, c.ID) - if err != nil { + return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...) +} + +// GenerateSpecWithPlatform will generate a default spec from the provided image +// for use as a containerd container in the platform requested. +func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) { + var s Spec + if err := generateDefaultSpecWithPlatform(ctx, platform, c.ID, &s); err != nil { return nil, err } - return s, ApplyOpts(ctx, client, c, s, opts...) + return &s, ApplyOpts(ctx, client, c, &s, opts...) +} + +func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s *Spec) error { + plat, err := platforms.Parse(platform) + if err != nil { + return err + } + + if plat.OS == "windows" { + err = populateDefaultWindowsSpec(ctx, s, id) + } else { + err = populateDefaultUnixSpec(ctx, s, id) + if err == nil && runtime.GOOS == "windows" { + // To run LCOW we have a Linux and Windows section. Add an empty one now. + s.Windows = &specs.Windows{} + } + } + return err } // ApplyOpts applys the options to the given spec, injecting data from the @@ -50,7 +90,173 @@ func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *S return nil } -func createDefaultSpec(ctx context.Context, id string) (*Spec, error) { - var s Spec - return &s, populateDefaultSpec(ctx, &s, id) +func defaultUnixCaps() []string { + return []string{ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE", + } +} + +func defaultUnixNamespaces() []specs.LinuxNamespace { + return []specs.LinuxNamespace{ + { + Type: specs.PIDNamespace, + }, + { + Type: specs.IPCNamespace, + }, + { + Type: specs.UTSNamespace, + }, + { + Type: specs.MountNamespace, + }, + { + Type: specs.NetworkNamespace, + }, + } +} + +func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + + *s = Spec{ + Version: specs.Version, + Root: &specs.Root{ + Path: defaultRootfsPath, + }, + Process: &specs.Process{ + Env: defaultUnixEnv, + Cwd: "/", + NoNewPrivileges: true, + User: specs.User{ + UID: 0, + GID: 0, + }, + Capabilities: &specs.LinuxCapabilities{ + Bounding: defaultUnixCaps(), + Permitted: defaultUnixCaps(), + Inheritable: defaultUnixCaps(), + Effective: defaultUnixCaps(), + }, + Rlimits: []specs.POSIXRlimit{ + { + Type: "RLIMIT_NOFILE", + Hard: uint64(1024), + Soft: uint64(1024), + }, + }, + }, + Mounts: []specs.Mount{ + { + Destination: "/proc", + Type: "proc", + Source: "proc", + }, + { + Destination: "/dev", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, + }, + { + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}, + }, + { + Destination: "/dev/shm", + Type: "tmpfs", + Source: "shm", + Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}, + }, + { + Destination: "/dev/mqueue", + Type: "mqueue", + Source: "mqueue", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + { + Destination: "/sys", + Type: "sysfs", + Source: "sysfs", + Options: []string{"nosuid", "noexec", "nodev", "ro"}, + }, + { + Destination: "/run", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, + }, + }, + Linux: &specs.Linux{ + MaskedPaths: []string{ + "/proc/acpi", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/sys/firmware", + "/proc/scsi", + }, + ReadonlyPaths: []string{ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger", + }, + CgroupsPath: filepath.Join("/", ns, id), + Resources: &specs.LinuxResources{ + Devices: []specs.LinuxDeviceCgroup{ + { + Allow: false, + Access: rwm, + }, + }, + }, + Namespaces: defaultUnixNamespaces(), + }, + } + return nil +} + +func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error { + *s = Spec{ + Version: specs.Version, + Root: &specs.Root{}, + Process: &specs.Process{ + Cwd: `C:\`, + ConsoleSize: &specs.Box{ + Width: 80, + Height: 20, + }, + }, + Windows: &specs.Windows{ + IgnoreFlushesDuringBoot: true, + Network: &specs.WindowsNetwork{ + AllowUnqualifiedDNSQuery: true, + }, + }, + } + return nil } diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts.go b/vendor/github.com/containerd/containerd/oci/spec_opts.go index fd2cfb0392..973ac7e152 100644 --- a/vendor/github.com/containerd/containerd/oci/spec_opts.go +++ b/vendor/github.com/containerd/containerd/oci/spec_opts.go @@ -19,12 +19,25 @@ package oci import ( "context" "encoding/json" + "fmt" "io/ioutil" + "os" + "path/filepath" + "strconv" "strings" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/platforms" + "github.com/containerd/continuity/fs" + "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/runc/libcontainer/user" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" + "github.com/syndtr/gocapability/capability" ) // SpecOpts sets spec specific information to a newly generated OCI spec @@ -49,13 +62,45 @@ func setProcess(s *Spec) { } } +// setRoot sets Root to empty if unset +func setRoot(s *Spec) { + if s.Root == nil { + s.Root = &specs.Root{} + } +} + +// setLinux sets Linux to empty if unset +func setLinux(s *Spec) { + if s.Linux == nil { + s.Linux = &specs.Linux{} + } +} + +// setCapabilities sets Linux Capabilities to empty if unset +func setCapabilities(s *Spec) { + setProcess(s) + if s.Process.Capabilities == nil { + s.Process.Capabilities = &specs.LinuxCapabilities{} + } +} + // WithDefaultSpec returns a SpecOpts that will populate the spec with default // values. // // Use as the first option to clear the spec, then apply options afterwards. func WithDefaultSpec() SpecOpts { return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error { - return populateDefaultSpec(ctx, s, c.ID) + return generateDefaultSpecWithPlatform(ctx, platforms.DefaultString(), c.ID, s) + } +} + +// WithDefaultSpecForPlatform returns a SpecOpts that will populate the spec +// with default values for a given platform. +// +// Use as the first option to clear the spec, then apply options afterwards. +func WithDefaultSpecForPlatform(platform string) SpecOpts { + return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error { + return generateDefaultSpecWithPlatform(ctx, platform, c.ID, s) } } @@ -81,32 +126,6 @@ func WithSpecFromFile(filename string) SpecOpts { } } -// WithProcessArgs replaces the args on the generated spec -func WithProcessArgs(args ...string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.Args = args - return nil - } -} - -// WithProcessCwd replaces the current working directory on the generated spec -func WithProcessCwd(cwd string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.Cwd = cwd - return nil - } -} - -// WithHostname sets the container's hostname -func WithHostname(name string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - s.Hostname = name - return nil - } -} - // WithEnv appends environment variables func WithEnv(environmentVariables []string) SpecOpts { return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { @@ -118,14 +137,6 @@ func WithEnv(environmentVariables []string) SpecOpts { } } -// WithMounts appends mounts -func WithMounts(mounts []specs.Mount) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - s.Mounts = append(s.Mounts, mounts...) - return nil - } -} - // replaceOrAppendEnvValues returns the defaults with the overrides either // replaced by env key or appended to the list func replaceOrAppendEnvValues(defaults, overrides []string) []string { @@ -163,3 +174,821 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string { return defaults } + +// WithProcessArgs replaces the args on the generated spec +func WithProcessArgs(args ...string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + s.Process.Args = args + return nil + } +} + +// WithProcessCwd replaces the current working directory on the generated spec +func WithProcessCwd(cwd string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + s.Process.Cwd = cwd + return nil + } +} + +// WithTTY sets the information on the spec as well as the environment variables for +// using a TTY +func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + s.Process.Terminal = true + if s.Linux != nil { + s.Process.Env = append(s.Process.Env, "TERM=xterm") + } + + return nil +} + +// WithTTYSize sets the information on the spec as well as the environment variables for +// using a TTY +func WithTTYSize(width, height int) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + if s.Process.ConsoleSize == nil { + s.Process.ConsoleSize = &specs.Box{} + } + s.Process.ConsoleSize.Width = uint(width) + s.Process.ConsoleSize.Height = uint(height) + return nil + } +} + +// WithHostname sets the container's hostname +func WithHostname(name string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + s.Hostname = name + return nil + } +} + +// WithMounts appends mounts +func WithMounts(mounts []specs.Mount) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + s.Mounts = append(s.Mounts, mounts...) + return nil + } +} + +// WithHostNamespace allows a task to run inside the host's linux namespace +func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + for i, n := range s.Linux.Namespaces { + if n.Type == ns { + s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...) + return nil + } + } + return nil + } +} + +// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the +// spec, the existing namespace is replaced by the one provided. +func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + for i, n := range s.Linux.Namespaces { + if n.Type == ns.Type { + before := s.Linux.Namespaces[:i] + after := s.Linux.Namespaces[i+1:] + s.Linux.Namespaces = append(before, ns) + s.Linux.Namespaces = append(s.Linux.Namespaces, after...) + return nil + } + } + s.Linux.Namespaces = append(s.Linux.Namespaces, ns) + return nil + } +} + +// WithImageConfig configures the spec to from the configuration of an Image +func WithImageConfig(image Image) SpecOpts { + return WithImageConfigArgs(image, nil) +} + +// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that +// replaces the CMD of the image +func WithImageConfigArgs(image Image, args []string) SpecOpts { + return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error { + ic, err := image.Config(ctx) + if err != nil { + return 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 err + } + + if err := json.Unmarshal(p, &ociimage); err != nil { + return err + } + config = ociimage.Config + default: + return fmt.Errorf("unknown image config media type %s", ic.MediaType) + } + + setProcess(s) + if s.Linux != nil { + s.Process.Env = append(s.Process.Env, config.Env...) + cmd := config.Cmd + if len(args) > 0 { + cmd = args + } + s.Process.Args = append(config.Entrypoint, cmd...) + + cwd := config.WorkingDir + if cwd == "" { + cwd = "/" + } + s.Process.Cwd = cwd + if config.User != "" { + return WithUser(config.User)(ctx, client, c, s) + } + } else if s.Windows != nil { + s.Process.Env = config.Env + s.Process.Args = append(config.Entrypoint, config.Cmd...) + s.Process.User = specs.User{ + Username: config.User, + } + } else { + return errors.New("spec does not contain Linux or Windows section") + } + return nil + } +} + +// WithRootFSPath specifies unmanaged rootfs path. +func WithRootFSPath(path string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setRoot(s) + s.Root.Path = path + // Entrypoint is not set here (it's up to caller) + return nil + } +} + +// WithRootFSReadonly sets specs.Root.Readonly to true +func WithRootFSReadonly() SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setRoot(s) + s.Root.Readonly = true + return nil + } +} + +// WithNoNewPrivileges sets no_new_privileges on the process for the container +func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + s.Process.NoNewPrivileges = true + return nil +} + +// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly +func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + s.Mounts = append(s.Mounts, specs.Mount{ + Destination: "/etc/hosts", + Type: "bind", + Source: "/etc/hosts", + Options: []string{"rbind", "ro"}, + }) + return nil +} + +// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly +func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + s.Mounts = append(s.Mounts, specs.Mount{ + Destination: "/etc/resolv.conf", + Type: "bind", + Source: "/etc/resolv.conf", + Options: []string{"rbind", "ro"}, + }) + return nil +} + +// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly +func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + s.Mounts = append(s.Mounts, specs.Mount{ + Destination: "/etc/localtime", + Type: "bind", + Source: "/etc/localtime", + Options: []string{"rbind", "ro"}, + }) + return nil +} + +// WithUserNamespace sets the uid and gid mappings for the task +// this can be called multiple times to add more mappings to the generated spec +func WithUserNamespace(container, host, size uint32) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + var hasUserns bool + setLinux(s) + for _, ns := range s.Linux.Namespaces { + if ns.Type == specs.UserNamespace { + hasUserns = true + break + } + } + if !hasUserns { + s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{ + Type: specs.UserNamespace, + }) + } + mapping := specs.LinuxIDMapping{ + ContainerID: container, + HostID: host, + Size: size, + } + s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping) + s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping) + return nil + } +} + +// WithCgroup sets the container's cgroup path +func WithCgroup(path string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + s.Linux.CgroupsPath = path + return nil + } +} + +// WithNamespacedCgroup uses the namespace set on the context to create a +// root directory for containers in the cgroup with the id as the subcgroup +func WithNamespacedCgroup() SpecOpts { + return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + setLinux(s) + s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID) + return nil + } +} + +// WithUser sets the user to be used within the container. +// It accepts a valid user string in OCI Image Spec v1.0.0: +// user, uid, user:group, uid:gid, uid:group, user:gid +func WithUser(userstr string) SpecOpts { + return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error { + setProcess(s) + parts := strings.Split(userstr, ":") + switch len(parts) { + case 1: + v, err := strconv.Atoi(parts[0]) + if err != nil { + // if we cannot parse as a uint they try to see if it is a username + return WithUsername(userstr)(ctx, client, c, s) + } + return WithUserID(uint32(v))(ctx, client, c, s) + case 2: + var ( + username string + groupname string + ) + var uid, gid uint32 + v, err := strconv.Atoi(parts[0]) + if err != nil { + username = parts[0] + } else { + uid = uint32(v) + } + if v, err = strconv.Atoi(parts[1]); err != nil { + groupname = parts[1] + } else { + gid = uint32(v) + } + if username == "" && groupname == "" { + s.Process.User.UID, s.Process.User.GID = uid, gid + return nil + } + f := func(root string) error { + if username != "" { + uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool { + return u.Name == username + }) + if err != nil { + return err + } + } + if groupname != "" { + gid, err = getGIDFromPath(root, func(g user.Group) bool { + return g.Name == groupname + }) + if err != nil { + return err + } + } + s.Process.User.UID, s.Process.User.GID = uid, gid + return nil + } + if c.Snapshotter == "" && c.SnapshotKey == "" { + if !isRootfsAbs(s.Root.Path) { + return errors.New("rootfs absolute path is required") + } + return f(s.Root.Path) + } + if c.Snapshotter == "" { + return errors.New("no snapshotter set for container") + } + if c.SnapshotKey == "" { + return errors.New("rootfs snapshot not created for container") + } + snapshotter := client.SnapshotService(c.Snapshotter) + mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) + if err != nil { + return err + } + return mount.WithTempMount(ctx, mounts, f) + default: + return fmt.Errorf("invalid USER value %s", userstr) + } + } +} + +// WithUIDGID allows the UID and GID for the Process to be set +func WithUIDGID(uid, gid uint32) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + s.Process.User.UID = uid + s.Process.User.GID = gid + return nil + } +} + +// WithUserID sets the correct UID and GID for the container based +// on the image's /etc/passwd contents. If /etc/passwd does not exist, +// or uid is not found in /etc/passwd, it sets the requested uid, +// additionally sets the gid to 0, and does not return an error. +func WithUserID(uid uint32) SpecOpts { + return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) { + setProcess(s) + if c.Snapshotter == "" && c.SnapshotKey == "" { + if !isRootfsAbs(s.Root.Path) { + return errors.Errorf("rootfs absolute path is required") + } + uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool { + return u.Uid == int(uid) + }) + if err != nil { + if os.IsNotExist(err) || err == errNoUsersFound { + s.Process.User.UID, s.Process.User.GID = uid, 0 + return nil + } + return err + } + s.Process.User.UID, s.Process.User.GID = uuid, ugid + return nil + + } + if c.Snapshotter == "" { + return errors.Errorf("no snapshotter set for container") + } + if c.SnapshotKey == "" { + return errors.Errorf("rootfs snapshot not created for container") + } + snapshotter := client.SnapshotService(c.Snapshotter) + mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) + if err != nil { + return err + } + return mount.WithTempMount(ctx, mounts, func(root string) error { + uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool { + return u.Uid == int(uid) + }) + if err != nil { + if os.IsNotExist(err) || err == errNoUsersFound { + s.Process.User.UID, s.Process.User.GID = uid, 0 + return nil + } + return err + } + s.Process.User.UID, s.Process.User.GID = uuid, ugid + return nil + }) + } +} + +// WithUsername sets the correct UID and GID for the container +// based on the the image's /etc/passwd contents. If /etc/passwd +// does not exist, or the username is not found in /etc/passwd, +// it returns error. +func WithUsername(username string) SpecOpts { + return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) { + setProcess(s) + if s.Linux != nil { + if c.Snapshotter == "" && c.SnapshotKey == "" { + if !isRootfsAbs(s.Root.Path) { + return errors.Errorf("rootfs absolute path is required") + } + uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool { + return u.Name == username + }) + if err != nil { + return err + } + s.Process.User.UID, s.Process.User.GID = uid, gid + return nil + } + if c.Snapshotter == "" { + return errors.Errorf("no snapshotter set for container") + } + if c.SnapshotKey == "" { + return errors.Errorf("rootfs snapshot not created for container") + } + snapshotter := client.SnapshotService(c.Snapshotter) + mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) + if err != nil { + return err + } + return mount.WithTempMount(ctx, mounts, func(root string) error { + uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool { + return u.Name == username + }) + if err != nil { + return err + } + s.Process.User.UID, s.Process.User.GID = uid, gid + return nil + }) + } else if s.Windows != nil { + s.Process.User.Username = username + } else { + return errors.New("spec does not contain Linux or Windows section") + } + return nil + } +} + +// WithAdditionalGIDs sets the OCI spec's additionalGids array to any additional groups listed +// for a particular user in the /etc/groups file of the image's root filesystem +func WithAdditionalGIDs(username string) SpecOpts { + return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) { + setProcess(s) + if c.Snapshotter == "" && c.SnapshotKey == "" { + if !isRootfsAbs(s.Root.Path) { + return errors.Errorf("rootfs absolute path is required") + } + gids, err := getSupplementalGroupsFromPath(s.Root.Path, func(g user.Group) bool { + // we only want supplemental groups + if g.Name == username { + return false + } + for _, entry := range g.List { + if entry == username { + return true + } + } + return false + }) + if err != nil { + return err + } + s.Process.User.AdditionalGids = gids + return nil + } + if c.Snapshotter == "" { + return errors.Errorf("no snapshotter set for container") + } + if c.SnapshotKey == "" { + return errors.Errorf("rootfs snapshot not created for container") + } + snapshotter := client.SnapshotService(c.Snapshotter) + mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) + if err != nil { + return err + } + return mount.WithTempMount(ctx, mounts, func(root string) error { + gids, err := getSupplementalGroupsFromPath(root, func(g user.Group) bool { + // we only want supplemental groups + if g.Name == username { + return false + } + for _, entry := range g.List { + if entry == username { + return true + } + } + return false + }) + if err != nil { + return err + } + s.Process.User.AdditionalGids = gids + return nil + }) + } +} + +// WithCapabilities sets Linux capabilities on the process +func WithCapabilities(caps []string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setCapabilities(s) + + s.Process.Capabilities.Bounding = caps + s.Process.Capabilities.Effective = caps + s.Process.Capabilities.Permitted = caps + s.Process.Capabilities.Inheritable = caps + + return nil + } +} + +// WithAllCapabilities sets all linux capabilities for the process +var WithAllCapabilities = WithCapabilities(getAllCapabilities()) + +func getAllCapabilities() []string { + last := capability.CAP_LAST_CAP + // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap + if last == capability.Cap(63) { + last = capability.CAP_BLOCK_SUSPEND + } + var caps []string + for _, cap := range capability.List() { + if cap > last { + continue + } + caps = append(caps, "CAP_"+strings.ToUpper(cap.String())) + } + return caps +} + +// WithAmbientCapabilities set the Linux ambient capabilities for the process +// Ambient capabilities should only be set for non-root users or the caller should +// understand how these capabilities are used and set +func WithAmbientCapabilities(caps []string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setCapabilities(s) + + s.Process.Capabilities.Ambient = caps + return nil + } +} + +var errNoUsersFound = errors.New("no users found") + +func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) { + ppath, err := fs.RootPath(root, "/etc/passwd") + if err != nil { + return 0, 0, err + } + users, err := user.ParsePasswdFileFilter(ppath, filter) + if err != nil { + return 0, 0, err + } + if len(users) == 0 { + return 0, 0, errNoUsersFound + } + u := users[0] + return uint32(u.Uid), uint32(u.Gid), nil +} + +var errNoGroupsFound = errors.New("no groups found") + +func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) { + gpath, err := fs.RootPath(root, "/etc/group") + if err != nil { + return 0, err + } + groups, err := user.ParseGroupFileFilter(gpath, filter) + if err != nil { + return 0, err + } + if len(groups) == 0 { + return 0, errNoGroupsFound + } + g := groups[0] + return uint32(g.Gid), nil +} + +func getSupplementalGroupsFromPath(root string, filter func(user.Group) bool) ([]uint32, error) { + gpath, err := fs.RootPath(root, "/etc/group") + if err != nil { + return []uint32{}, err + } + groups, err := user.ParseGroupFileFilter(gpath, filter) + if err != nil { + return []uint32{}, err + } + if len(groups) == 0 { + // if there are no additional groups; just return an empty set + return []uint32{}, nil + } + addlGids := []uint32{} + for _, grp := range groups { + addlGids = append(addlGids, uint32(grp.Gid)) + } + return addlGids, nil +} + +func isRootfsAbs(root string) bool { + return filepath.IsAbs(root) +} + +// WithMaskedPaths sets the masked paths option +func WithMaskedPaths(paths []string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + s.Linux.MaskedPaths = paths + return nil + } +} + +// WithReadonlyPaths sets the read only paths option +func WithReadonlyPaths(paths []string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + s.Linux.ReadonlyPaths = paths + return nil + } +} + +// WithWriteableSysfs makes any sysfs mounts writeable +func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + for i, m := range s.Mounts { + if m.Type == "sysfs" { + var options []string + for _, o := range m.Options { + if o == "ro" { + o = "rw" + } + options = append(options, o) + } + s.Mounts[i].Options = options + } + } + return nil +} + +// WithWriteableCgroupfs makes any cgroup mounts writeable +func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + for i, m := range s.Mounts { + if m.Type == "cgroup" { + var options []string + for _, o := range m.Options { + if o == "ro" { + o = "rw" + } + options = append(options, o) + } + s.Mounts[i].Options = options + } + } + return nil +} + +// WithSelinuxLabel sets the process SELinux label +func WithSelinuxLabel(label string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + s.Process.SelinuxLabel = label + return nil + } +} + +// WithApparmorProfile sets the Apparmor profile for the process +func WithApparmorProfile(profile string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setProcess(s) + s.Process.ApparmorProfile = profile + return nil + } +} + +// WithSeccompUnconfined clears the seccomp profile +func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + s.Linux.Seccomp = nil + return nil +} + +// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's +// allowed and denied devices +func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + if s.Linux.Resources == nil { + s.Linux.Resources = &specs.LinuxResources{} + } + s.Linux.Resources.Devices = nil + return nil +} + +// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to +// the container's resource cgroup spec +func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + if s.Linux.Resources == nil { + s.Linux.Resources = &specs.LinuxResources{} + } + intptr := func(i int64) *int64 { + return &i + } + s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{ + { + // "/dev/null", + Type: "c", + Major: intptr(1), + Minor: intptr(3), + Access: rwm, + Allow: true, + }, + { + // "/dev/random", + Type: "c", + Major: intptr(1), + Minor: intptr(8), + Access: rwm, + Allow: true, + }, + { + // "/dev/full", + Type: "c", + Major: intptr(1), + Minor: intptr(7), + Access: rwm, + Allow: true, + }, + { + // "/dev/tty", + Type: "c", + Major: intptr(5), + Minor: intptr(0), + Access: rwm, + Allow: true, + }, + { + // "/dev/zero", + Type: "c", + Major: intptr(1), + Minor: intptr(5), + Access: rwm, + Allow: true, + }, + { + // "/dev/urandom", + Type: "c", + Major: intptr(1), + Minor: intptr(9), + Access: rwm, + Allow: true, + }, + { + // "/dev/console", + Type: "c", + Major: intptr(5), + Minor: intptr(1), + Access: rwm, + Allow: true, + }, + // /dev/pts/ - pts namespaces are "coming soon" + { + Type: "c", + Major: intptr(136), + Access: rwm, + Allow: true, + }, + { + Type: "c", + Major: intptr(5), + Minor: intptr(2), + Access: rwm, + Allow: true, + }, + { + // tuntap + Type: "c", + Major: intptr(10), + Minor: intptr(200), + Access: rwm, + Allow: true, + }, + }...) + return nil +} + +// WithPrivileged sets up options for a privileged container +// TODO(justincormack) device handling +var WithPrivileged = Compose( + WithAllCapabilities, + WithMaskedPaths(nil), + WithReadonlyPaths(nil), + WithWriteableSysfs, + WithWriteableCgroupfs, + WithSelinuxLabel(""), + WithApparmorProfile(""), + WithSeccompUnconfined, +) diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go b/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go deleted file mode 100644 index 592da651d2..0000000000 --- a/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go +++ /dev/null @@ -1,733 +0,0 @@ -// +build !windows - -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package oci - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/containerd/containerd/containers" - "github.com/containerd/containerd/content" - "github.com/containerd/containerd/images" - "github.com/containerd/containerd/mount" - "github.com/containerd/containerd/namespaces" - "github.com/containerd/continuity/fs" - "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/opencontainers/runc/libcontainer/user" - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/syndtr/gocapability/capability" -) - -// WithTTY sets the information on the spec as well as the environment variables for -// using a TTY -func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.Terminal = true - s.Process.Env = append(s.Process.Env, "TERM=xterm") - return nil -} - -// setRoot sets Root to empty if unset -func setRoot(s *Spec) { - if s.Root == nil { - s.Root = &specs.Root{} - } -} - -// setLinux sets Linux to empty if unset -func setLinux(s *Spec) { - if s.Linux == nil { - s.Linux = &specs.Linux{} - } -} - -// setCapabilities sets Linux Capabilities to empty if unset -func setCapabilities(s *Spec) { - setProcess(s) - if s.Process.Capabilities == nil { - s.Process.Capabilities = &specs.LinuxCapabilities{} - } -} - -// WithHostNamespace allows a task to run inside the host's linux namespace -func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - for i, n := range s.Linux.Namespaces { - if n.Type == ns { - s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...) - return nil - } - } - return nil - } -} - -// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the -// spec, the existing namespace is replaced by the one provided. -func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - for i, n := range s.Linux.Namespaces { - if n.Type == ns.Type { - before := s.Linux.Namespaces[:i] - after := s.Linux.Namespaces[i+1:] - s.Linux.Namespaces = append(before, ns) - s.Linux.Namespaces = append(s.Linux.Namespaces, after...) - return nil - } - } - s.Linux.Namespaces = append(s.Linux.Namespaces, ns) - return nil - } -} - -// WithImageConfig configures the spec to from the configuration of an Image -func WithImageConfig(image Image) SpecOpts { - return WithImageConfigArgs(image, nil) -} - -// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that -// replaces the CMD of the image -func WithImageConfigArgs(image Image, args []string) SpecOpts { - return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error { - ic, err := image.Config(ctx) - if err != nil { - return 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 err - } - - if err := json.Unmarshal(p, &ociimage); err != nil { - return err - } - config = ociimage.Config - default: - return fmt.Errorf("unknown image config media type %s", ic.MediaType) - } - - setProcess(s) - s.Process.Env = append(s.Process.Env, config.Env...) - cmd := config.Cmd - if len(args) > 0 { - cmd = args - } - s.Process.Args = append(config.Entrypoint, cmd...) - - cwd := config.WorkingDir - if cwd == "" { - cwd = "/" - } - s.Process.Cwd = cwd - if config.User != "" { - return WithUser(config.User)(ctx, client, c, s) - } - return nil - } -} - -// WithRootFSPath specifies unmanaged rootfs path. -func WithRootFSPath(path string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setRoot(s) - s.Root.Path = path - // Entrypoint is not set here (it's up to caller) - return nil - } -} - -// WithRootFSReadonly sets specs.Root.Readonly to true -func WithRootFSReadonly() SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setRoot(s) - s.Root.Readonly = true - return nil - } -} - -// WithNoNewPrivileges sets no_new_privileges on the process for the container -func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.NoNewPrivileges = true - return nil -} - -// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly -func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - s.Mounts = append(s.Mounts, specs.Mount{ - Destination: "/etc/hosts", - Type: "bind", - Source: "/etc/hosts", - Options: []string{"rbind", "ro"}, - }) - return nil -} - -// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly -func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - s.Mounts = append(s.Mounts, specs.Mount{ - Destination: "/etc/resolv.conf", - Type: "bind", - Source: "/etc/resolv.conf", - Options: []string{"rbind", "ro"}, - }) - return nil -} - -// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly -func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - s.Mounts = append(s.Mounts, specs.Mount{ - Destination: "/etc/localtime", - Type: "bind", - Source: "/etc/localtime", - Options: []string{"rbind", "ro"}, - }) - return nil -} - -// WithUserNamespace sets the uid and gid mappings for the task -// this can be called multiple times to add more mappings to the generated spec -func WithUserNamespace(container, host, size uint32) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - var hasUserns bool - setLinux(s) - for _, ns := range s.Linux.Namespaces { - if ns.Type == specs.UserNamespace { - hasUserns = true - break - } - } - if !hasUserns { - s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{ - Type: specs.UserNamespace, - }) - } - mapping := specs.LinuxIDMapping{ - ContainerID: container, - HostID: host, - Size: size, - } - s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping) - s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping) - return nil - } -} - -// WithCgroup sets the container's cgroup path -func WithCgroup(path string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - s.Linux.CgroupsPath = path - return nil - } -} - -// WithNamespacedCgroup uses the namespace set on the context to create a -// root directory for containers in the cgroup with the id as the subcgroup -func WithNamespacedCgroup() SpecOpts { - return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error { - namespace, err := namespaces.NamespaceRequired(ctx) - if err != nil { - return err - } - setLinux(s) - s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID) - return nil - } -} - -// WithUser sets the user to be used within the container. -// It accepts a valid user string in OCI Image Spec v1.0.0: -// user, uid, user:group, uid:gid, uid:group, user:gid -func WithUser(userstr string) SpecOpts { - return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error { - setProcess(s) - parts := strings.Split(userstr, ":") - switch len(parts) { - case 1: - v, err := strconv.Atoi(parts[0]) - if err != nil { - // if we cannot parse as a uint they try to see if it is a username - return WithUsername(userstr)(ctx, client, c, s) - } - return WithUserID(uint32(v))(ctx, client, c, s) - case 2: - var ( - username string - groupname string - ) - var uid, gid uint32 - v, err := strconv.Atoi(parts[0]) - if err != nil { - username = parts[0] - } else { - uid = uint32(v) - } - if v, err = strconv.Atoi(parts[1]); err != nil { - groupname = parts[1] - } else { - gid = uint32(v) - } - if username == "" && groupname == "" { - s.Process.User.UID, s.Process.User.GID = uid, gid - return nil - } - f := func(root string) error { - if username != "" { - uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool { - return u.Name == username - }) - if err != nil { - return err - } - } - if groupname != "" { - gid, err = getGIDFromPath(root, func(g user.Group) bool { - return g.Name == groupname - }) - if err != nil { - return err - } - } - s.Process.User.UID, s.Process.User.GID = uid, gid - return nil - } - if c.Snapshotter == "" && c.SnapshotKey == "" { - if !isRootfsAbs(s.Root.Path) { - return errors.New("rootfs absolute path is required") - } - return f(s.Root.Path) - } - if c.Snapshotter == "" { - return errors.New("no snapshotter set for container") - } - if c.SnapshotKey == "" { - return errors.New("rootfs snapshot not created for container") - } - snapshotter := client.SnapshotService(c.Snapshotter) - mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) - if err != nil { - return err - } - return mount.WithTempMount(ctx, mounts, f) - default: - return fmt.Errorf("invalid USER value %s", userstr) - } - } -} - -// WithUIDGID allows the UID and GID for the Process to be set -func WithUIDGID(uid, gid uint32) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.User.UID = uid - s.Process.User.GID = gid - return nil - } -} - -// WithUserID sets the correct UID and GID for the container based -// on the image's /etc/passwd contents. If /etc/passwd does not exist, -// or uid is not found in /etc/passwd, it sets the requested uid, -// additionally sets the gid to 0, and does not return an error. -func WithUserID(uid uint32) SpecOpts { - return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) { - setProcess(s) - if c.Snapshotter == "" && c.SnapshotKey == "" { - if !isRootfsAbs(s.Root.Path) { - return errors.Errorf("rootfs absolute path is required") - } - uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool { - return u.Uid == int(uid) - }) - if err != nil { - if os.IsNotExist(err) || err == errNoUsersFound { - s.Process.User.UID, s.Process.User.GID = uid, 0 - return nil - } - return err - } - s.Process.User.UID, s.Process.User.GID = uuid, ugid - return nil - - } - if c.Snapshotter == "" { - return errors.Errorf("no snapshotter set for container") - } - if c.SnapshotKey == "" { - return errors.Errorf("rootfs snapshot not created for container") - } - snapshotter := client.SnapshotService(c.Snapshotter) - mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) - if err != nil { - return err - } - return mount.WithTempMount(ctx, mounts, func(root string) error { - uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool { - return u.Uid == int(uid) - }) - if err != nil { - if os.IsNotExist(err) || err == errNoUsersFound { - s.Process.User.UID, s.Process.User.GID = uid, 0 - return nil - } - return err - } - s.Process.User.UID, s.Process.User.GID = uuid, ugid - return nil - }) - } -} - -// WithUsername sets the correct UID and GID for the container -// based on the the image's /etc/passwd contents. If /etc/passwd -// does not exist, or the username is not found in /etc/passwd, -// it returns error. -func WithUsername(username string) SpecOpts { - return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) { - setProcess(s) - if c.Snapshotter == "" && c.SnapshotKey == "" { - if !isRootfsAbs(s.Root.Path) { - return errors.Errorf("rootfs absolute path is required") - } - uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool { - return u.Name == username - }) - if err != nil { - return err - } - s.Process.User.UID, s.Process.User.GID = uid, gid - return nil - } - if c.Snapshotter == "" { - return errors.Errorf("no snapshotter set for container") - } - if c.SnapshotKey == "" { - return errors.Errorf("rootfs snapshot not created for container") - } - snapshotter := client.SnapshotService(c.Snapshotter) - mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) - if err != nil { - return err - } - return mount.WithTempMount(ctx, mounts, func(root string) error { - uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool { - return u.Name == username - }) - if err != nil { - return err - } - s.Process.User.UID, s.Process.User.GID = uid, gid - return nil - }) - } -} - -// WithCapabilities sets Linux capabilities on the process -func WithCapabilities(caps []string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setCapabilities(s) - - s.Process.Capabilities.Bounding = caps - s.Process.Capabilities.Effective = caps - s.Process.Capabilities.Permitted = caps - s.Process.Capabilities.Inheritable = caps - - return nil - } -} - -// WithAllCapabilities sets all linux capabilities for the process -var WithAllCapabilities = WithCapabilities(getAllCapabilities()) - -func getAllCapabilities() []string { - last := capability.CAP_LAST_CAP - // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap - if last == capability.Cap(63) { - last = capability.CAP_BLOCK_SUSPEND - } - var caps []string - for _, cap := range capability.List() { - if cap > last { - continue - } - caps = append(caps, "CAP_"+strings.ToUpper(cap.String())) - } - return caps -} - -// WithAmbientCapabilities set the Linux ambient capabilities for the process -// Ambient capabilities should only be set for non-root users or the caller should -// understand how these capabilities are used and set -func WithAmbientCapabilities(caps []string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setCapabilities(s) - - s.Process.Capabilities.Ambient = caps - return nil - } -} - -var errNoUsersFound = errors.New("no users found") - -func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) { - ppath, err := fs.RootPath(root, "/etc/passwd") - if err != nil { - return 0, 0, err - } - users, err := user.ParsePasswdFileFilter(ppath, filter) - if err != nil { - return 0, 0, err - } - if len(users) == 0 { - return 0, 0, errNoUsersFound - } - u := users[0] - return uint32(u.Uid), uint32(u.Gid), nil -} - -var errNoGroupsFound = errors.New("no groups found") - -func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) { - gpath, err := fs.RootPath(root, "/etc/group") - if err != nil { - return 0, err - } - groups, err := user.ParseGroupFileFilter(gpath, filter) - if err != nil { - return 0, err - } - if len(groups) == 0 { - return 0, errNoGroupsFound - } - g := groups[0] - return uint32(g.Gid), nil -} - -func isRootfsAbs(root string) bool { - return filepath.IsAbs(root) -} - -// WithMaskedPaths sets the masked paths option -func WithMaskedPaths(paths []string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - s.Linux.MaskedPaths = paths - return nil - } -} - -// WithReadonlyPaths sets the read only paths option -func WithReadonlyPaths(paths []string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - s.Linux.ReadonlyPaths = paths - return nil - } -} - -// WithWriteableSysfs makes any sysfs mounts writeable -func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - for i, m := range s.Mounts { - if m.Type == "sysfs" { - var options []string - for _, o := range m.Options { - if o == "ro" { - o = "rw" - } - options = append(options, o) - } - s.Mounts[i].Options = options - } - } - return nil -} - -// WithWriteableCgroupfs makes any cgroup mounts writeable -func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - for i, m := range s.Mounts { - if m.Type == "cgroup" { - var options []string - for _, o := range m.Options { - if o == "ro" { - o = "rw" - } - options = append(options, o) - } - s.Mounts[i].Options = options - } - } - return nil -} - -// WithSelinuxLabel sets the process SELinux label -func WithSelinuxLabel(label string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.SelinuxLabel = label - return nil - } -} - -// WithApparmorProfile sets the Apparmor profile for the process -func WithApparmorProfile(profile string) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.ApparmorProfile = profile - return nil - } -} - -// WithSeccompUnconfined clears the seccomp profile -func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - s.Linux.Seccomp = nil - return nil -} - -// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's -// allowed and denied devices -func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - if s.Linux.Resources == nil { - s.Linux.Resources = &specs.LinuxResources{} - } - s.Linux.Resources.Devices = nil - return nil -} - -// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to -// the container's resource cgroup spec -func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setLinux(s) - if s.Linux.Resources == nil { - s.Linux.Resources = &specs.LinuxResources{} - } - intptr := func(i int64) *int64 { - return &i - } - s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{ - { - // "/dev/null", - Type: "c", - Major: intptr(1), - Minor: intptr(3), - Access: rwm, - Allow: true, - }, - { - // "/dev/random", - Type: "c", - Major: intptr(1), - Minor: intptr(8), - Access: rwm, - Allow: true, - }, - { - // "/dev/full", - Type: "c", - Major: intptr(1), - Minor: intptr(7), - Access: rwm, - Allow: true, - }, - { - // "/dev/tty", - Type: "c", - Major: intptr(5), - Minor: intptr(0), - Access: rwm, - Allow: true, - }, - { - // "/dev/zero", - Type: "c", - Major: intptr(1), - Minor: intptr(5), - Access: rwm, - Allow: true, - }, - { - // "/dev/urandom", - Type: "c", - Major: intptr(1), - Minor: intptr(9), - Access: rwm, - Allow: true, - }, - { - // "/dev/console", - Type: "c", - Major: intptr(5), - Minor: intptr(1), - Access: rwm, - Allow: true, - }, - // /dev/pts/ - pts namespaces are "coming soon" - { - Type: "c", - Major: intptr(136), - Access: rwm, - Allow: true, - }, - { - Type: "c", - Major: intptr(5), - Minor: intptr(2), - Access: rwm, - Allow: true, - }, - { - // tuntap - Type: "c", - Major: intptr(10), - Minor: intptr(200), - Access: rwm, - Allow: true, - }, - }...) - return nil -} - -// WithPrivileged sets up options for a privileged container -// TODO(justincormack) device handling -var WithPrivileged = Compose( - WithAllCapabilities, - WithMaskedPaths(nil), - WithReadonlyPaths(nil), - WithWriteableSysfs, - WithWriteableCgroupfs, - WithSelinuxLabel(""), - WithApparmorProfile(""), - WithSeccompUnconfined, -) diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts_windows.go b/vendor/github.com/containerd/containerd/oci/spec_opts_windows.go deleted file mode 100644 index 3688a582d2..0000000000 --- a/vendor/github.com/containerd/containerd/oci/spec_opts_windows.go +++ /dev/null @@ -1,89 +0,0 @@ -// +build windows - -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package oci - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/containerd/containerd/containers" - "github.com/containerd/containerd/content" - "github.com/containerd/containerd/images" - "github.com/opencontainers/image-spec/specs-go/v1" - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -// WithImageConfig configures the spec to from the configuration of an Image -func WithImageConfig(image Image) SpecOpts { - return func(ctx context.Context, client Client, _ *containers.Container, s *Spec) error { - setProcess(s) - ic, err := image.Config(ctx) - if err != nil { - return 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 err - } - if err := json.Unmarshal(p, &ociimage); err != nil { - return err - } - config = ociimage.Config - default: - return fmt.Errorf("unknown image config media type %s", ic.MediaType) - } - s.Process.Env = config.Env - s.Process.Args = append(config.Entrypoint, config.Cmd...) - s.Process.User = specs.User{ - Username: config.User, - } - return nil - } -} - -// WithTTY sets the information on the spec as well as the environment variables for -// using a TTY -func WithTTY(width, height int) SpecOpts { - return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { - setProcess(s) - s.Process.Terminal = true - if s.Process.ConsoleSize == nil { - s.Process.ConsoleSize = &specs.Box{} - } - s.Process.ConsoleSize.Width = uint(width) - s.Process.ConsoleSize.Height = uint(height) - return nil - } -} - -// WithUsername sets the username on the process -func WithUsername(username string) SpecOpts { - return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error { - setProcess(s) - s.Process.User.Username = username - return nil - } -} diff --git a/vendor/github.com/containerd/containerd/oci/spec_unix.go b/vendor/github.com/containerd/containerd/oci/spec_unix.go deleted file mode 100644 index cb69434cba..0000000000 --- a/vendor/github.com/containerd/containerd/oci/spec_unix.go +++ /dev/null @@ -1,188 +0,0 @@ -// +build !windows - -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package oci - -import ( - "context" - "path/filepath" - - "github.com/containerd/containerd/namespaces" - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -const ( - rwm = "rwm" - defaultRootfsPath = "rootfs" -) - -var ( - defaultEnv = []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - } -) - -func defaultCaps() []string { - return []string{ - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FSETID", - "CAP_FOWNER", - "CAP_MKNOD", - "CAP_NET_RAW", - "CAP_SETGID", - "CAP_SETUID", - "CAP_SETFCAP", - "CAP_SETPCAP", - "CAP_NET_BIND_SERVICE", - "CAP_SYS_CHROOT", - "CAP_KILL", - "CAP_AUDIT_WRITE", - } -} - -func defaultNamespaces() []specs.LinuxNamespace { - return []specs.LinuxNamespace{ - { - Type: specs.PIDNamespace, - }, - { - Type: specs.IPCNamespace, - }, - { - Type: specs.UTSNamespace, - }, - { - Type: specs.MountNamespace, - }, - { - Type: specs.NetworkNamespace, - }, - } -} - -func populateDefaultSpec(ctx context.Context, s *Spec, id string) error { - ns, err := namespaces.NamespaceRequired(ctx) - if err != nil { - return err - } - - *s = Spec{ - Version: specs.Version, - Root: &specs.Root{ - Path: defaultRootfsPath, - }, - Process: &specs.Process{ - Env: defaultEnv, - Cwd: "/", - NoNewPrivileges: true, - User: specs.User{ - UID: 0, - GID: 0, - }, - Capabilities: &specs.LinuxCapabilities{ - Bounding: defaultCaps(), - Permitted: defaultCaps(), - Inheritable: defaultCaps(), - Effective: defaultCaps(), - }, - Rlimits: []specs.POSIXRlimit{ - { - Type: "RLIMIT_NOFILE", - Hard: uint64(1024), - Soft: uint64(1024), - }, - }, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "proc", - Source: "proc", - }, - { - Destination: "/dev", - Type: "tmpfs", - Source: "tmpfs", - Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, - }, - { - Destination: "/dev/pts", - Type: "devpts", - Source: "devpts", - Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}, - }, - { - Destination: "/dev/shm", - Type: "tmpfs", - Source: "shm", - Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}, - }, - { - Destination: "/dev/mqueue", - Type: "mqueue", - Source: "mqueue", - Options: []string{"nosuid", "noexec", "nodev"}, - }, - { - Destination: "/sys", - Type: "sysfs", - Source: "sysfs", - Options: []string{"nosuid", "noexec", "nodev", "ro"}, - }, - { - Destination: "/run", - Type: "tmpfs", - Source: "tmpfs", - Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, - }, - }, - Linux: &specs.Linux{ - MaskedPaths: []string{ - "/proc/acpi", - "/proc/kcore", - "/proc/keys", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/sys/firmware", - "/proc/scsi", - }, - ReadonlyPaths: []string{ - "/proc/asound", - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger", - }, - CgroupsPath: filepath.Join("/", ns, id), - Resources: &specs.LinuxResources{ - Devices: []specs.LinuxDeviceCgroup{ - { - Allow: false, - Access: rwm, - }, - }, - }, - Namespaces: defaultNamespaces(), - }, - } - return nil -} diff --git a/vendor/github.com/containerd/containerd/platforms/defaults.go b/vendor/github.com/containerd/containerd/platforms/defaults.go index b8d9c8277f..a14d80e58c 100644 --- a/vendor/github.com/containerd/containerd/platforms/defaults.go +++ b/vendor/github.com/containerd/containerd/platforms/defaults.go @@ -22,11 +22,6 @@ import ( specs "github.com/opencontainers/image-spec/specs-go/v1" ) -// Default returns the default matcher for the platform. -func Default() MatchComparer { - return Only(DefaultSpec()) -} - // DefaultString returns the default string specifier for the platform. func DefaultString() string { return Format(DefaultSpec()) diff --git a/vendor/github.com/containerd/containerd/oci/spec_windows.go b/vendor/github.com/containerd/containerd/platforms/defaults_unix.go similarity index 54% rename from vendor/github.com/containerd/containerd/oci/spec_windows.go rename to vendor/github.com/containerd/containerd/platforms/defaults_unix.go index d0236585dc..e8a7d5ffa0 100644 --- a/vendor/github.com/containerd/containerd/oci/spec_windows.go +++ b/vendor/github.com/containerd/containerd/platforms/defaults_unix.go @@ -1,3 +1,5 @@ +// +build !windows + /* Copyright The containerd Authors. @@ -14,31 +16,9 @@ limitations under the License. */ -package oci +package platforms -import ( - "context" - - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -func populateDefaultSpec(ctx context.Context, s *Spec, id string) error { - *s = Spec{ - Version: specs.Version, - Root: &specs.Root{}, - Process: &specs.Process{ - Cwd: `C:\`, - ConsoleSize: &specs.Box{ - Width: 80, - Height: 20, - }, - }, - Windows: &specs.Windows{ - IgnoreFlushesDuringBoot: true, - Network: &specs.WindowsNetwork{ - AllowUnqualifiedDNSQuery: true, - }, - }, - } - return nil +// Default returns the default matcher for the platform. +func Default() MatchComparer { + return Only(DefaultSpec()) } diff --git a/vendor/github.com/containerd/containerd/task_opts_windows.go b/vendor/github.com/containerd/containerd/platforms/defaults_windows.go similarity index 63% rename from vendor/github.com/containerd/containerd/task_opts_windows.go rename to vendor/github.com/containerd/containerd/platforms/defaults_windows.go index 60836bc8fa..0defbd36c0 100644 --- a/vendor/github.com/containerd/containerd/task_opts_windows.go +++ b/vendor/github.com/containerd/containerd/platforms/defaults_windows.go @@ -1,3 +1,5 @@ +// +build windows + /* Copyright The containerd Authors. @@ -14,18 +16,16 @@ limitations under the License. */ -package containerd +package platforms import ( - "context" - - specs "github.com/opencontainers/runtime-spec/specs-go" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) -// WithResources sets the provided resources on the spec for task updates -func WithResources(resources *specs.WindowsResources) UpdateTaskOpts { - return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { - r.Resources = resources - return nil - } +// Default returns the default matcher for the platform. +func Default() MatchComparer { + return Ordered(DefaultSpec(), specs.Platform{ + OS: "linux", + Architecture: "amd64", + }) } diff --git a/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go b/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go index 1509e696cd..4a2ce3c393 100644 --- a/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go +++ b/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go @@ -117,7 +117,7 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int } } else { // TODO: Should any cases where use of content range - // without the proper header be considerd? + // without the proper header be considered? // 206 responses? // Discard up to offset diff --git a/vendor/github.com/containerd/containerd/remotes/docker/httpreadseeker.go b/vendor/github.com/containerd/containerd/remotes/docker/httpreadseeker.go index 5a77789537..9175b6a7a4 100644 --- a/vendor/github.com/containerd/containerd/remotes/docker/httpreadseeker.go +++ b/vendor/github.com/containerd/containerd/remotes/docker/httpreadseeker.go @@ -134,7 +134,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) { // There is an edge case here where offset == size of the content. If // we seek, we will probably get an error for content that cannot be // sought (?). In that case, we should err on committing the content, - // as the length is already satisified but we just return the empty + // as the length is already satisfied but we just return the empty // reader instead. hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{})) diff --git a/vendor/github.com/containerd/containerd/remotes/docker/schema1/converter.go b/vendor/github.com/containerd/containerd/remotes/docker/schema1/converter.go index 3155d6ec35..45ac1933fd 100644 --- a/vendor/github.com/containerd/containerd/remotes/docker/schema1/converter.go +++ b/vendor/github.com/containerd/containerd/remotes/docker/schema1/converter.go @@ -272,8 +272,14 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro return err } - // TODO: Check if blob -> diff id mapping already exists - // TODO: Check if blob empty label exists + reuse, err := c.reuseLabelBlobState(ctx, desc) + if err != nil { + return err + } + + if reuse { + return nil + } ra, err := c.contentStore.ReaderAt(ctx, desc) if err != nil { @@ -343,6 +349,17 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro state := calc.State() + cinfo := content.Info{ + Digest: desc.Digest, + Labels: map[string]string{ + "containerd.io/uncompressed": state.diffID.String(), + }, + } + + if _, err := c.contentStore.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil { + return errors.Wrap(err, "failed to update uncompressed label") + } + c.mu.Lock() c.blobMap[desc.Digest] = state c.layerBlobs[state.diffID] = desc @@ -351,6 +368,40 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro return nil } +func (c *Converter) reuseLabelBlobState(ctx context.Context, desc ocispec.Descriptor) (bool, error) { + cinfo, err := c.contentStore.Info(ctx, desc.Digest) + if err != nil { + return false, errors.Wrap(err, "failed to get blob info") + } + desc.Size = cinfo.Size + + diffID, ok := cinfo.Labels["containerd.io/uncompressed"] + if !ok { + return false, nil + } + + bState := blobState{empty: false} + + if bState.diffID, err = digest.Parse(diffID); err != nil { + log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label containerd.io/uncompressed: %v", diffID) + return false, nil + } + + // NOTE: there is no need to read header to get compression method + // because there are only two kinds of methods. + if bState.diffID == desc.Digest { + desc.MediaType = images.MediaTypeDockerSchema2Layer + } else { + desc.MediaType = images.MediaTypeDockerSchema2LayerGzip + } + + c.mu.Lock() + c.blobMap[desc.Digest] = bState + c.layerBlobs[bState.diffID] = desc + c.mu.Unlock() + return true, nil +} + func (c *Converter) schema1ManifestHistory() ([]ocispec.History, []digest.Digest, error) { if c.pulledManifest == nil { return nil, nil, errors.New("missing schema 1 manifest for conversion") diff --git a/vendor/github.com/containerd/containerd/runtime/restart/restart.go b/vendor/github.com/containerd/containerd/runtime/restart/restart.go deleted file mode 100644 index 47b98e003e..0000000000 --- a/vendor/github.com/containerd/containerd/runtime/restart/restart.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -// Package restart enables containers to have labels added and monitored to -// keep the container's task running if it is killed. -// -// Setting the StatusLabel on a container instructs the restart monitor to keep -// that container's task in a specific status. -// Setting the LogPathLabel on a container will setup the task's IO to be redirected -// to a log file when running a task within the restart manager. -// -// The restart labels can be cleared off of a container using the WithNoRestarts Opt. -// -// The restart monitor has one option in the containerd config under the [plugins.restart] -// section. `interval = "10s" sets the reconcile interval that the restart monitor checks -// for task state and reconciles the desired status for that task. -package restart - -import ( - "context" - - "github.com/containerd/containerd" - "github.com/containerd/containerd/containers" -) - -const ( - // StatusLabel sets the restart status label for a container - StatusLabel = "containerd.io/restart.status" - // LogPathLabel sets the restart log path label for a container - LogPathLabel = "containerd.io/restart.logpath" -) - -// WithLogPath sets the log path for a container -func WithLogPath(path string) func(context.Context, *containerd.Client, *containers.Container) error { - return func(_ context.Context, _ *containerd.Client, c *containers.Container) error { - ensureLabels(c) - c.Labels[LogPathLabel] = path - return nil - } -} - -// WithStatus sets the status for a container -func WithStatus(status containerd.ProcessStatus) func(context.Context, *containerd.Client, *containers.Container) error { - return func(_ context.Context, _ *containerd.Client, c *containers.Container) error { - ensureLabels(c) - c.Labels[StatusLabel] = string(status) - return nil - } -} - -// WithNoRestarts clears any restart information from the container -func WithNoRestarts(_ context.Context, _ *containerd.Client, c *containers.Container) error { - if c.Labels == nil { - return nil - } - delete(c.Labels, StatusLabel) - delete(c.Labels, LogPathLabel) - return nil -} - -func ensureLabels(c *containers.Container) { - if c.Labels == nil { - c.Labels = make(map[string]string) - } -} diff --git a/vendor/github.com/containerd/containerd/sys/socket_unix.go b/vendor/github.com/containerd/containerd/sys/socket_unix.go index 0dbca0e333..90fa55c482 100644 --- a/vendor/github.com/containerd/containerd/sys/socket_unix.go +++ b/vendor/github.com/containerd/containerd/sys/socket_unix.go @@ -42,7 +42,7 @@ func CreateUnixSocket(path string) (net.Listener, error) { return net.Listen("unix", path) } -// GetLocalListener returns a listerner out of a unix socket. +// GetLocalListener returns a listener out of a unix socket. func GetLocalListener(path string, uid, gid int) (net.Listener, error) { // Ensure parent directory is created if err := mkdirAs(filepath.Dir(path), uid, gid); err != nil { diff --git a/vendor/github.com/containerd/containerd/task_opts.go b/vendor/github.com/containerd/containerd/task_opts.go index 9e998a3498..ce861ea51d 100644 --- a/vendor/github.com/containerd/containerd/task_opts.go +++ b/vendor/github.com/containerd/containerd/task_opts.go @@ -18,10 +18,18 @@ package containerd import ( "context" + "encoding/json" + "fmt" "syscall" + "github.com/containerd/containerd/api/types" + "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/images" "github.com/containerd/containerd/mount" + imagespec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) // NewTaskOpts allows the caller to set options on a new task @@ -35,6 +43,44 @@ func WithRootFS(mounts []mount.Mount) NewTaskOpts { } } +// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a +// previous checkpoint. Additional software such as CRIU may be required to +// restore a task from a checkpoint +func WithTaskCheckpoint(im Image) NewTaskOpts { + return func(ctx context.Context, c *Client, info *TaskInfo) error { + desc := im.Target() + id := desc.Digest + index, err := decodeIndex(ctx, c.ContentStore(), desc) + if err != nil { + return err + } + for _, m := range index.Manifests { + if m.MediaType == images.MediaTypeContainerd1Checkpoint { + info.Checkpoint = &types.Descriptor{ + MediaType: m.MediaType, + Size_: m.Size, + Digest: m.Digest, + } + return nil + } + } + return fmt.Errorf("checkpoint not found in index %s", id) + } +} + +func decodeIndex(ctx context.Context, store content.Provider, desc imagespec.Descriptor) (*imagespec.Index, error) { + var index imagespec.Index + p, err := content.ReadBlob(ctx, store, desc) + if err != nil { + return nil, err + } + if err := json.Unmarshal(p, &index); err != nil { + return nil, err + } + + return &index, nil +} + // WithCheckpointName sets the image name for the checkpoint func WithCheckpointName(name string) CheckpointTaskOpts { return func(r *CheckpointTaskInfo) error { @@ -92,3 +138,19 @@ func WithKillExecID(execID string) KillOpts { return nil } } + +// WithResources sets the provided resources for task updates. Resources must be +// either a *specs.LinuxResources or a *specs.WindowsResources +func WithResources(resources interface{}) UpdateTaskOpts { + return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { + switch resources.(type) { + case *specs.LinuxResources: + case *specs.WindowsResources: + default: + return errors.New("WithResources requires a *specs.LinuxResources or *specs.WindowsResources") + } + + r.Resources = resources + return nil + } +} diff --git a/vendor/github.com/containerd/containerd/task_opts_linux.go b/vendor/github.com/containerd/containerd/task_opts_unix.go similarity index 72% rename from vendor/github.com/containerd/containerd/task_opts_linux.go rename to vendor/github.com/containerd/containerd/task_opts_unix.go index 551cb996c1..f8652be3bc 100644 --- a/vendor/github.com/containerd/containerd/task_opts_linux.go +++ b/vendor/github.com/containerd/containerd/task_opts_unix.go @@ -1,3 +1,5 @@ +// +build !windows + /* Copyright The containerd Authors. @@ -18,20 +20,11 @@ package containerd import ( "context" - "errors" "github.com/containerd/containerd/runtime/linux/runctypes" - "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) -// WithResources sets the provided resources for task updates -func WithResources(resources *specs.LinuxResources) UpdateTaskOpts { - return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { - r.Resources = resources - return nil - } -} - // WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage. // There is an upper limit on the number of keyrings in a linux system func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error { @@ -46,3 +39,19 @@ func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error { opts.NoNewKeyring = true return nil } + +// WithNoPivotRoot instructs the runtime not to you pivot_root +func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error { + if info.Options == nil { + info.Options = &runctypes.CreateOptions{ + NoPivotRoot: true, + } + return nil + } + opts, ok := info.Options.(*runctypes.CreateOptions) + if !ok { + return errors.New("invalid options type, expected runctypes.CreateOptions") + } + opts.NoPivotRoot = true + return nil +} diff --git a/vendor/github.com/containerd/containerd/vendor.conf b/vendor/github.com/containerd/containerd/vendor.conf index 094a1bed5a..657cd20aec 100644 --- a/vendor/github.com/containerd/containerd/vendor.conf +++ b/vendor/github.com/containerd/containerd/vendor.conf @@ -1,10 +1,10 @@ -github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292 +github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244 -github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b +github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537 github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 @@ -71,7 +71,7 @@ github.com/xeipuuv/gojsonschema 1d523034197ff1f222f6429836dd36a2457a1874 golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067 golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631 gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 -gopkg.in/yaml.v2 53feefa2559fb8dfa8d81baad31be332c97d6c77 +gopkg.in/yaml.v2 v2.2.1 k8s.io/api 9e5ffd1f1320950b238cfce291b926411f0af722 k8s.io/apimachinery ed135c5b96450fd24e5e981c708114fbbd950697 k8s.io/apiserver a90e3a95c2e91b944bfca8225c4e0d12e42a9eb5 @@ -85,4 +85,4 @@ github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2 github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd # aufs dependencies -github.com/containerd/aufs a7fbd554da7a9eafbe5a460a421313a9fd18d988 +github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92