github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/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  }