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