github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/create.go (about) 1 package container 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "regexp" 9 10 "github.com/containerd/containerd/platforms" 11 "github.com/distribution/reference" 12 "github.com/docker/cli/cli" 13 "github.com/docker/cli/cli/command" 14 "github.com/docker/cli/cli/command/completion" 15 "github.com/docker/cli/cli/command/image" 16 "github.com/docker/cli/cli/streams" 17 "github.com/docker/cli/opts" 18 "github.com/docker/docker/api/types/container" 19 imagetypes "github.com/docker/docker/api/types/image" 20 "github.com/docker/docker/api/types/versions" 21 "github.com/docker/docker/errdefs" 22 "github.com/docker/docker/pkg/jsonmessage" 23 specs "github.com/opencontainers/image-spec/specs-go/v1" 24 "github.com/pkg/errors" 25 "github.com/spf13/cobra" 26 "github.com/spf13/pflag" 27 ) 28 29 // Pull constants 30 const ( 31 PullImageAlways = "always" 32 PullImageMissing = "missing" // Default (matches previous behavior) 33 PullImageNever = "never" 34 ) 35 36 type createOptions struct { 37 name string 38 platform string 39 untrusted bool 40 pull string // always, missing, never 41 quiet bool 42 } 43 44 // NewCreateCommand creates a new cobra.Command for `docker create` 45 func NewCreateCommand(dockerCli command.Cli) *cobra.Command { 46 var options createOptions 47 var copts *containerOptions 48 49 cmd := &cobra.Command{ 50 Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]", 51 Short: "Create a new container", 52 Args: cli.RequiresMinArgs(1), 53 RunE: func(cmd *cobra.Command, args []string) error { 54 copts.Image = args[0] 55 if len(args) > 1 { 56 copts.Args = args[1:] 57 } 58 return runCreate(cmd.Context(), dockerCli, cmd.Flags(), &options, copts) 59 }, 60 Annotations: map[string]string{ 61 "aliases": "docker container create, docker create", 62 }, 63 ValidArgsFunction: completion.ImageNames(dockerCli), 64 } 65 66 flags := cmd.Flags() 67 flags.SetInterspersed(false) 68 69 flags.StringVar(&options.name, "name", "", "Assign a name to the container") 70 flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "|`+PullImageMissing+`", "`+PullImageNever+`")`) 71 flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output") 72 73 // Add an explicit help that doesn't have a `-h` to prevent the conflict 74 // with hostname 75 flags.Bool("help", false, "Print usage") 76 77 command.AddPlatformFlag(flags, &options.platform) 78 command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) 79 copts = addFlags(flags) 80 return cmd 81 } 82 83 func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error { 84 if err := validatePullOpt(options.pull); err != nil { 85 reportError(dockerCli.Err(), "create", err.Error(), true) 86 return cli.StatusError{StatusCode: 125} 87 } 88 proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll())) 89 newEnv := []string{} 90 for k, v := range proxyConfig { 91 if v == nil { 92 newEnv = append(newEnv, k) 93 } else { 94 newEnv = append(newEnv, k+"="+*v) 95 } 96 } 97 copts.env = *opts.NewListOptsRef(&newEnv, nil) 98 containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType) 99 if err != nil { 100 reportError(dockerCli.Err(), "create", err.Error(), true) 101 return cli.StatusError{StatusCode: 125} 102 } 103 if err = validateAPIVersion(containerCfg, dockerCli.Client().ClientVersion()); err != nil { 104 reportError(dockerCli.Err(), "create", err.Error(), true) 105 return cli.StatusError{StatusCode: 125} 106 } 107 id, err := createContainer(ctx, dockerCli, containerCfg, options) 108 if err != nil { 109 return err 110 } 111 _, _ = fmt.Fprintln(dockerCli.Out(), id) 112 return nil 113 } 114 115 // FIXME(thaJeztah): this is the only code-path that uses APIClient.ImageCreate. Rewrite this to use the regular "pull" code (or vice-versa). 116 func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *createOptions) error { 117 encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), img) 118 if err != nil { 119 return err 120 } 121 122 responseBody, err := dockerCli.Client().ImageCreate(ctx, img, imagetypes.CreateOptions{ 123 RegistryAuth: encodedAuth, 124 Platform: options.platform, 125 }) 126 if err != nil { 127 return err 128 } 129 defer responseBody.Close() 130 131 out := dockerCli.Err() 132 if options.quiet { 133 out = io.Discard 134 } 135 return jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(out), nil) 136 } 137 138 type cidFile struct { 139 path string 140 file *os.File 141 written bool 142 } 143 144 func (cid *cidFile) Close() error { 145 if cid.file == nil { 146 return nil 147 } 148 cid.file.Close() 149 150 if cid.written { 151 return nil 152 } 153 if err := os.Remove(cid.path); err != nil { 154 return errors.Wrapf(err, "failed to remove the CID file '%s'", cid.path) 155 } 156 157 return nil 158 } 159 160 func (cid *cidFile) Write(id string) error { 161 if cid.file == nil { 162 return nil 163 } 164 if _, err := cid.file.Write([]byte(id)); err != nil { 165 return errors.Wrap(err, "failed to write the container ID to the file") 166 } 167 cid.written = true 168 return nil 169 } 170 171 func newCIDFile(path string) (*cidFile, error) { 172 if path == "" { 173 return &cidFile{}, nil 174 } 175 if _, err := os.Stat(path); err == nil { 176 return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", path) 177 } 178 179 f, err := os.Create(path) 180 if err != nil { 181 return nil, errors.Wrap(err, "failed to create the container ID file") 182 } 183 184 return &cidFile{path: path, file: f}, nil 185 } 186 187 //nolint:gocyclo 188 func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *containerConfig, options *createOptions) (containerID string, err error) { 189 config := containerCfg.Config 190 hostConfig := containerCfg.HostConfig 191 networkingConfig := containerCfg.NetworkingConfig 192 193 warnOnOomKillDisable(*hostConfig, dockerCli.Err()) 194 warnOnLocalhostDNS(*hostConfig, dockerCli.Err()) 195 196 var ( 197 trustedRef reference.Canonical 198 namedRef reference.Named 199 ) 200 201 containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile) 202 if err != nil { 203 return "", err 204 } 205 defer containerIDFile.Close() 206 207 ref, err := reference.ParseAnyReference(config.Image) 208 if err != nil { 209 return "", err 210 } 211 if named, ok := ref.(reference.Named); ok { 212 namedRef = reference.TagNameOnly(named) 213 214 if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !options.untrusted { 215 var err error 216 trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef) 217 if err != nil { 218 return "", err 219 } 220 config.Image = reference.FamiliarString(trustedRef) 221 } 222 } 223 224 pullAndTagImage := func() error { 225 if err := pullImage(ctx, dockerCli, config.Image, options); err != nil { 226 return err 227 } 228 if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil { 229 return image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef) 230 } 231 return nil 232 } 233 234 var platform *specs.Platform 235 // Engine API version 1.41 first introduced the option to specify platform on 236 // create. It will produce an error if you try to set a platform on older API 237 // versions, so check the API version here to maintain backwards 238 // compatibility for CLI users. 239 if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") { 240 p, err := platforms.Parse(options.platform) 241 if err != nil { 242 return "", errors.Wrap(err, "error parsing specified platform") 243 } 244 platform = &p 245 } 246 247 if options.pull == PullImageAlways { 248 if err := pullAndTagImage(); err != nil { 249 return "", err 250 } 251 } 252 253 hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize() 254 255 response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name) 256 if err != nil { 257 // Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior. 258 if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing { 259 if !options.quiet { 260 // we don't want to write to stdout anything apart from container.ID 261 fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef)) 262 } 263 264 if err := pullAndTagImage(); err != nil { 265 return "", err 266 } 267 268 var retryErr error 269 response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name) 270 if retryErr != nil { 271 return "", retryErr 272 } 273 } else { 274 return "", err 275 } 276 } 277 278 for _, w := range response.Warnings { 279 _, _ = fmt.Fprintf(dockerCli.Err(), "WARNING: %s\n", w) 280 } 281 err = containerIDFile.Write(response.ID) 282 return response.ID, err 283 } 284 285 func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) { 286 if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 { 287 fmt.Fprintln(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.") 288 } 289 } 290 291 // check the DNS settings passed via --dns against localhost regexp to warn if 292 // they are trying to set a DNS to a localhost address 293 func warnOnLocalhostDNS(hostConfig container.HostConfig, stderr io.Writer) { 294 for _, dnsIP := range hostConfig.DNS { 295 if isLocalhost(dnsIP) { 296 fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) 297 return 298 } 299 } 300 } 301 302 // IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range. 303 const ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)` 304 305 var localhostIPRegexp = regexp.MustCompile(ipLocalhost) 306 307 // IsLocalhost returns true if ip matches the localhost IP regular expression. 308 // Used for determining if nameserver settings are being passed which are 309 // localhost addresses 310 func isLocalhost(ip string) bool { 311 return localhostIPRegexp.MatchString(ip) 312 } 313 314 func validatePullOpt(val string) error { 315 switch val { 316 case PullImageAlways, PullImageMissing, PullImageNever, "": 317 // valid option, but nothing to do yet 318 return nil 319 default: 320 return fmt.Errorf( 321 "invalid pull option: '%s': must be one of %q, %q or %q", 322 val, 323 PullImageAlways, 324 PullImageMissing, 325 PullImageNever, 326 ) 327 } 328 }