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