| // +build !windows |
| |
| package main |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "os/signal" |
| "path/filepath" |
| "runtime" |
| "strconv" |
| "strings" |
| |
| "golang.org/x/sys/unix" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/containerd/console" |
| containersapi "github.com/containerd/containerd/api/services/containers" |
| "github.com/containerd/containerd/api/services/execution" |
| "github.com/containerd/containerd/api/types/descriptor" |
| "github.com/containerd/containerd/api/types/mount" |
| mountt "github.com/containerd/containerd/mount" |
| protobuf "github.com/gogo/protobuf/types" |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| specs "github.com/opencontainers/runtime-spec/specs-go" |
| "github.com/urfave/cli" |
| ) |
| |
| const ( |
| rwm = "rwm" |
| defaultRootfsPath = "rootfs" |
| ) |
| |
| var capabilities = []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 spec(id string, config *ocispec.ImageConfig, context *cli.Context, rootfs string) (*specs.Spec, error) { |
| defaultEnv := []string{ |
| "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |
| } |
| defaultEnv = append(defaultEnv, config.Env...) |
| cmd := config.Cmd |
| if v := context.Args().Tail(); len(v) > 0 { |
| cmd = v |
| } |
| var ( |
| // TODO: support overriding entrypoint |
| args = append(config.Entrypoint, cmd...) |
| tty = context.Bool("tty") |
| uid, gid uint32 |
| ) |
| if config.User != "" { |
| parts := strings.Split(config.User, ":") |
| switch len(parts) { |
| case 1: |
| v, err := strconv.ParseUint(parts[0], 0, 10) |
| if err != nil { |
| return nil, err |
| } |
| uid, gid = uint32(v), uint32(v) |
| case 2: |
| v, err := strconv.ParseUint(parts[0], 0, 10) |
| if err != nil { |
| return nil, err |
| } |
| uid = uint32(v) |
| if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil { |
| return nil, err |
| } |
| gid = uint32(v) |
| default: |
| return nil, fmt.Errorf("invalid USER value %s", config.User) |
| } |
| } |
| if tty { |
| defaultEnv = append(defaultEnv, "TERM=xterm") |
| } |
| |
| // additional environment vars |
| env := replaceOrAppendEnvValues(defaultEnv, context.StringSlice("env")) |
| |
| cwd := config.WorkingDir |
| if cwd == "" { |
| cwd = "/" |
| } |
| if rootfs == "" { |
| rootfs = defaultRootfsPath |
| } |
| s := &specs.Spec{ |
| Version: specs.Version, |
| Platform: specs.Platform{ |
| OS: runtime.GOOS, |
| Arch: runtime.GOARCH, |
| }, |
| Root: specs.Root{ |
| Path: rootfs, |
| Readonly: context.Bool("readonly"), |
| }, |
| Process: specs.Process{ |
| Args: args, |
| Env: env, |
| Terminal: tty, |
| Cwd: cwd, |
| NoNewPrivileges: true, |
| User: specs.User{ |
| UID: uid, |
| GID: gid, |
| }, |
| Capabilities: &specs.LinuxCapabilities{ |
| Bounding: capabilities, |
| Permitted: capabilities, |
| Inheritable: capabilities, |
| Effective: capabilities, |
| Ambient: capabilities, |
| }, |
| Rlimits: []specs.LinuxRlimit{ |
| { |
| 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"}, |
| }, |
| { |
| Destination: "/etc/resolv.conf", |
| Type: "bind", |
| Source: "/etc/resolv.conf", |
| Options: []string{"rbind", "ro"}, |
| }, |
| { |
| Destination: "/etc/hosts", |
| Type: "bind", |
| Source: "/etc/hosts", |
| Options: []string{"rbind", "ro"}, |
| }, |
| { |
| Destination: "/etc/localtime", |
| Type: "bind", |
| Source: "/etc/localtime", |
| Options: []string{"rbind", "ro"}, |
| }, |
| }, |
| Hostname: id, |
| Linux: &specs.Linux{ |
| Resources: &specs.LinuxResources{ |
| Devices: []specs.LinuxDeviceCgroup{ |
| { |
| Allow: false, |
| Access: rwm, |
| }, |
| }, |
| }, |
| Namespaces: []specs.LinuxNamespace{ |
| { |
| Type: "pid", |
| }, |
| { |
| Type: "ipc", |
| }, |
| { |
| Type: "uts", |
| }, |
| { |
| Type: "mount", |
| }, |
| }, |
| }, |
| } |
| if !context.Bool("net-host") { |
| s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{ |
| Type: "network", |
| }) |
| } |
| for _, mount := range context.StringSlice("mount") { |
| m, err := parseMountFlag(mount) |
| if err != nil { |
| return nil, err |
| } |
| |
| s.Mounts = append(s.Mounts, m) |
| } |
| return s, nil |
| } |
| |
| func customSpec(configPath string, rootfs string) (*specs.Spec, error) { |
| b, err := ioutil.ReadFile(configPath) |
| if err != nil { |
| return nil, err |
| } |
| var s specs.Spec |
| if err := json.Unmarshal(b, &s); err != nil { |
| return nil, err |
| } |
| if rootfs == "" { |
| if s.Root.Path != defaultRootfsPath { |
| logrus.Warnf("ignoring Root.Path %q, setting %q forcibly", s.Root.Path, defaultRootfsPath) |
| s.Root.Path = defaultRootfsPath |
| } |
| } else { |
| s.Root.Path = rootfs |
| } |
| return &s, nil |
| } |
| |
| func getConfig(context *cli.Context, imageConfig *ocispec.ImageConfig, rootfs string) (*specs.Spec, error) { |
| config := context.String("runtime-config") |
| if config == "" { |
| return spec(context.String("id"), imageConfig, context, rootfs) |
| } |
| |
| return customSpec(config, rootfs) |
| } |
| |
| func newContainerSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string) ([]byte, error) { |
| s, err := getConfig(context, config, context.String("rootfs")) |
| if err != nil { |
| return nil, err |
| } |
| if s.Annotations == nil { |
| s.Annotations = make(map[string]string) |
| } |
| s.Annotations["image"] = imageRef |
| return json.Marshal(s) |
| } |
| |
| func newCreateContainerRequest(context *cli.Context, id, snapshot, image string, spec []byte) (*containersapi.CreateContainerRequest, error) { |
| create := &containersapi.CreateContainerRequest{ |
| Container: containersapi.Container{ |
| ID: id, |
| Image: image, |
| Spec: &protobuf.Any{ |
| TypeUrl: specs.Version, |
| Value: spec, |
| }, |
| Runtime: context.String("runtime"), |
| RootFS: snapshot, |
| }, |
| } |
| |
| return create, nil |
| } |
| |
| func newCreateTaskRequest(context *cli.Context, id, tmpDir string, checkpoint *ocispec.Descriptor, mounts []mountt.Mount) (*execution.CreateRequest, error) { |
| create := &execution.CreateRequest{ |
| ContainerID: id, |
| Terminal: context.Bool("tty"), |
| Stdin: filepath.Join(tmpDir, "stdin"), |
| Stdout: filepath.Join(tmpDir, "stdout"), |
| Stderr: filepath.Join(tmpDir, "stderr"), |
| } |
| |
| for _, m := range mounts { |
| create.Rootfs = append(create.Rootfs, &mount.Mount{ |
| Type: m.Type, |
| Source: m.Source, |
| Options: m.Options, |
| }) |
| } |
| |
| if checkpoint != nil { |
| create.Checkpoint = &descriptor.Descriptor{ |
| MediaType: checkpoint.MediaType, |
| Size_: checkpoint.Size, |
| Digest: checkpoint.Digest, |
| } |
| } |
| |
| return create, nil |
| } |
| |
| func handleConsoleResize(ctx context.Context, service execution.TasksClient, id string, pid uint32, con console.Console) error { |
| // do an initial resize of the console |
| size, err := con.Size() |
| if err != nil { |
| return err |
| } |
| if _, err := service.Pty(ctx, &execution.PtyRequest{ |
| ContainerID: id, |
| Pid: pid, |
| Width: uint32(size.Width), |
| Height: uint32(size.Height), |
| }); err != nil { |
| return err |
| } |
| s := make(chan os.Signal, 16) |
| signal.Notify(s, unix.SIGWINCH) |
| go func() { |
| for range s { |
| size, err := con.Size() |
| if err != nil { |
| logrus.WithError(err).Error("get pty size") |
| continue |
| } |
| if _, err := service.Pty(ctx, &execution.PtyRequest{ |
| ContainerID: id, |
| Pid: pid, |
| Width: uint32(size.Width), |
| Height: uint32(size.Height), |
| }); err != nil { |
| logrus.WithError(err).Error("resize pty") |
| } |
| } |
| }() |
| return nil |
| } |