github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/daemon/container.go (about)

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