github.com/moby/docker@v26.1.3+incompatible/daemon/container.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "runtime" 9 "time" 10 11 "github.com/containerd/log" 12 containertypes "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/api/types/strslice" 14 "github.com/docker/docker/container" 15 "github.com/docker/docker/daemon/config" 16 "github.com/docker/docker/daemon/network" 17 "github.com/docker/docker/errdefs" 18 "github.com/docker/docker/image" 19 "github.com/docker/docker/oci/caps" 20 "github.com/docker/docker/opts" 21 "github.com/docker/docker/pkg/system" 22 "github.com/docker/docker/runconfig" 23 volumemounts "github.com/docker/docker/volume/mounts" 24 "github.com/docker/go-connections/nat" 25 "github.com/moby/sys/signal" 26 "github.com/opencontainers/selinux/go-selinux" 27 "github.com/pkg/errors" 28 ) 29 30 // GetContainer looks for a container using the provided information, which could be 31 // one of the following inputs from the caller: 32 // - A full container ID, which will exact match a container in daemon's list 33 // - A container name, which will only exact match via the GetByName() function 34 // - A partial container ID prefix (e.g. short ID) of any length that is 35 // unique enough to only return a single container object 36 // If none of these searches succeed, an error is returned 37 func (daemon *Daemon) GetContainer(prefixOrName string) (*container.Container, error) { 38 if len(prefixOrName) == 0 { 39 return nil, errors.WithStack(invalidIdentifier(prefixOrName)) 40 } 41 42 if containerByID := daemon.containers.Get(prefixOrName); containerByID != nil { 43 // prefix is an exact match to a full container ID 44 return containerByID, nil 45 } 46 47 // GetByName will match only an exact name provided; we ignore errors 48 if containerByName, _ := daemon.GetByName(prefixOrName); containerByName != nil { 49 // prefix is an exact match to a full container Name 50 return containerByName, nil 51 } 52 53 containerID, err := daemon.containersReplica.GetByPrefix(prefixOrName) 54 if err != nil { 55 return nil, err 56 } 57 ctr := daemon.containers.Get(containerID) 58 if ctr == nil { 59 // Updates to the daemon.containersReplica ViewDB are not atomic 60 // or consistent w.r.t. the live daemon.containers Store so 61 // while reaching this code path may be indicative of a bug, 62 // it is not _necessarily_ the case. 63 log.G(context.TODO()).WithField("prefixOrName", prefixOrName). 64 WithField("id", containerID). 65 Debugf("daemon.GetContainer: container is known to daemon.containersReplica but not daemon.containers") 66 return nil, containerNotFound(prefixOrName) 67 } 68 return ctr, nil 69 } 70 71 // Exists returns a true if a container of the specified ID or name exists, 72 // false otherwise. 73 func (daemon *Daemon) Exists(id string) bool { 74 c, _ := daemon.GetContainer(id) 75 return c != nil 76 } 77 78 // IsPaused returns a bool indicating if the specified container is paused. 79 func (daemon *Daemon) IsPaused(id string) bool { 80 c, _ := daemon.GetContainer(id) 81 return c.State.IsPaused() 82 } 83 84 func (daemon *Daemon) containerRoot(id string) string { 85 return filepath.Join(daemon.repository, id) 86 } 87 88 // Load reads the contents of a container from disk 89 // This is typically done at startup. 90 func (daemon *Daemon) load(id string) (*container.Container, error) { 91 ctr := daemon.newBaseContainer(id) 92 93 if err := ctr.FromDisk(); err != nil { 94 return nil, err 95 } 96 selinux.ReserveLabel(ctr.ProcessLabel) 97 98 if ctr.ID != id { 99 return ctr, fmt.Errorf("Container %s is stored at %s", ctr.ID, id) 100 } 101 102 return ctr, nil 103 } 104 105 // Register makes a container object usable by the daemon as <container.ID> 106 func (daemon *Daemon) Register(c *container.Container) error { 107 // Attach to stdout and stderr 108 if c.Config.OpenStdin { 109 c.StreamConfig.NewInputPipes() 110 } else { 111 c.StreamConfig.NewNopInputPipe() 112 } 113 114 // once in the memory store it is visible to other goroutines 115 // grab a Lock until it has been checkpointed to avoid races 116 c.Lock() 117 defer c.Unlock() 118 119 daemon.containers.Add(c.ID, c) 120 return c.CheckpointTo(daemon.containersReplica) 121 } 122 123 func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) { 124 var ( 125 id string 126 err error 127 ) 128 id, name, err = daemon.generateIDAndName(name) 129 if err != nil { 130 return nil, err 131 } 132 133 if hostConfig.NetworkMode.IsHost() { 134 if config.Hostname == "" { 135 config.Hostname, err = os.Hostname() 136 if err != nil { 137 return nil, errdefs.System(err) 138 } 139 } 140 } else { 141 daemon.generateHostname(id, config) 142 } 143 entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd) 144 145 base := daemon.newBaseContainer(id) 146 base.Created = time.Now().UTC() 147 base.Managed = managed 148 base.Path = entrypoint 149 base.Args = args // FIXME: de-duplicate from config 150 base.Config = config 151 base.HostConfig = &containertypes.HostConfig{} 152 base.ImageID = imgID 153 base.NetworkSettings = &network.Settings{} 154 base.Name = name 155 base.Driver = daemon.imageService.StorageDriver() 156 base.OS = operatingSystem 157 return base, err 158 } 159 160 // GetByName returns a container given a name. 161 func (daemon *Daemon) GetByName(name string) (*container.Container, error) { 162 if len(name) == 0 { 163 return nil, fmt.Errorf("No container name supplied") 164 } 165 fullName := name 166 if name[0] != '/' { 167 fullName = "/" + name 168 } 169 id, err := daemon.containersReplica.Snapshot().GetID(fullName) 170 if err != nil { 171 return nil, fmt.Errorf("Could not find entity for %s", name) 172 } 173 e := daemon.containers.Get(id) 174 if e == nil { 175 return nil, fmt.Errorf("Could not find container for entity id %s", id) 176 } 177 return e, nil 178 } 179 180 // newBaseContainer creates a new container with its initial 181 // configuration based on the root storage from the daemon. 182 func (daemon *Daemon) newBaseContainer(id string) *container.Container { 183 return container.NewBaseContainer(id, daemon.containerRoot(id)) 184 } 185 186 func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint strslice.StrSlice, configCmd strslice.StrSlice) (string, []string) { 187 if len(configEntrypoint) != 0 { 188 return configEntrypoint[0], append(configEntrypoint[1:], configCmd...) 189 } 190 return configCmd[0], configCmd[1:] 191 } 192 193 func (daemon *Daemon) generateHostname(id string, config *containertypes.Config) { 194 // Generate default hostname 195 if config.Hostname == "" { 196 config.Hostname = id[:12] 197 } 198 } 199 200 func (daemon *Daemon) setSecurityOptions(cfg *config.Config, container *container.Container, hostConfig *containertypes.HostConfig) error { 201 container.Lock() 202 defer container.Unlock() 203 return daemon.parseSecurityOpt(cfg, &container.SecurityOptions, hostConfig) 204 } 205 206 func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *containertypes.HostConfig, defaultReadOnlyNonRecursive bool) error { 207 // Do not lock while creating volumes since this could be calling out to external plugins 208 // Don't want to block other actions, like `docker ps` because we're waiting on an external plugin 209 if err := daemon.registerMountPoints(container, hostConfig, defaultReadOnlyNonRecursive); err != nil { 210 return err 211 } 212 213 container.Lock() 214 defer container.Unlock() 215 216 // Register any links from the host config before starting the container 217 if err := daemon.registerLinks(container, hostConfig); err != nil { 218 return err 219 } 220 221 runconfig.SetDefaultNetModeIfBlank(hostConfig) 222 container.HostConfig = hostConfig 223 return nil 224 } 225 226 // verifyContainerSettings performs validation of the hostconfig and config 227 // structures. 228 func (daemon *Daemon) verifyContainerSettings(daemonCfg *configStore, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) (warnings []string, err error) { 229 // First perform verification of settings common across all platforms. 230 if err = validateContainerConfig(config); err != nil { 231 return warnings, err 232 } 233 if err := validateHostConfig(hostConfig); err != nil { 234 return warnings, err 235 } 236 237 // Now do platform-specific verification 238 warnings, err = verifyPlatformContainerSettings(daemon, daemonCfg, hostConfig, update) 239 for _, w := range warnings { 240 log.G(context.TODO()).Warn(w) 241 } 242 return warnings, err 243 } 244 245 func validateContainerConfig(config *containertypes.Config) error { 246 if config == nil { 247 return nil 248 } 249 if err := translateWorkingDir(config); err != nil { 250 return err 251 } 252 if len(config.StopSignal) > 0 { 253 if _, err := signal.ParseSignal(config.StopSignal); err != nil { 254 return err 255 } 256 } 257 // Validate if Env contains empty variable or not (e.g., ``, `=foo`) 258 for _, env := range config.Env { 259 if _, err := opts.ValidateEnv(env); err != nil { 260 return err 261 } 262 } 263 return validateHealthCheck(config.Healthcheck) 264 } 265 266 func validateHostConfig(hostConfig *containertypes.HostConfig) error { 267 if hostConfig == nil { 268 return nil 269 } 270 271 if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() { 272 return errors.Errorf("can't create 'AutoRemove' container with restart policy") 273 } 274 // Validate mounts; check if host directories still exist 275 parser := volumemounts.NewParser() 276 for _, c := range hostConfig.Mounts { 277 cfg := c 278 if err := parser.ValidateMountConfig(&cfg); err != nil { 279 return err 280 } 281 } 282 for _, extraHost := range hostConfig.ExtraHosts { 283 if _, err := opts.ValidateExtraHost(extraHost); err != nil { 284 return err 285 } 286 } 287 if err := validatePortBindings(hostConfig.PortBindings); err != nil { 288 return err 289 } 290 if err := containertypes.ValidateRestartPolicy(hostConfig.RestartPolicy); err != nil { 291 return err 292 } 293 if err := validateCapabilities(hostConfig); err != nil { 294 return err 295 } 296 if !hostConfig.Isolation.IsValid() { 297 return errors.Errorf("invalid isolation '%s' on %s", hostConfig.Isolation, runtime.GOOS) 298 } 299 for k := range hostConfig.Annotations { 300 if k == "" { 301 return errors.Errorf("invalid Annotations: the empty string is not permitted as an annotation key") 302 } 303 } 304 return nil 305 } 306 307 func validateCapabilities(hostConfig *containertypes.HostConfig) error { 308 if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapAdd); err != nil { 309 return errors.Wrap(err, "invalid CapAdd") 310 } 311 if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapDrop); err != nil { 312 return errors.Wrap(err, "invalid CapDrop") 313 } 314 // TODO consider returning warnings if "Privileged" is combined with Capabilities, CapAdd and/or CapDrop 315 return nil 316 } 317 318 // validateHealthCheck validates the healthcheck params of Config 319 func validateHealthCheck(healthConfig *containertypes.HealthConfig) error { 320 if healthConfig == nil { 321 return nil 322 } 323 if healthConfig.Interval != 0 && healthConfig.Interval < containertypes.MinimumDuration { 324 return errors.Errorf("Interval in Healthcheck cannot be less than %s", containertypes.MinimumDuration) 325 } 326 if healthConfig.Timeout != 0 && healthConfig.Timeout < containertypes.MinimumDuration { 327 return errors.Errorf("Timeout in Healthcheck cannot be less than %s", containertypes.MinimumDuration) 328 } 329 if healthConfig.Retries < 0 { 330 return errors.Errorf("Retries in Healthcheck cannot be negative") 331 } 332 if healthConfig.StartPeriod != 0 && healthConfig.StartPeriod < containertypes.MinimumDuration { 333 return errors.Errorf("StartPeriod in Healthcheck cannot be less than %s", containertypes.MinimumDuration) 334 } 335 if healthConfig.StartInterval != 0 && healthConfig.StartInterval < containertypes.MinimumDuration { 336 return errors.Errorf("StartInterval in Healthcheck cannot be less than %s", containertypes.MinimumDuration) 337 } 338 return nil 339 } 340 341 func validatePortBindings(ports nat.PortMap) error { 342 for port := range ports { 343 _, portStr := nat.SplitProtoPort(string(port)) 344 if _, err := nat.ParsePort(portStr); err != nil { 345 return errors.Errorf("invalid port specification: %q", portStr) 346 } 347 for _, pb := range ports[port] { 348 _, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort)) 349 if err != nil { 350 return errors.Errorf("invalid port specification: %q", pb.HostPort) 351 } 352 } 353 } 354 return nil 355 } 356 357 // translateWorkingDir translates the working-dir for the target platform, 358 // and returns an error if the given path is not an absolute path. 359 func translateWorkingDir(config *containertypes.Config) error { 360 if config.WorkingDir == "" { 361 return nil 362 } 363 wd := filepath.FromSlash(config.WorkingDir) // Ensure in platform semantics 364 if !system.IsAbs(wd) { 365 return fmt.Errorf("the working directory '%s' is invalid, it needs to be an absolute path", config.WorkingDir) 366 } 367 config.WorkingDir = filepath.Clean(wd) 368 return nil 369 }