github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+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/docker/cli/cli" 12 "github.com/docker/cli/cli/command" 13 "github.com/docker/cli/cli/command/image" 14 "github.com/docker/cli/opts" 15 "github.com/docker/distribution/reference" 16 "github.com/docker/docker/api/types" 17 "github.com/docker/docker/api/types/container" 18 "github.com/docker/docker/api/types/versions" 19 apiclient "github.com/docker/docker/client" 20 "github.com/docker/docker/pkg/jsonmessage" 21 "github.com/docker/docker/registry" 22 specs "github.com/opencontainers/image-spec/specs-go/v1" 23 "github.com/pkg/errors" 24 "github.com/spf13/cobra" 25 "github.com/spf13/pflag" 26 ) 27 28 // Pull constants 29 const ( 30 PullImageAlways = "always" 31 PullImageMissing = "missing" // Default (matches previous behavior) 32 PullImageNever = "never" 33 ) 34 35 type createOptions struct { 36 name string 37 platform string 38 untrusted bool 39 pull string // alway, missing, never 40 } 41 42 // NewCreateCommand creates a new cobra.Command for `docker create` 43 func NewCreateCommand(dockerCli command.Cli) *cobra.Command { 44 var opts createOptions 45 var copts *containerOptions 46 47 cmd := &cobra.Command{ 48 Use: "create [OPTIONS] IMAGE [COMMAND] [ARG...]", 49 Short: "Create a new container", 50 Args: cli.RequiresMinArgs(1), 51 RunE: func(cmd *cobra.Command, args []string) error { 52 copts.Image = args[0] 53 if len(args) > 1 { 54 copts.Args = args[1:] 55 } 56 return runCreate(dockerCli, cmd.Flags(), &opts, copts) 57 }, 58 } 59 60 flags := cmd.Flags() 61 flags.SetInterspersed(false) 62 63 flags.StringVar(&opts.name, "name", "", "Assign a name to the container") 64 flags.StringVar(&opts.pull, "pull", PullImageMissing, 65 `Pull image before creating ("`+PullImageAlways+`"|"`+PullImageMissing+`"|"`+PullImageNever+`")`) 66 67 // Add an explicit help that doesn't have a `-h` to prevent the conflict 68 // with hostname 69 flags.Bool("help", false, "Print usage") 70 71 command.AddPlatformFlag(flags, &opts.platform) 72 command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) 73 copts = addFlags(flags) 74 return cmd 75 } 76 77 func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error { 78 proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll())) 79 newEnv := []string{} 80 for k, v := range proxyConfig { 81 if v == nil { 82 newEnv = append(newEnv, k) 83 } else { 84 newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v)) 85 } 86 } 87 copts.env = *opts.NewListOptsRef(&newEnv, nil) 88 containerConfig, err := parse(flags, copts, dockerCli.ServerInfo().OSType) 89 if err != nil { 90 reportError(dockerCli.Err(), "create", err.Error(), true) 91 return cli.StatusError{StatusCode: 125} 92 } 93 if err = validateAPIVersion(containerConfig, dockerCli.Client().ClientVersion()); err != nil { 94 reportError(dockerCli.Err(), "create", err.Error(), true) 95 return cli.StatusError{StatusCode: 125} 96 } 97 response, err := createContainer(context.Background(), dockerCli, containerConfig, options) 98 if err != nil { 99 return err 100 } 101 fmt.Fprintln(dockerCli.Out(), response.ID) 102 return nil 103 } 104 105 func pullImage(ctx context.Context, dockerCli command.Cli, image string, platform string, out io.Writer) error { 106 ref, err := reference.ParseNormalizedNamed(image) 107 if err != nil { 108 return err 109 } 110 111 // Resolve the Repository name from fqn to RepositoryInfo 112 repoInfo, err := registry.ParseRepositoryInfo(ref) 113 if err != nil { 114 return err 115 } 116 117 authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) 118 encodedAuth, err := command.EncodeAuthToBase64(authConfig) 119 if err != nil { 120 return err 121 } 122 123 options := types.ImageCreateOptions{ 124 RegistryAuth: encodedAuth, 125 Platform: platform, 126 } 127 128 responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options) 129 if err != nil { 130 return err 131 } 132 defer responseBody.Close() 133 134 return jsonmessage.DisplayJSONMessagesStream( 135 responseBody, 136 out, 137 dockerCli.Out().FD(), 138 dockerCli.Out().IsTerminal(), 139 nil) 140 } 141 142 type cidFile struct { 143 path string 144 file *os.File 145 written bool 146 } 147 148 func (cid *cidFile) Close() error { 149 if cid.file == nil { 150 return nil 151 } 152 cid.file.Close() 153 154 if cid.written { 155 return nil 156 } 157 if err := os.Remove(cid.path); err != nil { 158 return errors.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err) 159 } 160 161 return nil 162 } 163 164 func (cid *cidFile) Write(id string) error { 165 if cid.file == nil { 166 return nil 167 } 168 if _, err := cid.file.Write([]byte(id)); err != nil { 169 return errors.Errorf("Failed to write the container ID to the file: %s", err) 170 } 171 cid.written = true 172 return nil 173 } 174 175 func newCIDFile(path string) (*cidFile, error) { 176 if path == "" { 177 return &cidFile{}, nil 178 } 179 if _, err := os.Stat(path); err == nil { 180 return nil, errors.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path) 181 } 182 183 f, err := os.Create(path) 184 if err != nil { 185 return nil, errors.Errorf("Failed to create the container ID file: %s", err) 186 } 187 188 return &cidFile{path: path, file: f}, nil 189 } 190 191 // nolint: gocyclo 192 func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.ContainerCreateCreatedBody, error) { 193 config := containerConfig.Config 194 hostConfig := containerConfig.HostConfig 195 networkingConfig := containerConfig.NetworkingConfig 196 stderr := dockerCli.Err() 197 198 warnOnOomKillDisable(*hostConfig, stderr) 199 warnOnLocalhostDNS(*hostConfig, stderr) 200 201 var ( 202 trustedRef reference.Canonical 203 namedRef reference.Named 204 ) 205 206 containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile) 207 if err != nil { 208 return nil, err 209 } 210 defer containerIDFile.Close() 211 212 ref, err := reference.ParseAnyReference(config.Image) 213 if err != nil { 214 return nil, err 215 } 216 if named, ok := ref.(reference.Named); ok { 217 namedRef = reference.TagNameOnly(named) 218 219 if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !opts.untrusted { 220 var err error 221 trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil) 222 if err != nil { 223 return nil, err 224 } 225 config.Image = reference.FamiliarString(trustedRef) 226 } 227 } 228 229 pullAndTagImage := func() error { 230 if err := pullImage(ctx, dockerCli, config.Image, opts.platform, stderr); err != nil { 231 return err 232 } 233 if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil { 234 return image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef) 235 } 236 return nil 237 } 238 239 var platform *specs.Platform 240 // Engine API version 1.41 first introduced the option to specify platform on 241 // create. It will produce an error if you try to set a platform on older API 242 // versions, so check the API version here to maintain backwards 243 // compatibility for CLI users. 244 if opts.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") { 245 p, err := platforms.Parse(opts.platform) 246 if err != nil { 247 return nil, errors.Wrap(err, "error parsing specified platform") 248 } 249 platform = &p 250 } 251 252 if opts.pull == PullImageAlways { 253 if err := pullAndTagImage(); err != nil { 254 return nil, err 255 } 256 } 257 258 response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name) 259 if err != nil { 260 // Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior. 261 if apiclient.IsErrNotFound(err) && namedRef != nil && opts.pull == PullImageMissing { 262 // we don't want to write to stdout anything apart from container.ID 263 fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef)) 264 if err := pullAndTagImage(); err != nil { 265 return nil, err 266 } 267 268 var retryErr error 269 response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name) 270 if retryErr != nil { 271 return nil, retryErr 272 } 273 } else { 274 return nil, err 275 } 276 } 277 278 for _, warning := range response.Warnings { 279 fmt.Fprintf(stderr, "WARNING: %s\n", warning) 280 } 281 err = containerIDFile.Write(response.ID) 282 return &response, 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 }