| package main |
| |
| import ( |
| gocontext "context" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "runtime" |
| |
| "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/images" |
| "github.com/containerd/containerd/mount" |
| "github.com/containerd/containerd/snapshot" |
| digest "github.com/opencontainers/go-digest" |
| "github.com/opencontainers/image-spec/identity" |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| "github.com/pkg/errors" |
| "github.com/urfave/cli" |
| ) |
| |
| var runCommand = cli.Command{ |
| Name: "run", |
| Usage: "run a container", |
| ArgsUsage: "IMAGE [COMMAND] [ARG...]", |
| Flags: []cli.Flag{ |
| cli.StringFlag{ |
| Name: "id", |
| Usage: "id of the container", |
| }, |
| cli.BoolFlag{ |
| Name: "tty,t", |
| Usage: "allocate a TTY for the container", |
| }, |
| cli.StringFlag{ |
| Name: "rootfs", |
| Usage: "path to rootfs", |
| }, |
| cli.StringFlag{ |
| Name: "runtime", |
| Usage: "runtime name (linux, windows, vmware-linux)", |
| Value: "linux", |
| }, |
| cli.StringFlag{ |
| Name: "runtime-config", |
| Usage: "set the OCI config file for the container", |
| }, |
| cli.BoolFlag{ |
| Name: "readonly", |
| Usage: "set the containers filesystem as readonly", |
| }, |
| cli.BoolFlag{ |
| Name: "net-host", |
| Usage: "enable host networking for the container", |
| }, |
| cli.StringSliceFlag{ |
| Name: "mount", |
| Usage: "specify additional container mount (ex: type=bind,src=/tmp,dest=/host,options=rbind:ro)", |
| }, |
| cli.StringSliceFlag{ |
| Name: "env", |
| Usage: "specify additional container environment variables (i.e. FOO=bar)", |
| }, |
| cli.BoolFlag{ |
| Name: "rm", |
| Usage: "remove the container after running", |
| }, |
| cli.StringFlag{ |
| Name: "checkpoint", |
| Usage: "provide the checkpoint digest to restore the container", |
| }, |
| }, |
| Action: func(context *cli.Context) error { |
| var ( |
| err error |
| mounts []mount.Mount |
| imageConfig ocispec.Image |
| |
| ctx = gocontext.Background() |
| id = context.String("id") |
| ) |
| if id == "" { |
| return errors.New("container id must be provided") |
| } |
| containers, err := getContainersService(context) |
| if err != nil { |
| return err |
| } |
| tasks, err := getTasksService(context) |
| if err != nil { |
| return err |
| } |
| tmpDir, err := getTempDir(id) |
| if err != nil { |
| return err |
| } |
| defer os.RemoveAll(tmpDir) |
| events, err := tasks.Events(ctx, &execution.EventsRequest{}) |
| if err != nil { |
| return err |
| } |
| |
| content, err := getContentStore(context) |
| if err != nil { |
| return err |
| } |
| |
| snapshotter, err := getSnapshotter(context) |
| if err != nil { |
| return err |
| } |
| imageStore, err := getImageStore(context) |
| if err != nil { |
| return errors.Wrap(err, "failed resolving image store") |
| } |
| differ, err := getDiffService(context) |
| if err != nil { |
| return err |
| } |
| var ( |
| checkpoint *ocispec.Descriptor |
| checkpointIndex digest.Digest |
| ref = context.Args().First() |
| ) |
| if raw := context.String("checkpoint"); raw != "" { |
| if checkpointIndex, err = digest.Parse(raw); err != nil { |
| return err |
| } |
| } |
| var spec []byte |
| if checkpointIndex != "" { |
| var index ocispec.ImageIndex |
| r, err := content.Reader(ctx, checkpointIndex) |
| if err != nil { |
| return err |
| } |
| err = json.NewDecoder(r).Decode(&index) |
| r.Close() |
| if err != nil { |
| return err |
| } |
| var rw ocispec.Descriptor |
| for _, m := range index.Manifests { |
| switch m.MediaType { |
| case images.MediaTypeContainerd1Checkpoint: |
| fkingo := m.Descriptor |
| checkpoint = &fkingo |
| case images.MediaTypeContainerd1CheckpointConfig: |
| if r, err = content.Reader(ctx, m.Digest); err != nil { |
| return err |
| } |
| spec, err = ioutil.ReadAll(r) |
| r.Close() |
| if err != nil { |
| return err |
| } |
| case images.MediaTypeDockerSchema2Manifest: |
| // make sure we have the original image that was used during checkpoint |
| diffIDs, err := images.RootFS(ctx, content, m.Descriptor) |
| if err != nil { |
| return err |
| } |
| if _, err := snapshotter.Prepare(ctx, id, identity.ChainID(diffIDs).String()); err != nil { |
| if !snapshot.IsExist(err) { |
| return err |
| } |
| } |
| case ocispec.MediaTypeImageLayer: |
| rw = m.Descriptor |
| } |
| } |
| if mounts, err = snapshotter.Mounts(ctx, id); err != nil { |
| return err |
| } |
| if _, err := differ.Apply(ctx, rw, mounts); err != nil { |
| return err |
| } |
| } else { |
| if runtime.GOOS != "windows" && context.String("rootfs") == "" { |
| image, err := imageStore.Get(ctx, ref) |
| if err != nil { |
| return errors.Wrapf(err, "could not resolve %q", ref) |
| } |
| // let's close out our db and tx so we don't hold the lock whilst running. |
| diffIDs, err := image.RootFS(ctx, content) |
| if err != nil { |
| return err |
| } |
| if context.Bool("readonly") { |
| mounts, err = snapshotter.View(ctx, id, identity.ChainID(diffIDs).String()) |
| } else { |
| mounts, err = snapshotter.Prepare(ctx, id, identity.ChainID(diffIDs).String()) |
| } |
| defer func() { |
| if err != nil || context.Bool("rm") { |
| if err := snapshotter.Remove(ctx, id); err != nil { |
| logrus.WithError(err).Errorf("failed to remove snapshot %q", id) |
| } |
| } |
| }() |
| if err != nil { |
| if !snapshot.IsExist(err) { |
| return err |
| } |
| mounts, err = snapshotter.Mounts(ctx, id) |
| if err != nil { |
| return err |
| } |
| } |
| ic, err := image.Config(ctx, content) |
| if err != nil { |
| return err |
| } |
| switch ic.MediaType { |
| case ocispec.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: |
| r, err := content.Reader(ctx, ic.Digest) |
| if err != nil { |
| return err |
| } |
| if err := json.NewDecoder(r).Decode(&imageConfig); err != nil { |
| r.Close() |
| return err |
| } |
| r.Close() |
| default: |
| return fmt.Errorf("unknown image config media type %s", ic.MediaType) |
| } |
| } else { |
| // TODO: get the image / rootfs through the API once windows has a snapshotter |
| } |
| } |
| if err != nil { |
| return err |
| } |
| if len(spec) == 0 { |
| if spec, err = newContainerSpec(context, &imageConfig.Config, ref); err != nil { |
| return err |
| } |
| } |
| |
| createContainer, err := newCreateContainerRequest(context, id, id, ref, spec) |
| if err != nil { |
| return err |
| } |
| |
| _, err = containers.Create(ctx, createContainer) |
| if err != nil { |
| return err |
| } |
| |
| create, err := newCreateTaskRequest(context, id, tmpDir, checkpoint, mounts) |
| if err != nil { |
| return err |
| } |
| var con console.Console |
| if create.Terminal { |
| con = console.Current() |
| defer con.Reset() |
| if err := con.SetRaw(); err != nil { |
| return err |
| } |
| } |
| fwg, err := prepareStdio(create.Stdin, create.Stdout, create.Stderr, create.Terminal) |
| if err != nil { |
| return err |
| } |
| response, err := tasks.Create(ctx, create) |
| if err != nil { |
| return err |
| } |
| pid := response.Pid |
| if create.Terminal { |
| if err := handleConsoleResize(ctx, tasks, id, pid, con); err != nil { |
| logrus.WithError(err).Error("console resize") |
| } |
| } else { |
| sigc := forwardAllSignals(tasks, id) |
| defer stopCatch(sigc) |
| } |
| if checkpoint == nil { |
| if _, err := tasks.Start(ctx, &execution.StartRequest{ |
| ContainerID: id, |
| }); err != nil { |
| return err |
| } |
| } |
| // Ensure we read all io only if container started successfully. |
| defer fwg.Wait() |
| |
| status, err := waitContainer(events, id, pid) |
| if err != nil { |
| return err |
| } |
| if _, err := tasks.Delete(ctx, &execution.DeleteRequest{ |
| ContainerID: response.ContainerID, |
| }); err != nil { |
| return err |
| } |
| if context.Bool("rm") { |
| if _, err := containers.Delete(ctx, &containersapi.DeleteContainerRequest{ID: id}); err != nil { |
| return err |
| } |
| } |
| if status != 0 { |
| return cli.NewExitError("", int(status)) |
| } |
| return nil |
| }, |
| } |