diff --git a/command/checkpoint/cmd.go b/command/checkpoint/cmd.go new file mode 100644 index 0000000000..bc8224a2ff --- /dev/null +++ b/command/checkpoint/cmd.go @@ -0,0 +1,12 @@ +// +build !experimental + +package checkpoint + +import ( + "github.com/docker/docker/cli/command" + "github.com/spf13/cobra" +) + +// NewCheckpointCommand appends the `checkpoint` subcommands to rootCmd (only in experimental) +func NewCheckpointCommand(rootCmd *cobra.Command, dockerCli *command.DockerCli) { +} diff --git a/command/checkpoint/cmd_experimental.go b/command/checkpoint/cmd_experimental.go new file mode 100644 index 0000000000..7939678cd5 --- /dev/null +++ b/command/checkpoint/cmd_experimental.go @@ -0,0 +1,31 @@ +// +build experimental + +package checkpoint + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/docker/docker/cli" + "github.com/docker/docker/cli/command" +) + +// NewCheckpointCommand appends the `checkpoint` subcommands to rootCmd +func NewCheckpointCommand(rootCmd *cobra.Command, dockerCli *command.DockerCli) { + cmd := &cobra.Command{ + Use: "checkpoint", + Short: "Manage Container Checkpoints", + Args: cli.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) + }, + } + cmd.AddCommand( + newCreateCommand(dockerCli), + newListCommand(dockerCli), + newRemoveCommand(dockerCli), + ) + + rootCmd.AddCommand(cmd) +} diff --git a/command/checkpoint/create.go b/command/checkpoint/create.go new file mode 100644 index 0000000000..f214574556 --- /dev/null +++ b/command/checkpoint/create.go @@ -0,0 +1,54 @@ +// +build experimental + +package checkpoint + +import ( + "golang.org/x/net/context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/cli" + "github.com/docker/docker/cli/command" + "github.com/spf13/cobra" +) + +type createOptions struct { + container string + checkpoint string + leaveRunning bool +} + +func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { + var opts createOptions + + cmd := &cobra.Command{ + Use: "create CONTAINER CHECKPOINT", + Short: "Create a checkpoint from a running container", + Args: cli.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + opts.container = args[0] + opts.checkpoint = args[1] + return runCreate(dockerCli, opts) + }, + } + + flags := cmd.Flags() + flags.BoolVar(&opts.leaveRunning, "leave-running", false, "leave the container running after checkpoint") + + return cmd +} + +func runCreate(dockerCli *command.DockerCli, opts createOptions) error { + client := dockerCli.Client() + + checkpointOpts := types.CheckpointCreateOptions{ + CheckpointID: opts.checkpoint, + Exit: !opts.leaveRunning, + } + + err := client.CheckpointCreate(context.Background(), opts.container, checkpointOpts) + if err != nil { + return err + } + + return nil +} diff --git a/command/checkpoint/list.go b/command/checkpoint/list.go new file mode 100644 index 0000000000..6d22531d45 --- /dev/null +++ b/command/checkpoint/list.go @@ -0,0 +1,47 @@ +// +build experimental + +package checkpoint + +import ( + "fmt" + "text/tabwriter" + + "golang.org/x/net/context" + + "github.com/docker/docker/cli" + "github.com/docker/docker/cli/command" + "github.com/spf13/cobra" +) + +func newListCommand(dockerCli *command.DockerCli) *cobra.Command { + return &cobra.Command{ + Use: "ls CONTAINER", + Aliases: []string{"list"}, + Short: "List checkpoints for a container", + Args: cli.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return runList(dockerCli, args[0]) + }, + } +} + +func runList(dockerCli *command.DockerCli, container string) error { + client := dockerCli.Client() + + checkpoints, err := client.CheckpointList(context.Background(), container) + if err != nil { + return err + } + + w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0) + fmt.Fprintf(w, "CHECKPOINT NAME") + fmt.Fprintf(w, "\n") + + for _, checkpoint := range checkpoints { + fmt.Fprintf(w, "%s\t", checkpoint.Name) + fmt.Fprint(w, "\n") + } + + w.Flush() + return nil +} diff --git a/command/checkpoint/remove.go b/command/checkpoint/remove.go new file mode 100644 index 0000000000..6605c5e472 --- /dev/null +++ b/command/checkpoint/remove.go @@ -0,0 +1,28 @@ +// +build experimental + +package checkpoint + +import ( + "golang.org/x/net/context" + + "github.com/docker/docker/cli" + "github.com/docker/docker/cli/command" + "github.com/spf13/cobra" +) + +func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { + return &cobra.Command{ + Use: "rm CONTAINER CHECKPOINT", + Aliases: []string{"remove"}, + Short: "Remove a checkpoint", + Args: cli.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + return runRemove(dockerCli, args[0], args[1]) + }, + } +} + +func runRemove(dockerCli *command.DockerCli, container string, checkpoint string) error { + client := dockerCli.Client() + return client.CheckpointDelete(context.Background(), container, checkpoint) +} diff --git a/command/commands/commands.go b/command/commands/commands.go index 35fd6860b0..0adf8e3f3e 100644 --- a/command/commands/commands.go +++ b/command/commands/commands.go @@ -2,6 +2,7 @@ package commands import ( "github.com/docker/docker/cli/command" + "github.com/docker/docker/cli/command/checkpoint" "github.com/docker/docker/cli/command/container" "github.com/docker/docker/cli/command/image" "github.com/docker/docker/cli/command/network" @@ -67,5 +68,6 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) { volume.NewVolumeCommand(dockerCli), system.NewInfoCommand(dockerCli), ) + checkpoint.NewCheckpointCommand(cmd, dockerCli) plugin.NewPluginCommand(cmd, dockerCli) } diff --git a/command/container/start.go b/command/container/start.go index e72369177a..9f414a7c66 100644 --- a/command/container/start.go +++ b/command/container/start.go @@ -20,6 +20,7 @@ type startOptions struct { attach bool openStdin bool detachKeys string + checkpoint string containers []string } @@ -42,6 +43,9 @@ func NewStartCommand(dockerCli *command.DockerCli) *cobra.Command { flags.BoolVarP(&opts.attach, "attach", "a", false, "Attach STDOUT/STDERR and forward signals") flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN") flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") + + addExperimentalStartFlags(flags, &opts) + return cmd } @@ -105,9 +109,12 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error { // 3. We should open a channel for receiving status code of the container // no matter it's detached, removed on daemon side(--rm) or exit normally. statusChan, statusErr := waitExitOrRemoved(dockerCli, context.Background(), c.ID, c.HostConfig.AutoRemove) + startOptions := types.ContainerStartOptions{ + CheckpointID: opts.checkpoint, + } // 4. Start the container. - if err := dockerCli.Client().ContainerStart(ctx, c.ID, types.ContainerStartOptions{}); err != nil { + if err := dockerCli.Client().ContainerStart(ctx, c.ID, startOptions); err != nil { cancelFun() <-cErr if c.HostConfig.AutoRemove && statusErr == nil { @@ -134,6 +141,16 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error { if status := <-statusChan; status != 0 { return cli.StatusError{StatusCode: status} } + } else if opts.checkpoint != "" { + if len(opts.containers) > 1 { + return fmt.Errorf("You cannot restore multiple containers at once.") + } + container := opts.containers[0] + startOptions := types.ContainerStartOptions{ + CheckpointID: opts.checkpoint, + } + return dockerCli.Client().ContainerStart(ctx, container, startOptions) + } else { // We're not going to attach to anything. // Start as many containers as we want. diff --git a/command/container/start_utils.go b/command/container/start_utils.go new file mode 100644 index 0000000000..689d742f06 --- /dev/null +++ b/command/container/start_utils.go @@ -0,0 +1,8 @@ +// +build !experimental + +package container + +import "github.com/spf13/pflag" + +func addExperimentalStartFlags(flags *pflag.FlagSet, opts *startOptions) { +} diff --git a/command/container/start_utils_experimental.go b/command/container/start_utils_experimental.go new file mode 100644 index 0000000000..43c64f431c --- /dev/null +++ b/command/container/start_utils_experimental.go @@ -0,0 +1,9 @@ +// +build experimental + +package container + +import "github.com/spf13/pflag" + +func addExperimentalStartFlags(flags *pflag.FlagSet, opts *startOptions) { + flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") +}