mirror of https://github.com/docker/cli.git
Install binaries on host for upgrade
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
78c42cf031
commit
cfec8027ed
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/docker/licensing/model"
|
"github.com/docker/licensing/model"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type activateOptions struct {
|
type activateOptions struct {
|
||||||
|
@ -67,6 +68,9 @@ https://hub.docker.com/ then specify the file with the '--license' flag.
|
||||||
}
|
}
|
||||||
|
|
||||||
func runActivate(cli command.Cli, options activateOptions) error {
|
func runActivate(cli command.Cli, options activateOptions) error {
|
||||||
|
if unix.Geteuid() != 0 {
|
||||||
|
return errors.New("must be privileged to activate engine")
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := cli.NewContainerizedEngineClient(options.sockPath)
|
client, err := cli.NewContainerizedEngineClient(options.sockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
|
||||||
clitypes "github.com/docker/cli/types"
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -50,67 +49,63 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCheck(dockerCli command.Cli, options checkOptions) error {
|
func runCheck(dockerCli command.Cli, options checkOptions) error {
|
||||||
ctx := context.Background()
|
if unix.Geteuid() != 0 {
|
||||||
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
return errors.New("must be privileged to activate engine")
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to access local containerd")
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
currentOpts, err := client.GetCurrentEngineVersion(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// override with user provided prefix if specified
|
/*
|
||||||
if options.registryPrefix != "" {
|
ctx := context.Background()
|
||||||
currentOpts.RegistryPrefix = options.registryPrefix
|
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
||||||
}
|
if err != nil {
|
||||||
imageName := currentOpts.RegistryPrefix + "/" + currentOpts.EngineImage
|
return errors.Wrap(err, "unable to access local containerd")
|
||||||
currentVersion := currentOpts.EngineVersion
|
}
|
||||||
versions, err := client.GetEngineVersions(ctx, dockerCli.RegistryClient(false), currentVersion, imageName)
|
defer client.Close()
|
||||||
if err != nil {
|
versions, err := client.GetEngineVersions(ctx, dockerCli.RegistryClient(false), currentVersion, imageName)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
availUpdates := []clitypes.Update{
|
availUpdates := []clitypes.Update{
|
||||||
{Type: "current", Version: currentVersion},
|
{Type: "current", Version: currentVersion},
|
||||||
}
|
}
|
||||||
if len(versions.Patches) > 0 {
|
if len(versions.Patches) > 0 {
|
||||||
availUpdates = append(availUpdates,
|
availUpdates = append(availUpdates,
|
||||||
processVersions(
|
processVersions(
|
||||||
currentVersion,
|
currentVersion,
|
||||||
"patch",
|
"patch",
|
||||||
options.preReleases,
|
options.preReleases,
|
||||||
versions.Patches)...)
|
versions.Patches)...)
|
||||||
}
|
}
|
||||||
if options.upgrades {
|
if options.upgrades {
|
||||||
availUpdates = append(availUpdates,
|
availUpdates = append(availUpdates,
|
||||||
processVersions(
|
processVersions(
|
||||||
currentVersion,
|
currentVersion,
|
||||||
"upgrade",
|
"upgrade",
|
||||||
options.preReleases,
|
options.preReleases,
|
||||||
versions.Upgrades)...)
|
versions.Upgrades)...)
|
||||||
}
|
}
|
||||||
if options.downgrades {
|
if options.downgrades {
|
||||||
availUpdates = append(availUpdates,
|
availUpdates = append(availUpdates,
|
||||||
processVersions(
|
processVersions(
|
||||||
currentVersion,
|
currentVersion,
|
||||||
"downgrade",
|
"downgrade",
|
||||||
options.preReleases,
|
options.preReleases,
|
||||||
versions.Downgrades)...)
|
versions.Downgrades)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := options.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
}
|
}
|
||||||
|
|
||||||
updatesCtx := formatter.Context{
|
updatesCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewUpdatesFormat(format, options.quiet),
|
Format: formatter.NewUpdatesFormat(format, options.quiet),
|
||||||
Trunc: false,
|
Trunc: false,
|
||||||
}
|
}
|
||||||
return formatter.UpdatesWrite(updatesCtx, availUpdates)
|
return formatter.UpdatesWrite(updatesCtx, availUpdates)
|
||||||
|
*/
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processVersions(currentVersion, verType string,
|
func processVersions(currentVersion, verType string,
|
||||||
|
|
|
@ -15,11 +15,9 @@ func NewEngineCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCli.Err()),
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newInitCommand(dockerCli),
|
|
||||||
newActivateCommand(dockerCli),
|
newActivateCommand(dockerCli),
|
||||||
newCheckForUpdatesCommand(dockerCli),
|
newCheckForUpdatesCommand(dockerCli),
|
||||||
newUpdateCommand(dockerCli),
|
newUpdateCommand(dockerCli),
|
||||||
newRmCommand(dockerCli),
|
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
@ -32,6 +33,9 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
|
func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
|
||||||
|
if unix.Geteuid() != 0 {
|
||||||
|
return errors.New("must be privileged to activate engine")
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -39,15 +43,11 @@ func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
if options.EngineImage == "" || options.RegistryPrefix == "" {
|
if options.EngineImage == "" || options.RegistryPrefix == "" {
|
||||||
currentOpts, err := client.GetCurrentEngineVersion(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if options.EngineImage == "" {
|
if options.EngineImage == "" {
|
||||||
options.EngineImage = currentOpts.EngineImage
|
options.EngineImage = "docker/engine-community"
|
||||||
}
|
}
|
||||||
if options.RegistryPrefix == "" {
|
if options.RegistryPrefix == "" {
|
||||||
options.RegistryPrefix = currentOpts.RegistryPrefix
|
options.RegistryPrefix = "docker.io"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
|
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
|
||||||
|
|
|
@ -13,10 +13,7 @@ import (
|
||||||
const (
|
const (
|
||||||
containerdSockPath = "/run/containerd/containerd.sock"
|
containerdSockPath = "/run/containerd/containerd.sock"
|
||||||
engineContainerName = "dockerd"
|
engineContainerName = "dockerd"
|
||||||
engineNamespace = "docker"
|
engineNamespace = "com.docker"
|
||||||
|
|
||||||
// Used to signal the containerd-proxy if it should manage
|
|
||||||
proxyLabel = "com.docker/containerd-proxy.scope"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -82,4 +79,5 @@ type containerdClient interface {
|
||||||
Close() error
|
Close() error
|
||||||
ContentStore() content.Store
|
ContentStore() content.Store
|
||||||
ContainerService() containers.Store
|
ContainerService() containers.Store
|
||||||
|
Install(context.Context, containerd.Image, ...containerd.InstallOpts) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,73 +3,20 @@ package containerizedengine
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/docker/cli/internal/pkg/containerized"
|
|
||||||
clitypes "github.com/docker/cli/types"
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/docker/distribution/reference"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/pkg/errors"
|
"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
|
// ActivateEngine will switch the image from the CE to EE image
|
||||||
func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
|
func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
|
||||||
authConfig *types.AuthConfig, healthfn func(context.Context) error) error {
|
authConfig *types.AuthConfig, healthfn func(context.Context) error) error {
|
||||||
|
|
||||||
// set the proxy scope to "ee" for activate flows
|
|
||||||
opts.Scope = "ee"
|
|
||||||
|
|
||||||
ctx = namespaces.WithNamespace(ctx, engineNamespace)
|
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)
|
return c.DoUpdate(ctx, opts, out, authConfig, healthfn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,31 +48,5 @@ func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptio
|
||||||
return errors.Wrapf(err, "unable to check for image %s", imageName)
|
return errors.Wrapf(err, "unable to check for image %s", imageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return c.cclient.Install(ctx, image, containerd.WithInstallReplace, containerd.WithInstallPath("/usr"))
|
||||||
// Gather information about the existing engine so we can recreate it
|
|
||||||
engine, err := c.GetEngine(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if err == ErrEngineNotPresent {
|
|
||||||
return errors.Wrap(err, "unable to find existing engine - please use init")
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,6 @@ type ContainerizedClient interface {
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig,
|
||||||
healthfn func(context.Context) error) error
|
healthfn func(context.Context) error) error
|
||||||
GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, currentVersion, imageName string) (AvailableVersions, error)
|
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
|
// EngineInitOptions contains the configuration settings
|
||||||
|
@ -48,7 +46,6 @@ type EngineInitOptions struct {
|
||||||
EngineImage string
|
EngineImage string
|
||||||
EngineVersion string
|
EngineVersion string
|
||||||
ConfigFile string
|
ConfigFile string
|
||||||
Scope string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvailableVersions groups the available versions which were discovered
|
// AvailableVersions groups the available versions which were discovered
|
||||||
|
|
|
@ -3,7 +3,7 @@ github.com/asaskevich/govalidator f9ffefc3facfbe0caee3fea233cbb6e8208f4541
|
||||||
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
||||||
github.com/beorn7/perks 3a771d992973f24aa725d07868b467d1ddfceafb
|
github.com/beorn7/perks 3a771d992973f24aa725d07868b467d1ddfceafb
|
||||||
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
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/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371
|
||||||
github.com/containerd/fifo 3d5202a
|
github.com/containerd/fifo 3d5202a
|
||||||
github.com/containerd/typeurl f694355
|
github.com/containerd/typeurl f694355
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/containerd/containerd?status.svg)](https://godoc.org/github.com/containerd/containerd)
|
[![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)
|
[![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)
|
[![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)
|
[![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)
|
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1271/badge)](https://bestpractices.coreinfrastructure.org/projects/1271)
|
||||||
|
|
|
@ -141,7 +141,7 @@ type EventsClient interface {
|
||||||
// Forward sends an event that has already been packaged into an envelope
|
// Forward sends an event that has already been packaged into an envelope
|
||||||
// with a timestamp and namespace.
|
// 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.
|
// behalf of another component, namespace or publisher.
|
||||||
Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*google_protobuf2.Empty, error)
|
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
|
// 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
|
// Forward sends an event that has already been packaged into an envelope
|
||||||
// with a timestamp and namespace.
|
// 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.
|
// behalf of another component, namespace or publisher.
|
||||||
Forward(context.Context, *ForwardRequest) (*google_protobuf2.Empty, error)
|
Forward(context.Context, *ForwardRequest) (*google_protobuf2.Empty, error)
|
||||||
// Subscribe to a stream of events, possibly returning only that match any
|
// Subscribe to a stream of events, possibly returning only that match any
|
||||||
|
|
|
@ -20,7 +20,7 @@ service Events {
|
||||||
// Forward sends an event that has already been packaged into an envelope
|
// Forward sends an event that has already been packaged into an envelope
|
||||||
// with a timestamp and namespace.
|
// 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.
|
// behalf of another component, namespace or publisher.
|
||||||
rpc Forward(ForwardRequest) returns (google.protobuf.Empty);
|
rpc Forward(ForwardRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,15 @@ func NewCreator(opts ...Opt) Creator {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
return copyIO(fifos, streams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,25 +20,21 @@ package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containerd/containerd/api/types"
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/containerd/containerd/runtime/linux/runctypes"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
protobuf "github.com/gogo/protobuf/types"
|
protobuf "github.com/gogo/protobuf/types"
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
"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
|
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
|
||||||
// filesystem to be used by a container with user namespaces
|
// filesystem to be used by a container with user namespaces
|
||||||
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
|
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)
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ var _ events.Subscriber = &Exchange{}
|
||||||
|
|
||||||
// Forward accepts an envelope to be direcly distributed on the 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.
|
// when the event is propagated on behalf of another publisher.
|
||||||
func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) {
|
func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) {
|
||||||
if err := validateEnvelope(envelope); err != nil {
|
if err := validateEnvelope(envelope); err != nil {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -22,7 +22,6 @@ import (
|
||||||
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type importOpts struct {
|
type importOpts struct {
|
||||||
|
@ -84,35 +83,3 @@ func (c *Client) Import(ctx context.Context, importer images.Importer, reader io
|
||||||
}
|
}
|
||||||
return images, nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -33,25 +33,14 @@ import (
|
||||||
|
|
||||||
// Install a binary image into the opt service
|
// Install a binary image into the opt service
|
||||||
func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) error {
|
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
|
var config InstallConfig
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&config)
|
o(&config)
|
||||||
}
|
}
|
||||||
|
path, err := c.getInstallPath(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
cs = image.ContentStore()
|
cs = image.ContentStore()
|
||||||
platform = platforms.Default()
|
platform = platforms.Default()
|
||||||
|
@ -89,3 +78,25 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts)
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ type InstallConfig struct {
|
||||||
Libs bool
|
Libs bool
|
||||||
// Replace will overwrite existing binaries or libs in the opt directory
|
// Replace will overwrite existing binaries or libs in the opt directory
|
||||||
Replace bool
|
Replace bool
|
||||||
|
// Path to install libs and binaries to
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithInstallLibs installs libs from the image
|
// WithInstallLibs installs libs from the image
|
||||||
|
@ -36,3 +38,10 @@ func WithInstallLibs(c *InstallConfig) {
|
||||||
func WithInstallReplace(c *InstallConfig) {
|
func WithInstallReplace(c *InstallConfig) {
|
||||||
c.Replace = true
|
c.Replace = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInstallPath sets the optional install path
|
||||||
|
func WithInstallPath(path string) InstallOpts {
|
||||||
|
return func(c *InstallConfig) {
|
||||||
|
c.Path = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,10 @@ var (
|
||||||
|
|
||||||
// Mount to the provided target
|
// Mount to the provided target
|
||||||
func (m *Mount) Mount(target string) error {
|
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)
|
home, layerID := filepath.Split(m.Source)
|
||||||
|
|
||||||
parentLayerPaths, err := m.GetParentPaths()
|
parentLayerPaths, err := m.GetParentPaths()
|
||||||
|
|
|
@ -18,11 +18,27 @@ package oci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/namespaces"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
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
|
// 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
|
// to be created without the "issues" with go vendoring and package imports
|
||||||
type Spec = specs.Spec
|
type Spec = specs.Spec
|
||||||
|
@ -30,12 +46,36 @@ type Spec = specs.Spec
|
||||||
// GenerateSpec will generate a default spec from the provided image
|
// GenerateSpec will generate a default spec from the provided image
|
||||||
// for use as a containerd container
|
// for use as a containerd container
|
||||||
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
||||||
s, err := createDefaultSpec(ctx, c.ID)
|
return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...)
|
||||||
if err != nil {
|
}
|
||||||
|
|
||||||
|
// 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 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
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultSpec(ctx context.Context, id string) (*Spec, error) {
|
func defaultUnixCaps() []string {
|
||||||
var s Spec
|
return []string{
|
||||||
return &s, populateDefaultSpec(ctx, &s, id)
|
"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
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,25 @@ package oci
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"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"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/syndtr/gocapability/capability"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SpecOpts sets spec specific information to a newly generated OCI spec
|
// 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
|
// WithDefaultSpec returns a SpecOpts that will populate the spec with default
|
||||||
// values.
|
// values.
|
||||||
//
|
//
|
||||||
// Use as the first option to clear the spec, then apply options afterwards.
|
// Use as the first option to clear the spec, then apply options afterwards.
|
||||||
func WithDefaultSpec() SpecOpts {
|
func WithDefaultSpec() SpecOpts {
|
||||||
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
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
|
// WithEnv appends environment variables
|
||||||
func WithEnv(environmentVariables []string) SpecOpts {
|
func WithEnv(environmentVariables []string) SpecOpts {
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
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
|
// replaceOrAppendEnvValues returns the defaults with the overrides either
|
||||||
// replaced by env key or appended to the list
|
// replaced by env key or appended to the list
|
||||||
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||||
|
@ -163,3 +174,821 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||||
|
|
||||||
return defaults
|
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,
|
||||||
|
)
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -22,11 +22,6 @@ import (
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
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.
|
// DefaultString returns the default string specifier for the platform.
|
||||||
func DefaultString() string {
|
func DefaultString() string {
|
||||||
return Format(DefaultSpec())
|
return Format(DefaultSpec())
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
@ -14,31 +16,9 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package oci
|
package platforms
|
||||||
|
|
||||||
import (
|
// Default returns the default matcher for the platform.
|
||||||
"context"
|
func Default() MatchComparer {
|
||||||
|
return Only(DefaultSpec())
|
||||||
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
|
|
||||||
}
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
@ -14,18 +16,16 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package containerd
|
package platforms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithResources sets the provided resources on the spec for task updates
|
// Default returns the default matcher for the platform.
|
||||||
func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
|
func Default() MatchComparer {
|
||||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
return Ordered(DefaultSpec(), specs.Platform{
|
||||||
r.Resources = resources
|
OS: "linux",
|
||||||
return nil
|
Architecture: "amd64",
|
||||||
}
|
})
|
||||||
}
|
}
|
|
@ -117,7 +117,7 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Should any cases where use of content range
|
// TODO: Should any cases where use of content range
|
||||||
// without the proper header be considerd?
|
// without the proper header be considered?
|
||||||
// 206 responses?
|
// 206 responses?
|
||||||
|
|
||||||
// Discard up to offset
|
// Discard up to offset
|
||||||
|
|
|
@ -134,7 +134,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||||
// There is an edge case here where offset == size of the content. If
|
// 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
|
// we seek, we will probably get an error for content that cannot be
|
||||||
// sought (?). In that case, we should err on committing the content,
|
// 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.
|
// reader instead.
|
||||||
|
|
||||||
hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
|
|
|
@ -272,8 +272,14 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check if blob -> diff id mapping already exists
|
reuse, err := c.reuseLabelBlobState(ctx, desc)
|
||||||
// TODO: Check if blob empty label exists
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reuse {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ra, err := c.contentStore.ReaderAt(ctx, desc)
|
ra, err := c.contentStore.ReaderAt(ctx, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -343,6 +349,17 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
|
||||||
|
|
||||||
state := calc.State()
|
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.mu.Lock()
|
||||||
c.blobMap[desc.Digest] = state
|
c.blobMap[desc.Digest] = state
|
||||||
c.layerBlobs[state.diffID] = desc
|
c.layerBlobs[state.diffID] = desc
|
||||||
|
@ -351,6 +368,40 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
|
||||||
return nil
|
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) {
|
func (c *Converter) schema1ManifestHistory() ([]ocispec.History, []digest.Digest, error) {
|
||||||
if c.pulledManifest == nil {
|
if c.pulledManifest == nil {
|
||||||
return nil, nil, errors.New("missing schema 1 manifest for conversion")
|
return nil, nil, errors.New("missing schema 1 manifest for conversion")
|
||||||
|
|
|
@ -42,7 +42,7 @@ func CreateUnixSocket(path string) (net.Listener, error) {
|
||||||
return net.Listen("unix", path)
|
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) {
|
func GetLocalListener(path string, uid, gid int) (net.Listener, error) {
|
||||||
// Ensure parent directory is created
|
// Ensure parent directory is created
|
||||||
if err := mkdirAs(filepath.Dir(path), uid, gid); err != nil {
|
if err := mkdirAs(filepath.Dir(path), uid, gid); err != nil {
|
||||||
|
|
|
@ -18,10 +18,18 @@ package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/api/types"
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/mount"
|
"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
|
// 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
|
// WithCheckpointName sets the image name for the checkpoint
|
||||||
func WithCheckpointName(name string) CheckpointTaskOpts {
|
func WithCheckpointName(name string) CheckpointTaskOpts {
|
||||||
return func(r *CheckpointTaskInfo) error {
|
return func(r *CheckpointTaskInfo) error {
|
||||||
|
@ -92,3 +138,19 @@ func WithKillExecID(execID string) KillOpts {
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
@ -18,20 +20,11 @@ package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/runtime/linux/runctypes"
|
"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.
|
// 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
|
// There is an upper limit on the number of keyrings in a linux system
|
||||||
func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
|
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
|
opts.NoNewKeyring = true
|
||||||
return nil
|
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
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292
|
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
||||||
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
||||||
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
|
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
|
||||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||||
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
|
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
|
||||||
github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b
|
github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537
|
||||||
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
||||||
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
|
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
|
||||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||||
|
@ -71,7 +71,7 @@ github.com/xeipuuv/gojsonschema 1d523034197ff1f222f6429836dd36a2457a1874
|
||||||
golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067
|
golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067
|
||||||
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
|
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
|
||||||
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
|
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
|
||||||
gopkg.in/yaml.v2 53feefa2559fb8dfa8d81baad31be332c97d6c77
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
k8s.io/api 9e5ffd1f1320950b238cfce291b926411f0af722
|
k8s.io/api 9e5ffd1f1320950b238cfce291b926411f0af722
|
||||||
k8s.io/apimachinery ed135c5b96450fd24e5e981c708114fbbd950697
|
k8s.io/apimachinery ed135c5b96450fd24e5e981c708114fbbd950697
|
||||||
k8s.io/apiserver a90e3a95c2e91b944bfca8225c4e0d12e42a9eb5
|
k8s.io/apiserver a90e3a95c2e91b944bfca8225c4e0d12e42a9eb5
|
||||||
|
@ -85,4 +85,4 @@ github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
||||||
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
||||||
|
|
||||||
# aufs dependencies
|
# aufs dependencies
|
||||||
github.com/containerd/aufs a7fbd554da7a9eafbe5a460a421313a9fd18d988
|
github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92
|
||||||
|
|
Loading…
Reference in New Issue