github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/daemon/oci_windows.go (about)

     1  package daemon
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  
    11  	containertypes "github.com/docker/docker/api/types/container"
    12  	"github.com/docker/docker/container"
    13  	"github.com/docker/docker/layer"
    14  	"github.com/docker/docker/oci"
    15  	"github.com/docker/docker/pkg/sysinfo"
    16  	"github.com/docker/docker/pkg/system"
    17  	"github.com/opencontainers/runtime-spec/specs-go"
    18  	"golang.org/x/sys/windows"
    19  	"golang.org/x/sys/windows/registry"
    20  )
    21  
    22  const (
    23  	credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
    24  	credentialSpecFileLocation     = "CredentialSpecs"
    25  )
    26  
    27  func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
    28  	img, err := daemon.GetImage(string(c.ImageID))
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	s := oci.DefaultOSSpec(img.OS)
    34  
    35  	linkedEnv, err := daemon.setupLinkedContainers(c)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	// Note, unlike Unix, we do NOT call into SetupWorkingDirectory as
    41  	// this is done in VMCompute. Further, we couldn't do it for Hyper-V
    42  	// containers anyway.
    43  
    44  	// In base spec
    45  	s.Hostname = c.FullHostname()
    46  
    47  	if err := daemon.setupSecretDir(c); err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	if err := daemon.setupConfigDir(c); err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	// In s.Mounts
    56  	mounts, err := daemon.setupMounts(c)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	var isHyperV bool
    62  	if c.HostConfig.Isolation.IsDefault() {
    63  		// Container using default isolation, so take the default from the daemon configuration
    64  		isHyperV = daemon.defaultIsolation.IsHyperV()
    65  	} else {
    66  		// Container may be requesting an explicit isolation mode.
    67  		isHyperV = c.HostConfig.Isolation.IsHyperV()
    68  	}
    69  
    70  	if isHyperV {
    71  		s.Windows.HyperV = &specs.WindowsHyperV{}
    72  	}
    73  
    74  	// If the container has not been started, and has configs or secrets
    75  	// secrets, create symlinks to each config and secret. If it has been
    76  	// started before, the symlinks should have already been created. Also, it
    77  	// is important to not mount a Hyper-V  container that has been started
    78  	// before, to protect the host from the container; for example, from
    79  	// malicious mutation of NTFS data structures.
    80  	if !c.HasBeenStartedBefore && (len(c.SecretReferences) > 0 || len(c.ConfigReferences) > 0) {
    81  		// The container file system is mounted before this function is called,
    82  		// except for Hyper-V containers, so mount it here in that case.
    83  		if isHyperV {
    84  			if err := daemon.Mount(c); err != nil {
    85  				return nil, err
    86  			}
    87  			defer daemon.Unmount(c)
    88  		}
    89  		if err := c.CreateSecretSymlinks(); err != nil {
    90  			return nil, err
    91  		}
    92  		if err := c.CreateConfigSymlinks(); err != nil {
    93  			return nil, err
    94  		}
    95  	}
    96  
    97  	if m := c.SecretMounts(); m != nil {
    98  		mounts = append(mounts, m...)
    99  	}
   100  
   101  	if m := c.ConfigMounts(); m != nil {
   102  		mounts = append(mounts, m...)
   103  	}
   104  
   105  	for _, mount := range mounts {
   106  		m := specs.Mount{
   107  			Source:      mount.Source,
   108  			Destination: mount.Destination,
   109  		}
   110  		if !mount.Writable {
   111  			m.Options = append(m.Options, "ro")
   112  		}
   113  		if img.OS != runtime.GOOS {
   114  			m.Type = "bind"
   115  			m.Options = append(m.Options, "rbind")
   116  			m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID))
   117  		}
   118  		s.Mounts = append(s.Mounts, m)
   119  	}
   120  
   121  	// In s.Process
   122  	s.Process.Args = append([]string{c.Path}, c.Args...)
   123  	if !c.Config.ArgsEscaped && img.OS == "windows" {
   124  		s.Process.Args = escapeArgs(s.Process.Args)
   125  	}
   126  
   127  	s.Process.Cwd = c.Config.WorkingDir
   128  	s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
   129  	if c.Config.Tty {
   130  		s.Process.Terminal = c.Config.Tty
   131  		s.Process.ConsoleSize = &specs.Box{
   132  			Height: c.HostConfig.ConsoleSize[0],
   133  			Width:  c.HostConfig.ConsoleSize[1],
   134  		}
   135  	}
   136  	s.Process.User.Username = c.Config.User
   137  
   138  	// Get the layer path for each layer.
   139  	max := len(img.RootFS.DiffIDs)
   140  	for i := 1; i <= max; i++ {
   141  		img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
   142  		layerPath, err := layer.GetLayerPath(daemon.stores[c.OS].layerStore, img.RootFS.ChainID())
   143  		if err != nil {
   144  			return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.OS].layerStore, img.RootFS.ChainID(), err)
   145  		}
   146  		// Reverse order, expecting parent most first
   147  		s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
   148  	}
   149  	if c.RWLayer == nil {
   150  		return nil, errors.New("RWLayer of container " + c.ID + " is unexpectedly nil")
   151  	}
   152  	m, err := c.RWLayer.Metadata()
   153  	if err != nil {
   154  		return nil, fmt.Errorf("failed to get layer metadata - %s", err)
   155  	}
   156  	s.Windows.LayerFolders = append(s.Windows.LayerFolders, m["dir"])
   157  
   158  	dnsSearch := daemon.getDNSSearchSettings(c)
   159  
   160  	// Get endpoints for the libnetwork allocated networks to the container
   161  	var epList []string
   162  	AllowUnqualifiedDNSQuery := false
   163  	gwHNSID := ""
   164  	if c.NetworkSettings != nil {
   165  		for n := range c.NetworkSettings.Networks {
   166  			sn, err := daemon.FindNetwork(n)
   167  			if err != nil {
   168  				continue
   169  			}
   170  
   171  			ep, err := c.GetEndpointInNetwork(sn)
   172  			if err != nil {
   173  				continue
   174  			}
   175  
   176  			data, err := ep.DriverInfo()
   177  			if err != nil {
   178  				continue
   179  			}
   180  
   181  			if data["GW_INFO"] != nil {
   182  				gwInfo := data["GW_INFO"].(map[string]interface{})
   183  				if gwInfo["hnsid"] != nil {
   184  					gwHNSID = gwInfo["hnsid"].(string)
   185  				}
   186  			}
   187  
   188  			if data["hnsid"] != nil {
   189  				epList = append(epList, data["hnsid"].(string))
   190  			}
   191  
   192  			if data["AllowUnqualifiedDNSQuery"] != nil {
   193  				AllowUnqualifiedDNSQuery = true
   194  			}
   195  		}
   196  	}
   197  
   198  	var networkSharedContainerID string
   199  	if c.HostConfig.NetworkMode.IsContainer() {
   200  		networkSharedContainerID = c.NetworkSharedContainerID
   201  		for _, ep := range c.SharedEndpointList {
   202  			epList = append(epList, ep)
   203  		}
   204  	}
   205  
   206  	if gwHNSID != "" {
   207  		epList = append(epList, gwHNSID)
   208  	}
   209  
   210  	s.Windows.Network = &specs.WindowsNetwork{
   211  		AllowUnqualifiedDNSQuery:   AllowUnqualifiedDNSQuery,
   212  		DNSSearchList:              dnsSearch,
   213  		EndpointList:               epList,
   214  		NetworkSharedContainerName: networkSharedContainerID,
   215  	}
   216  
   217  	if img.OS == "windows" {
   218  		if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
   219  			return nil, err
   220  		}
   221  	} else {
   222  		// TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode
   223  		if system.LCOWSupported() && img.OS == "linux" {
   224  			daemon.createSpecLinuxFields(c, &s)
   225  		}
   226  	}
   227  
   228  	return (*specs.Spec)(&s), nil
   229  }
   230  
   231  // Sets the Windows-specific fields of the OCI spec
   232  func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
   233  	if len(s.Process.Cwd) == 0 {
   234  		// We default to C:\ to workaround the oddity of the case that the
   235  		// default directory for cmd running as LocalSystem (or
   236  		// ContainerAdministrator) is c:\windows\system32. Hence docker run
   237  		// <image> cmd will by default end in c:\windows\system32, rather
   238  		// than 'root' (/) on Linux. The oddity is that if you have a dockerfile
   239  		// which has no WORKDIR and has a COPY file ., . will be interpreted
   240  		// as c:\. Hence, setting it to default of c:\ makes for consistency.
   241  		s.Process.Cwd = `C:\`
   242  	}
   243  
   244  	s.Root.Readonly = false // Windows does not support a read-only root filesystem
   245  	if !isHyperV {
   246  		s.Root.Path = c.BaseFS.Path() // This is not set for Hyper-V containers
   247  		if !strings.HasSuffix(s.Root.Path, `\`) {
   248  			s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
   249  		}
   250  	}
   251  
   252  	// First boot optimization
   253  	s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore
   254  
   255  	// In s.Windows.Resources
   256  	cpuShares := uint16(c.HostConfig.CPUShares)
   257  	cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100
   258  	cpuCount := uint64(c.HostConfig.CPUCount)
   259  	if c.HostConfig.NanoCPUs > 0 {
   260  		if isHyperV {
   261  			cpuCount = uint64(c.HostConfig.NanoCPUs / 1e9)
   262  			leftoverNanoCPUs := c.HostConfig.NanoCPUs % 1e9
   263  			if leftoverNanoCPUs != 0 {
   264  				cpuCount++
   265  				cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(cpuCount) / (1e9 / 10000))
   266  				if cpuMaximum < 1 {
   267  					// The requested NanoCPUs is so small that we rounded to 0, use 1 instead
   268  					cpuMaximum = 1
   269  				}
   270  			}
   271  		} else {
   272  			cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(sysinfo.NumCPU()) / (1e9 / 10000))
   273  			if cpuMaximum < 1 {
   274  				// The requested NanoCPUs is so small that we rounded to 0, use 1 instead
   275  				cpuMaximum = 1
   276  			}
   277  		}
   278  	}
   279  	memoryLimit := uint64(c.HostConfig.Memory)
   280  	s.Windows.Resources = &specs.WindowsResources{
   281  		CPU: &specs.WindowsCPUResources{
   282  			Maximum: &cpuMaximum,
   283  			Shares:  &cpuShares,
   284  			Count:   &cpuCount,
   285  		},
   286  		Memory: &specs.WindowsMemoryResources{
   287  			Limit: &memoryLimit,
   288  		},
   289  		Storage: &specs.WindowsStorageResources{
   290  			Bps:  &c.HostConfig.IOMaximumBandwidth,
   291  			Iops: &c.HostConfig.IOMaximumIOps,
   292  		},
   293  	}
   294  
   295  	// Read and add credentials from the security options if a credential spec has been provided.
   296  	if c.HostConfig.SecurityOpt != nil {
   297  		cs := ""
   298  		for _, sOpt := range c.HostConfig.SecurityOpt {
   299  			sOpt = strings.ToLower(sOpt)
   300  			if !strings.Contains(sOpt, "=") {
   301  				return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
   302  			}
   303  			var splitsOpt []string
   304  			splitsOpt = strings.SplitN(sOpt, "=", 2)
   305  			if len(splitsOpt) != 2 {
   306  				return fmt.Errorf("invalid security option: %s", sOpt)
   307  			}
   308  			if splitsOpt[0] != "credentialspec" {
   309  				return fmt.Errorf("security option not supported: %s", splitsOpt[0])
   310  			}
   311  
   312  			var (
   313  				match   bool
   314  				csValue string
   315  				err     error
   316  			)
   317  			if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
   318  				if csValue == "" {
   319  					return fmt.Errorf("no value supplied for file:// credential spec security option")
   320  				}
   321  				if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil {
   322  					return err
   323  				}
   324  			} else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
   325  				if csValue == "" {
   326  					return fmt.Errorf("no value supplied for registry:// credential spec security option")
   327  				}
   328  				if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
   329  					return err
   330  				}
   331  			} else {
   332  				return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
   333  			}
   334  		}
   335  		s.Windows.CredentialSpec = cs
   336  	}
   337  
   338  	// Assume we are not starting a container for a servicing operation
   339  	s.Windows.Servicing = false
   340  
   341  	return nil
   342  }
   343  
   344  // Sets the Linux-specific fields of the OCI spec
   345  // TODO: @jhowardmsft LCOW Support. We need to do a lot more pulling in what can
   346  // be pulled in from oci_linux.go.
   347  func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) {
   348  	if len(s.Process.Cwd) == 0 {
   349  		s.Process.Cwd = `/`
   350  	}
   351  	s.Root.Path = "rootfs"
   352  	s.Root.Readonly = c.HostConfig.ReadonlyRootfs
   353  }
   354  
   355  func escapeArgs(args []string) []string {
   356  	escapedArgs := make([]string, len(args))
   357  	for i, a := range args {
   358  		escapedArgs[i] = windows.EscapeArg(a)
   359  	}
   360  	return escapedArgs
   361  }
   362  
   363  // mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig
   364  // It will do nothing on non-Linux platform
   365  func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
   366  	return
   367  }
   368  
   369  // getCredentialSpec is a helper function to get the value of a credential spec supplied
   370  // on the CLI, stripping the prefix
   371  func getCredentialSpec(prefix, value string) (bool, string) {
   372  	if strings.HasPrefix(value, prefix) {
   373  		return true, strings.TrimPrefix(value, prefix)
   374  	}
   375  	return false, ""
   376  }
   377  
   378  // readCredentialSpecRegistry is a helper function to read a credential spec from
   379  // the registry. If not found, we return an empty string and warn in the log.
   380  // This allows for staging on machines which do not have the necessary components.
   381  func readCredentialSpecRegistry(id, name string) (string, error) {
   382  	var (
   383  		k   registry.Key
   384  		err error
   385  		val string
   386  	)
   387  	if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
   388  		return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
   389  	}
   390  	if val, _, err = k.GetStringValue(name); err != nil {
   391  		if err == registry.ErrNotExist {
   392  			return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
   393  		}
   394  		return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
   395  	}
   396  	return val, nil
   397  }
   398  
   399  // readCredentialSpecFile is a helper function to read a credential spec from
   400  // a file. If not found, we return an empty string and warn in the log.
   401  // This allows for staging on machines which do not have the necessary components.
   402  func readCredentialSpecFile(id, root, location string) (string, error) {
   403  	if filepath.IsAbs(location) {
   404  		return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
   405  	}
   406  	base := filepath.Join(root, credentialSpecFileLocation)
   407  	full := filepath.Join(base, location)
   408  	if !strings.HasPrefix(full, base) {
   409  		return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
   410  	}
   411  	bcontents, err := ioutil.ReadFile(full)
   412  	if err != nil {
   413  		return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
   414  	}
   415  	return string(bcontents[:]), nil
   416  }