github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/cli/command/container/start.go (about) 1 package container 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "strings" 8 9 "github.com/docker/cli/cli" 10 "github.com/docker/cli/cli/command" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/pkg/signal" 13 "github.com/moby/term" 14 "github.com/pkg/errors" 15 "github.com/spf13/cobra" 16 ) 17 18 type startOptions struct { 19 attach bool 20 openStdin bool 21 detachKeys string 22 checkpoint string 23 checkpointDir string 24 25 containers []string 26 } 27 28 // NewStartCommand creates a new cobra.Command for `docker start` 29 func NewStartCommand(dockerCli command.Cli) *cobra.Command { 30 var opts startOptions 31 32 cmd := &cobra.Command{ 33 Use: "start [OPTIONS] CONTAINER [CONTAINER...]", 34 Short: "Start one or more stopped containers", 35 Args: cli.RequiresMinArgs(1), 36 RunE: func(cmd *cobra.Command, args []string) error { 37 opts.containers = args 38 return runStart(dockerCli, &opts) 39 }, 40 } 41 42 flags := cmd.Flags() 43 flags.BoolVarP(&opts.attach, "attach", "a", false, "Attach STDOUT/STDERR and forward signals") 44 flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN") 45 flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") 46 47 flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") 48 flags.SetAnnotation("checkpoint", "experimental", nil) 49 flags.SetAnnotation("checkpoint", "ostype", []string{"linux"}) 50 flags.StringVar(&opts.checkpointDir, "checkpoint-dir", "", "Use a custom checkpoint storage directory") 51 flags.SetAnnotation("checkpoint-dir", "experimental", nil) 52 flags.SetAnnotation("checkpoint-dir", "ostype", []string{"linux"}) 53 return cmd 54 } 55 56 // nolint: gocyclo 57 func runStart(dockerCli command.Cli, opts *startOptions) error { 58 ctx, cancelFun := context.WithCancel(context.Background()) 59 defer cancelFun() 60 61 if opts.attach || opts.openStdin { 62 // We're going to attach to a container. 63 // 1. Ensure we only have one container. 64 if len(opts.containers) > 1 { 65 return errors.New("you cannot start and attach multiple containers at once") 66 } 67 68 // 2. Attach to the container. 69 container := opts.containers[0] 70 c, err := dockerCli.Client().ContainerInspect(ctx, container) 71 if err != nil { 72 return err 73 } 74 75 // We always use c.ID instead of container to maintain consistency during `docker start` 76 if !c.Config.Tty { 77 sigc := ForwardAllSignals(ctx, dockerCli, c.ID) 78 defer signal.StopCatch(sigc) 79 } 80 81 if opts.detachKeys != "" { 82 dockerCli.ConfigFile().DetachKeys = opts.detachKeys 83 } 84 85 options := types.ContainerAttachOptions{ 86 Stream: true, 87 Stdin: opts.openStdin && c.Config.OpenStdin, 88 Stdout: true, 89 Stderr: true, 90 DetachKeys: dockerCli.ConfigFile().DetachKeys, 91 } 92 93 var in io.ReadCloser 94 95 if options.Stdin { 96 in = dockerCli.In() 97 } 98 99 resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options) 100 if errAttach != nil { 101 return errAttach 102 } 103 defer resp.Close() 104 105 cErr := make(chan error, 1) 106 107 go func() { 108 cErr <- func() error { 109 streamer := hijackedIOStreamer{ 110 streams: dockerCli, 111 inputStream: in, 112 outputStream: dockerCli.Out(), 113 errorStream: dockerCli.Err(), 114 resp: resp, 115 tty: c.Config.Tty, 116 detachKeys: options.DetachKeys, 117 } 118 119 errHijack := streamer.stream(ctx) 120 if errHijack == nil { 121 return errAttach 122 } 123 return errHijack 124 }() 125 }() 126 127 // 3. We should open a channel for receiving status code of the container 128 // no matter it's detached, removed on daemon side(--rm) or exit normally. 129 statusChan := waitExitOrRemoved(ctx, dockerCli, c.ID, c.HostConfig.AutoRemove) 130 startOptions := types.ContainerStartOptions{ 131 CheckpointID: opts.checkpoint, 132 CheckpointDir: opts.checkpointDir, 133 } 134 135 // 4. Start the container. 136 if err := dockerCli.Client().ContainerStart(ctx, c.ID, startOptions); err != nil { 137 cancelFun() 138 <-cErr 139 if c.HostConfig.AutoRemove { 140 // wait container to be removed 141 <-statusChan 142 } 143 return err 144 } 145 146 // 5. Wait for attachment to break. 147 if c.Config.Tty && dockerCli.Out().IsTerminal() { 148 if err := MonitorTtySize(ctx, dockerCli, c.ID, false); err != nil { 149 fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err) 150 } 151 } 152 if attachErr := <-cErr; attachErr != nil { 153 if _, ok := attachErr.(term.EscapeError); ok { 154 // The user entered the detach escape sequence. 155 return nil 156 } 157 return attachErr 158 } 159 160 if status := <-statusChan; status != 0 { 161 return cli.StatusError{StatusCode: status} 162 } 163 } else if opts.checkpoint != "" { 164 if len(opts.containers) > 1 { 165 return errors.New("you cannot restore multiple containers at once") 166 } 167 container := opts.containers[0] 168 startOptions := types.ContainerStartOptions{ 169 CheckpointID: opts.checkpoint, 170 CheckpointDir: opts.checkpointDir, 171 } 172 return dockerCli.Client().ContainerStart(ctx, container, startOptions) 173 174 } else { 175 // We're not going to attach to anything. 176 // Start as many containers as we want. 177 return startContainersWithoutAttachments(ctx, dockerCli, opts.containers) 178 } 179 180 return nil 181 } 182 183 func startContainersWithoutAttachments(ctx context.Context, dockerCli command.Cli, containers []string) error { 184 var failedContainers []string 185 for _, container := range containers { 186 if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil { 187 fmt.Fprintln(dockerCli.Err(), err) 188 failedContainers = append(failedContainers, container) 189 continue 190 } 191 fmt.Fprintln(dockerCli.Out(), container) 192 } 193 194 if len(failedContainers) > 0 { 195 return errors.Errorf("Error: failed to start containers: %s", strings.Join(failedContainers, ", ")) 196 } 197 return nil 198 }