Signed-off-by: Darren Stahl <darst@microsoft.com>
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
| ... | ... |
@@ -22,22 +22,17 @@ func (daemon *Daemon) postRunProcessing(container *container.Container, e libcon |
| 22 | 22 |
return err |
| 23 | 23 |
} |
| 24 | 24 |
|
| 25 |
- newOpts := []libcontainerd.CreateOption{&libcontainerd.ServicingOption{
|
|
| 26 |
- IsServicing: true, |
|
| 27 |
- }} |
|
| 25 |
+ // Turn on servicing |
|
| 26 |
+ spec.Windows.Servicing = true |
|
| 28 | 27 |
|
| 29 | 28 |
copts, err := daemon.getLibcontainerdCreateOptions(container) |
| 30 | 29 |
if err != nil {
|
| 31 | 30 |
return err |
| 32 | 31 |
} |
| 33 | 32 |
|
| 34 |
- if copts != nil {
|
|
| 35 |
- newOpts = append(newOpts, copts...) |
|
| 36 |
- } |
|
| 37 |
- |
|
| 38 | 33 |
// Create a new servicing container, which will start, complete the update, and merge back the |
| 39 | 34 |
// results if it succeeded, all as part of the below function call. |
| 40 |
- if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, container.InitializeStdio, newOpts...); err != nil {
|
|
| 35 |
+ if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, container.InitializeStdio, copts...); err != nil {
|
|
| 41 | 36 |
container.SetExitCode(-1) |
| 42 | 37 |
return fmt.Errorf("Post-run update servicing failed: %s", err)
|
| 43 | 38 |
} |
| ... | ... |
@@ -1,13 +1,25 @@ |
| 1 | 1 |
package daemon |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "strings" |
|
| 8 |
+ |
|
| 4 | 9 |
containertypes "github.com/docker/docker/api/types/container" |
| 5 | 10 |
"github.com/docker/docker/container" |
| 11 |
+ "github.com/docker/docker/layer" |
|
| 6 | 12 |
"github.com/docker/docker/oci" |
| 7 | 13 |
"github.com/docker/docker/pkg/sysinfo" |
| 8 | 14 |
"github.com/docker/docker/pkg/system" |
| 9 | 15 |
"github.com/opencontainers/runtime-spec/specs-go" |
| 10 | 16 |
"golang.org/x/sys/windows" |
| 17 |
+ "golang.org/x/sys/windows/registry" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+const ( |
|
| 21 |
+ credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs` |
|
| 22 |
+ credentialSpecFileLocation = "CredentialSpecs" |
|
| 11 | 23 |
) |
| 12 | 24 |
|
| 13 | 25 |
func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| ... | ... |
@@ -53,6 +65,10 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 53 | 53 |
isHyperV = c.HostConfig.Isolation.IsHyperV() |
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 |
+ if isHyperV {
|
|
| 57 |
+ s.Windows.HyperV = &specs.WindowsHyperV{}
|
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 56 | 60 |
// If the container has not been started, and has configs or secrets |
| 57 | 61 |
// secrets, create symlinks to each config and secret. If it has been |
| 58 | 62 |
// started before, the symlinks should have already been created. Also, it |
| ... | ... |
@@ -105,13 +121,93 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 105 | 105 |
s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv) |
| 106 | 106 |
if c.Config.Tty {
|
| 107 | 107 |
s.Process.Terminal = c.Config.Tty |
| 108 |
- s.Process.ConsoleSize.Height = c.HostConfig.ConsoleSize[0] |
|
| 109 |
- s.Process.ConsoleSize.Width = c.HostConfig.ConsoleSize[1] |
|
| 108 |
+ s.Process.ConsoleSize = &specs.Box{
|
|
| 109 |
+ Height: c.HostConfig.ConsoleSize[0], |
|
| 110 |
+ Width: c.HostConfig.ConsoleSize[1], |
|
| 111 |
+ } |
|
| 110 | 112 |
} |
| 111 | 113 |
s.Process.User.Username = c.Config.User |
| 112 | 114 |
|
| 115 |
+ // Get the layer path for each layer. |
|
| 116 |
+ max := len(img.RootFS.DiffIDs) |
|
| 117 |
+ for i := 1; i <= max; i++ {
|
|
| 118 |
+ img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i] |
|
| 119 |
+ layerPath, err := layer.GetLayerPath(daemon.stores[c.Platform].layerStore, img.RootFS.ChainID()) |
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.Platform].layerStore, img.RootFS.ChainID(), err)
|
|
| 122 |
+ } |
|
| 123 |
+ // Reverse order, expecting parent most first |
|
| 124 |
+ s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
|
|
| 125 |
+ } |
|
| 126 |
+ m, err := c.RWLayer.Metadata() |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ return nil, fmt.Errorf("failed to get layer metadata - %s", err)
|
|
| 129 |
+ } |
|
| 130 |
+ s.Windows.LayerFolders = append(s.Windows.LayerFolders, m["dir"]) |
|
| 131 |
+ |
|
| 132 |
+ dnsSearch := daemon.getDNSSearchSettings(c) |
|
| 133 |
+ |
|
| 134 |
+ // Get endpoints for the libnetwork allocated networks to the container |
|
| 135 |
+ var epList []string |
|
| 136 |
+ AllowUnqualifiedDNSQuery := false |
|
| 137 |
+ gwHNSID := "" |
|
| 138 |
+ if c.NetworkSettings != nil {
|
|
| 139 |
+ for n := range c.NetworkSettings.Networks {
|
|
| 140 |
+ sn, err := daemon.FindNetwork(n) |
|
| 141 |
+ if err != nil {
|
|
| 142 |
+ continue |
|
| 143 |
+ } |
|
| 144 |
+ |
|
| 145 |
+ ep, err := c.GetEndpointInNetwork(sn) |
|
| 146 |
+ if err != nil {
|
|
| 147 |
+ continue |
|
| 148 |
+ } |
|
| 149 |
+ |
|
| 150 |
+ data, err := ep.DriverInfo() |
|
| 151 |
+ if err != nil {
|
|
| 152 |
+ continue |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ if data["GW_INFO"] != nil {
|
|
| 156 |
+ gwInfo := data["GW_INFO"].(map[string]interface{})
|
|
| 157 |
+ if gwInfo["hnsid"] != nil {
|
|
| 158 |
+ gwHNSID = gwInfo["hnsid"].(string) |
|
| 159 |
+ } |
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ if data["hnsid"] != nil {
|
|
| 163 |
+ epList = append(epList, data["hnsid"].(string)) |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ if data["AllowUnqualifiedDNSQuery"] != nil {
|
|
| 167 |
+ AllowUnqualifiedDNSQuery = true |
|
| 168 |
+ } |
|
| 169 |
+ } |
|
| 170 |
+ } |
|
| 171 |
+ |
|
| 172 |
+ var networkSharedContainerID string |
|
| 173 |
+ if c.HostConfig.NetworkMode.IsContainer() {
|
|
| 174 |
+ networkSharedContainerID = c.NetworkSharedContainerID |
|
| 175 |
+ for _, ep := range c.SharedEndpointList {
|
|
| 176 |
+ epList = append(epList, ep) |
|
| 177 |
+ } |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ if gwHNSID != "" {
|
|
| 181 |
+ epList = append(epList, gwHNSID) |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ s.Windows.Network = &specs.WindowsNetwork{
|
|
| 185 |
+ AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery, |
|
| 186 |
+ DNSSearchList: dnsSearch, |
|
| 187 |
+ EndpointList: epList, |
|
| 188 |
+ NetworkSharedContainerName: networkSharedContainerID, |
|
| 189 |
+ } |
|
| 190 |
+ |
|
| 113 | 191 |
if img.OS == "windows" {
|
| 114 |
- daemon.createSpecWindowsFields(c, &s, isHyperV) |
|
| 192 |
+ if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
|
|
| 193 |
+ return nil, err |
|
| 194 |
+ } |
|
| 115 | 195 |
} else {
|
| 116 | 196 |
// TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode |
| 117 | 197 |
if system.LCOWSupported() && img.OS == "linux" {
|
| ... | ... |
@@ -123,7 +219,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 123 | 123 |
} |
| 124 | 124 |
|
| 125 | 125 |
// Sets the Windows-specific fields of the OCI spec |
| 126 |
-func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) {
|
|
| 126 |
+func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
|
|
| 127 | 127 |
if len(s.Process.Cwd) == 0 {
|
| 128 | 128 |
// We default to C:\ to workaround the oddity of the case that the |
| 129 | 129 |
// default directory for cmd running as LocalSystem (or |
| ... | ... |
@@ -138,8 +234,14 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S |
| 138 | 138 |
s.Root.Readonly = false // Windows does not support a read-only root filesystem |
| 139 | 139 |
if !isHyperV {
|
| 140 | 140 |
s.Root.Path = c.BaseFS // This is not set for Hyper-V containers |
| 141 |
+ if !strings.HasSuffix(s.Root.Path, `\`) {
|
|
| 142 |
+ s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
|
|
| 143 |
+ } |
|
| 141 | 144 |
} |
| 142 | 145 |
|
| 146 |
+ // First boot optimization |
|
| 147 |
+ s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore |
|
| 148 |
+ |
|
| 143 | 149 |
// In s.Windows.Resources |
| 144 | 150 |
cpuShares := uint16(c.HostConfig.CPUShares) |
| 145 | 151 |
cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100 |
| ... | ... |
@@ -179,6 +281,54 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S |
| 179 | 179 |
Iops: &c.HostConfig.IOMaximumIOps, |
| 180 | 180 |
}, |
| 181 | 181 |
} |
| 182 |
+ |
|
| 183 |
+ // Read and add credentials from the security options if a credential spec has been provided. |
|
| 184 |
+ if c.HostConfig.SecurityOpt != nil {
|
|
| 185 |
+ cs := "" |
|
| 186 |
+ for _, sOpt := range c.HostConfig.SecurityOpt {
|
|
| 187 |
+ sOpt = strings.ToLower(sOpt) |
|
| 188 |
+ if !strings.Contains(sOpt, "=") {
|
|
| 189 |
+ return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
|
|
| 190 |
+ } |
|
| 191 |
+ var splitsOpt []string |
|
| 192 |
+ splitsOpt = strings.SplitN(sOpt, "=", 2) |
|
| 193 |
+ if len(splitsOpt) != 2 {
|
|
| 194 |
+ return fmt.Errorf("invalid security option: %s", sOpt)
|
|
| 195 |
+ } |
|
| 196 |
+ if splitsOpt[0] != "credentialspec" {
|
|
| 197 |
+ return fmt.Errorf("security option not supported: %s", splitsOpt[0])
|
|
| 198 |
+ } |
|
| 199 |
+ |
|
| 200 |
+ var ( |
|
| 201 |
+ match bool |
|
| 202 |
+ csValue string |
|
| 203 |
+ err error |
|
| 204 |
+ ) |
|
| 205 |
+ if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
|
|
| 206 |
+ if csValue == "" {
|
|
| 207 |
+ return fmt.Errorf("no value supplied for file:// credential spec security option")
|
|
| 208 |
+ } |
|
| 209 |
+ if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil {
|
|
| 210 |
+ return err |
|
| 211 |
+ } |
|
| 212 |
+ } else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
|
|
| 213 |
+ if csValue == "" {
|
|
| 214 |
+ return fmt.Errorf("no value supplied for registry:// credential spec security option")
|
|
| 215 |
+ } |
|
| 216 |
+ if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
|
|
| 217 |
+ return err |
|
| 218 |
+ } |
|
| 219 |
+ } else {
|
|
| 220 |
+ return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
|
|
| 221 |
+ } |
|
| 222 |
+ } |
|
| 223 |
+ s.Windows.CredentialSpec = cs |
|
| 224 |
+ } |
|
| 225 |
+ |
|
| 226 |
+ // Assume we are not starting a container for a servicing operation |
|
| 227 |
+ s.Windows.Servicing = false |
|
| 228 |
+ |
|
| 229 |
+ return nil |
|
| 182 | 230 |
} |
| 183 | 231 |
|
| 184 | 232 |
// Sets the Linux-specific fields of the OCI spec |
| ... | ... |
@@ -205,3 +355,52 @@ func escapeArgs(args []string) []string {
|
| 205 | 205 |
func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
|
| 206 | 206 |
return |
| 207 | 207 |
} |
| 208 |
+ |
|
| 209 |
+// getCredentialSpec is a helper function to get the value of a credential spec supplied |
|
| 210 |
+// on the CLI, stripping the prefix |
|
| 211 |
+func getCredentialSpec(prefix, value string) (bool, string) {
|
|
| 212 |
+ if strings.HasPrefix(value, prefix) {
|
|
| 213 |
+ return true, strings.TrimPrefix(value, prefix) |
|
| 214 |
+ } |
|
| 215 |
+ return false, "" |
|
| 216 |
+} |
|
| 217 |
+ |
|
| 218 |
+// readCredentialSpecRegistry is a helper function to read a credential spec from |
|
| 219 |
+// the registry. If not found, we return an empty string and warn in the log. |
|
| 220 |
+// This allows for staging on machines which do not have the necessary components. |
|
| 221 |
+func readCredentialSpecRegistry(id, name string) (string, error) {
|
|
| 222 |
+ var ( |
|
| 223 |
+ k registry.Key |
|
| 224 |
+ err error |
|
| 225 |
+ val string |
|
| 226 |
+ ) |
|
| 227 |
+ if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
|
|
| 228 |
+ return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
|
|
| 229 |
+ } |
|
| 230 |
+ if val, _, err = k.GetStringValue(name); err != nil {
|
|
| 231 |
+ if err == registry.ErrNotExist {
|
|
| 232 |
+ return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
|
|
| 233 |
+ } |
|
| 234 |
+ return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
|
|
| 235 |
+ } |
|
| 236 |
+ return val, nil |
|
| 237 |
+} |
|
| 238 |
+ |
|
| 239 |
+// readCredentialSpecFile is a helper function to read a credential spec from |
|
| 240 |
+// a file. If not found, we return an empty string and warn in the log. |
|
| 241 |
+// This allows for staging on machines which do not have the necessary components. |
|
| 242 |
+func readCredentialSpecFile(id, root, location string) (string, error) {
|
|
| 243 |
+ if filepath.IsAbs(location) {
|
|
| 244 |
+ return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
|
|
| 245 |
+ } |
|
| 246 |
+ base := filepath.Join(root, credentialSpecFileLocation) |
|
| 247 |
+ full := filepath.Join(base, location) |
|
| 248 |
+ if !strings.HasPrefix(full, base) {
|
|
| 249 |
+ return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
|
|
| 250 |
+ } |
|
| 251 |
+ bcontents, err := ioutil.ReadFile(full) |
|
| 252 |
+ if err != nil {
|
|
| 253 |
+ return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
|
|
| 254 |
+ } |
|
| 255 |
+ return string(bcontents[:]), nil |
|
| 256 |
+} |
| ... | ... |
@@ -1,148 +1,14 @@ |
| 1 | 1 |
package daemon |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "strings" |
|
| 8 |
- |
|
| 9 | 4 |
"github.com/Microsoft/opengcs/client" |
| 10 | 5 |
"github.com/docker/docker/container" |
| 11 |
- "github.com/docker/docker/layer" |
|
| 12 | 6 |
"github.com/docker/docker/libcontainerd" |
| 13 |
- "golang.org/x/sys/windows/registry" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-const ( |
|
| 17 |
- credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs` |
|
| 18 |
- credentialSpecFileLocation = "CredentialSpecs" |
|
| 19 | 7 |
) |
| 20 | 8 |
|
| 21 | 9 |
func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Container) ([]libcontainerd.CreateOption, error) {
|
| 22 | 10 |
createOptions := []libcontainerd.CreateOption{}
|
| 23 | 11 |
|
| 24 |
- // Are we going to run as a Hyper-V container? |
|
| 25 |
- hvOpts := &libcontainerd.HyperVIsolationOption{}
|
|
| 26 |
- if container.HostConfig.Isolation.IsDefault() {
|
|
| 27 |
- // Container is set to use the default, so take the default from the daemon configuration |
|
| 28 |
- hvOpts.IsHyperV = daemon.defaultIsolation.IsHyperV() |
|
| 29 |
- } else {
|
|
| 30 |
- // Container is requesting an isolation mode. Honour it. |
|
| 31 |
- hvOpts.IsHyperV = container.HostConfig.Isolation.IsHyperV() |
|
| 32 |
- } |
|
| 33 |
- |
|
| 34 |
- dnsSearch := daemon.getDNSSearchSettings(container) |
|
| 35 |
- |
|
| 36 |
- // Generate the layer folder of the layer options |
|
| 37 |
- layerOpts := &libcontainerd.LayerOption{}
|
|
| 38 |
- m, err := container.RWLayer.Metadata() |
|
| 39 |
- if err != nil {
|
|
| 40 |
- return nil, fmt.Errorf("failed to get layer metadata - %s", err)
|
|
| 41 |
- } |
|
| 42 |
- layerOpts.LayerFolderPath = m["dir"] |
|
| 43 |
- |
|
| 44 |
- // Generate the layer paths of the layer options |
|
| 45 |
- img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID) |
|
| 46 |
- if err != nil {
|
|
| 47 |
- return nil, fmt.Errorf("failed to graph.Get on ImageID %s - %s", container.ImageID, err)
|
|
| 48 |
- } |
|
| 49 |
- // Get the layer path for each layer. |
|
| 50 |
- max := len(img.RootFS.DiffIDs) |
|
| 51 |
- for i := 1; i <= max; i++ {
|
|
| 52 |
- img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i] |
|
| 53 |
- layerPath, err := layer.GetLayerPath(daemon.stores[container.Platform].layerStore, img.RootFS.ChainID()) |
|
| 54 |
- if err != nil {
|
|
| 55 |
- return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[container.Platform].layerStore, img.RootFS.ChainID(), err)
|
|
| 56 |
- } |
|
| 57 |
- // Reverse order, expecting parent most first |
|
| 58 |
- layerOpts.LayerPaths = append([]string{layerPath}, layerOpts.LayerPaths...)
|
|
| 59 |
- } |
|
| 60 |
- |
|
| 61 |
- // Get endpoints for the libnetwork allocated networks to the container |
|
| 62 |
- var epList []string |
|
| 63 |
- AllowUnqualifiedDNSQuery := false |
|
| 64 |
- gwHNSID := "" |
|
| 65 |
- if container.NetworkSettings != nil {
|
|
| 66 |
- for n := range container.NetworkSettings.Networks {
|
|
| 67 |
- sn, err := daemon.FindNetwork(n) |
|
| 68 |
- if err != nil {
|
|
| 69 |
- continue |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- ep, err := container.GetEndpointInNetwork(sn) |
|
| 73 |
- if err != nil {
|
|
| 74 |
- continue |
|
| 75 |
- } |
|
| 76 |
- |
|
| 77 |
- data, err := ep.DriverInfo() |
|
| 78 |
- if err != nil {
|
|
| 79 |
- continue |
|
| 80 |
- } |
|
| 81 |
- |
|
| 82 |
- if data["GW_INFO"] != nil {
|
|
| 83 |
- gwInfo := data["GW_INFO"].(map[string]interface{})
|
|
| 84 |
- if gwInfo["hnsid"] != nil {
|
|
| 85 |
- gwHNSID = gwInfo["hnsid"].(string) |
|
| 86 |
- } |
|
| 87 |
- } |
|
| 88 |
- |
|
| 89 |
- if data["hnsid"] != nil {
|
|
| 90 |
- epList = append(epList, data["hnsid"].(string)) |
|
| 91 |
- } |
|
| 92 |
- |
|
| 93 |
- if data["AllowUnqualifiedDNSQuery"] != nil {
|
|
| 94 |
- AllowUnqualifiedDNSQuery = true |
|
| 95 |
- } |
|
| 96 |
- } |
|
| 97 |
- } |
|
| 98 |
- |
|
| 99 |
- if gwHNSID != "" {
|
|
| 100 |
- epList = append(epList, gwHNSID) |
|
| 101 |
- } |
|
| 102 |
- |
|
| 103 |
- // Read and add credentials from the security options if a credential spec has been provided. |
|
| 104 |
- if container.HostConfig.SecurityOpt != nil {
|
|
| 105 |
- for _, sOpt := range container.HostConfig.SecurityOpt {
|
|
| 106 |
- sOpt = strings.ToLower(sOpt) |
|
| 107 |
- if !strings.Contains(sOpt, "=") {
|
|
| 108 |
- return nil, fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
|
|
| 109 |
- } |
|
| 110 |
- var splitsOpt []string |
|
| 111 |
- splitsOpt = strings.SplitN(sOpt, "=", 2) |
|
| 112 |
- if len(splitsOpt) != 2 {
|
|
| 113 |
- return nil, fmt.Errorf("invalid security option: %s", sOpt)
|
|
| 114 |
- } |
|
| 115 |
- if splitsOpt[0] != "credentialspec" {
|
|
| 116 |
- return nil, fmt.Errorf("security option not supported: %s", splitsOpt[0])
|
|
| 117 |
- } |
|
| 118 |
- |
|
| 119 |
- credentialsOpts := &libcontainerd.CredentialsOption{}
|
|
| 120 |
- var ( |
|
| 121 |
- match bool |
|
| 122 |
- csValue string |
|
| 123 |
- err error |
|
| 124 |
- ) |
|
| 125 |
- if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
|
|
| 126 |
- if csValue == "" {
|
|
| 127 |
- return nil, fmt.Errorf("no value supplied for file:// credential spec security option")
|
|
| 128 |
- } |
|
| 129 |
- if credentialsOpts.Credentials, err = readCredentialSpecFile(container.ID, daemon.root, filepath.Clean(csValue)); err != nil {
|
|
| 130 |
- return nil, err |
|
| 131 |
- } |
|
| 132 |
- } else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
|
|
| 133 |
- if csValue == "" {
|
|
| 134 |
- return nil, fmt.Errorf("no value supplied for registry:// credential spec security option")
|
|
| 135 |
- } |
|
| 136 |
- if credentialsOpts.Credentials, err = readCredentialSpecRegistry(container.ID, csValue); err != nil {
|
|
| 137 |
- return nil, err |
|
| 138 |
- } |
|
| 139 |
- } else {
|
|
| 140 |
- return nil, fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
|
|
| 141 |
- } |
|
| 142 |
- createOptions = append(createOptions, credentialsOpts) |
|
| 143 |
- } |
|
| 144 |
- } |
|
| 145 |
- |
|
| 146 | 12 |
// LCOW options. |
| 147 | 13 |
if container.Platform == "linux" {
|
| 148 | 14 |
config := &client.Config{}
|
| ... | ... |
@@ -173,73 +39,5 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain |
| 173 | 173 |
createOptions = append(createOptions, lcowOpts) |
| 174 | 174 |
} |
| 175 | 175 |
|
| 176 |
- // Now add the remaining options. |
|
| 177 |
- createOptions = append(createOptions, &libcontainerd.FlushOption{IgnoreFlushesDuringBoot: !container.HasBeenStartedBefore})
|
|
| 178 |
- createOptions = append(createOptions, hvOpts) |
|
| 179 |
- createOptions = append(createOptions, layerOpts) |
|
| 180 |
- |
|
| 181 |
- var networkSharedContainerID string |
|
| 182 |
- if container.HostConfig.NetworkMode.IsContainer() {
|
|
| 183 |
- networkSharedContainerID = container.NetworkSharedContainerID |
|
| 184 |
- for _, ep := range container.SharedEndpointList {
|
|
| 185 |
- epList = append(epList, ep) |
|
| 186 |
- } |
|
| 187 |
- } |
|
| 188 |
- |
|
| 189 |
- createOptions = append(createOptions, &libcontainerd.NetworkEndpointsOption{
|
|
| 190 |
- Endpoints: epList, |
|
| 191 |
- AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery, |
|
| 192 |
- DNSSearchList: dnsSearch, |
|
| 193 |
- NetworkSharedContainerID: networkSharedContainerID, |
|
| 194 |
- }) |
|
| 195 | 176 |
return createOptions, nil |
| 196 | 177 |
} |
| 197 |
- |
|
| 198 |
-// getCredentialSpec is a helper function to get the value of a credential spec supplied |
|
| 199 |
-// on the CLI, stripping the prefix |
|
| 200 |
-func getCredentialSpec(prefix, value string) (bool, string) {
|
|
| 201 |
- if strings.HasPrefix(value, prefix) {
|
|
| 202 |
- return true, strings.TrimPrefix(value, prefix) |
|
| 203 |
- } |
|
| 204 |
- return false, "" |
|
| 205 |
-} |
|
| 206 |
- |
|
| 207 |
-// readCredentialSpecRegistry is a helper function to read a credential spec from |
|
| 208 |
-// the registry. If not found, we return an empty string and warn in the log. |
|
| 209 |
-// This allows for staging on machines which do not have the necessary components. |
|
| 210 |
-func readCredentialSpecRegistry(id, name string) (string, error) {
|
|
| 211 |
- var ( |
|
| 212 |
- k registry.Key |
|
| 213 |
- err error |
|
| 214 |
- val string |
|
| 215 |
- ) |
|
| 216 |
- if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
|
|
| 217 |
- return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
|
|
| 218 |
- } |
|
| 219 |
- if val, _, err = k.GetStringValue(name); err != nil {
|
|
| 220 |
- if err == registry.ErrNotExist {
|
|
| 221 |
- return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
|
|
| 222 |
- } |
|
| 223 |
- return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
|
|
| 224 |
- } |
|
| 225 |
- return val, nil |
|
| 226 |
-} |
|
| 227 |
- |
|
| 228 |
-// readCredentialSpecFile is a helper function to read a credential spec from |
|
| 229 |
-// a file. If not found, we return an empty string and warn in the log. |
|
| 230 |
-// This allows for staging on machines which do not have the necessary components. |
|
| 231 |
-func readCredentialSpecFile(id, root, location string) (string, error) {
|
|
| 232 |
- if filepath.IsAbs(location) {
|
|
| 233 |
- return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
|
|
| 234 |
- } |
|
| 235 |
- base := filepath.Join(root, credentialSpecFileLocation) |
|
| 236 |
- full := filepath.Join(base, location) |
|
| 237 |
- if !strings.HasPrefix(full, base) {
|
|
| 238 |
- return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
|
|
| 239 |
- } |
|
| 240 |
- bcontents, err := ioutil.ReadFile(full) |
|
| 241 |
- if err != nil {
|
|
| 242 |
- return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
|
|
| 243 |
- } |
|
| 244 |
- return string(bcontents[:]), nil |
|
| 245 |
-} |
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
"io/ioutil" |
| 9 | 9 |
"os" |
| 10 | 10 |
"path/filepath" |
| 11 |
+ "regexp" |
|
| 11 | 12 |
"strings" |
| 12 | 13 |
"syscall" |
| 13 | 14 |
"time" |
| ... | ... |
@@ -102,8 +103,11 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir |
| 102 | 102 |
if b, err := json.Marshal(spec); err == nil {
|
| 103 | 103 |
logrus.Debugln("libcontainerd: client.Create() with spec", string(b))
|
| 104 | 104 |
} |
| 105 |
- osName := spec.Platform.OS |
|
| 106 |
- if osName == "windows" {
|
|
| 105 |
+ |
|
| 106 |
+ // spec.Linux must be nil for Windows containers, but spec.Windows will be filled in regardless of container platform. |
|
| 107 |
+ // This is a temporary workaround due to LCOW requiring layer folder paths, which are stored under spec.Windows. |
|
| 108 |
+ // TODO: @darrenstahlmsft fix this once the OCI spec is updated to support layer folder paths for LCOW |
|
| 109 |
+ if spec.Linux == nil {
|
|
| 107 | 110 |
return clnt.createWindows(containerID, checkpoint, checkpointDir, spec, attachStdio, options...) |
| 108 | 111 |
} |
| 109 | 112 |
return clnt.createLinux(containerID, checkpoint, checkpointDir, spec, attachStdio, options...) |
| ... | ... |
@@ -114,9 +118,10 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo |
| 114 | 114 |
SystemType: "Container", |
| 115 | 115 |
Name: containerID, |
| 116 | 116 |
Owner: defaultOwner, |
| 117 |
- IgnoreFlushesDuringBoot: false, |
|
| 117 |
+ IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot, |
|
| 118 | 118 |
HostName: spec.Hostname, |
| 119 | 119 |
HvPartition: false, |
| 120 |
+ Servicing: spec.Windows.Servicing, |
|
| 120 | 121 |
} |
| 121 | 122 |
|
| 122 | 123 |
if spec.Windows.Resources != nil {
|
| ... | ... |
@@ -155,49 +160,43 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo |
| 155 | 155 |
} |
| 156 | 156 |
} |
| 157 | 157 |
|
| 158 |
- var layerOpt *LayerOption |
|
| 159 |
- for _, option := range options {
|
|
| 160 |
- if s, ok := option.(*ServicingOption); ok {
|
|
| 161 |
- configuration.Servicing = s.IsServicing |
|
| 162 |
- continue |
|
| 163 |
- } |
|
| 164 |
- if f, ok := option.(*FlushOption); ok {
|
|
| 165 |
- configuration.IgnoreFlushesDuringBoot = f.IgnoreFlushesDuringBoot |
|
| 166 |
- continue |
|
| 167 |
- } |
|
| 168 |
- if h, ok := option.(*HyperVIsolationOption); ok {
|
|
| 169 |
- configuration.HvPartition = h.IsHyperV |
|
| 170 |
- continue |
|
| 171 |
- } |
|
| 172 |
- if l, ok := option.(*LayerOption); ok {
|
|
| 173 |
- layerOpt = l |
|
| 174 |
- } |
|
| 175 |
- if n, ok := option.(*NetworkEndpointsOption); ok {
|
|
| 176 |
- configuration.EndpointList = n.Endpoints |
|
| 177 |
- configuration.AllowUnqualifiedDNSQuery = n.AllowUnqualifiedDNSQuery |
|
| 178 |
- if n.DNSSearchList != nil {
|
|
| 179 |
- configuration.DNSSearchList = strings.Join(n.DNSSearchList, ",") |
|
| 180 |
- } |
|
| 181 |
- configuration.NetworkSharedContainerName = n.NetworkSharedContainerID |
|
| 182 |
- continue |
|
| 183 |
- } |
|
| 184 |
- if c, ok := option.(*CredentialsOption); ok {
|
|
| 185 |
- configuration.Credentials = c.Credentials |
|
| 186 |
- continue |
|
| 158 |
+ if spec.Windows.HyperV != nil {
|
|
| 159 |
+ configuration.HvPartition = true |
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ if spec.Windows.Network != nil {
|
|
| 163 |
+ configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 164 |
+ configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 165 |
+ if spec.Windows.Network.DNSSearchList != nil {
|
|
| 166 |
+ configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 187 | 167 |
} |
| 168 |
+ configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ if cs, ok := spec.Windows.CredentialSpec.(string); ok {
|
|
| 172 |
+ configuration.Credentials = cs |
|
| 188 | 173 |
} |
| 189 | 174 |
|
| 190 |
- // We must have a layer option with at least one path |
|
| 191 |
- if layerOpt == nil || layerOpt.LayerPaths == nil {
|
|
| 192 |
- return fmt.Errorf("no layer option or paths were supplied to the runtime")
|
|
| 175 |
+ // We must have least two layers in the spec, the bottom one being a base image, |
|
| 176 |
+ // the top one being the RW layer. |
|
| 177 |
+ if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
|
|
| 178 |
+ return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
|
|
| 193 | 179 |
} |
| 194 | 180 |
|
| 181 |
+ // Strip off the top-most layer as that's passed in separately to HCS |
|
| 182 |
+ configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 183 |
+ layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 184 |
+ |
|
| 195 | 185 |
if configuration.HvPartition {
|
| 196 |
- // Find the upper-most utility VM image, since the utility VM does not |
|
| 197 |
- // use layering in RS1. |
|
| 198 |
- // TODO @swernli/jhowardmsft at some point post RS1 this may be re-locatable. |
|
| 186 |
+ // We don't currently support setting the utility VM image explicitly. |
|
| 187 |
+ // TODO @swernli/jhowardmsft circa RS3/4, this may be re-locatable. |
|
| 188 |
+ if spec.Windows.HyperV.UtilityVMPath != "" {
|
|
| 189 |
+ return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
|
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ // Find the upper-most utility VM image. |
|
| 199 | 193 |
var uvmImagePath string |
| 200 |
- for _, path := range layerOpt.LayerPaths {
|
|
| 194 |
+ for _, path := range layerFolders {
|
|
| 201 | 195 |
fullPath := filepath.Join(path, "UtilityVM") |
| 202 | 196 |
_, err := os.Stat(fullPath) |
| 203 | 197 |
if err == nil {
|
| ... | ... |
@@ -212,13 +211,24 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo |
| 212 | 212 |
return errors.New("utility VM image could not be found")
|
| 213 | 213 |
} |
| 214 | 214 |
configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
|
| 215 |
+ |
|
| 216 |
+ if spec.Root.Path != "" {
|
|
| 217 |
+ return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
|
|
| 218 |
+ } |
|
| 215 | 219 |
} else {
|
| 216 |
- configuration.VolumePath = spec.Root.Path |
|
| 220 |
+ const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}\\$`
|
|
| 221 |
+ if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
|
|
| 222 |
+ return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
|
|
| 223 |
+ } |
|
| 224 |
+ // HCS API requires the trailing backslash to be removed |
|
| 225 |
+ configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1] |
|
| 217 | 226 |
} |
| 218 | 227 |
|
| 219 |
- configuration.LayerFolderPath = layerOpt.LayerFolderPath |
|
| 228 |
+ if spec.Root.Readonly {
|
|
| 229 |
+ return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`) |
|
| 230 |
+ } |
|
| 220 | 231 |
|
| 221 |
- for _, layerPath := range layerOpt.LayerPaths {
|
|
| 232 |
+ for _, layerPath := range layerFolders {
|
|
| 222 | 233 |
_, filename := filepath.Split(layerPath) |
| 223 | 234 |
g, err := hcsshim.NameToGuid(filename) |
| 224 | 235 |
if err != nil {
|
| ... | ... |
@@ -235,6 +245,9 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo |
| 235 | 235 |
var mps []hcsshim.MappedPipe |
| 236 | 236 |
for _, mount := range spec.Mounts {
|
| 237 | 237 |
const pipePrefix = `\\.\pipe\` |
| 238 |
+ if mount.Type != "" {
|
|
| 239 |
+ return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
|
|
| 240 |
+ } |
|
| 238 | 241 |
if strings.HasPrefix(mount.Destination, pipePrefix) {
|
| 239 | 242 |
mp := hcsshim.MappedPipe{
|
| 240 | 243 |
HostPath: mount.Source, |
| ... | ... |
@@ -278,6 +291,7 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo |
| 278 | 278 |
}, |
| 279 | 279 |
processes: make(map[string]*process), |
| 280 | 280 |
}, |
| 281 |
+ isWindows: true, |
|
| 281 | 282 |
ociSpec: spec, |
| 282 | 283 |
hcsContainer: hcsContainer, |
| 283 | 284 |
} |
| ... | ... |
@@ -306,12 +320,8 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo |
| 306 | 306 |
func (clnt *client) createLinux(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
|
| 307 | 307 |
logrus.Debugf("libcontainerd: createLinux(): containerId %s ", containerID)
|
| 308 | 308 |
|
| 309 |
- var layerOpt *LayerOption |
|
| 310 | 309 |
var lcowOpt *LCOWOption |
| 311 | 310 |
for _, option := range options {
|
| 312 |
- if layer, ok := option.(*LayerOption); ok {
|
|
| 313 |
- layerOpt = layer |
|
| 314 |
- } |
|
| 315 | 311 |
if lcow, ok := option.(*LCOWOption); ok {
|
| 316 | 312 |
lcowOpt = lcow |
| 317 | 313 |
} |
| ... | ... |
@@ -342,14 +352,20 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin |
| 342 | 342 |
} |
| 343 | 343 |
} |
| 344 | 344 |
|
| 345 |
- // We must have a layer option with at least one path |
|
| 346 |
- if layerOpt == nil || layerOpt.LayerPaths == nil {
|
|
| 347 |
- return fmt.Errorf("no layer option or paths were supplied to the runtime")
|
|
| 345 |
+ if spec.Windows == nil {
|
|
| 346 |
+ return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
|
|
| 347 |
+ } |
|
| 348 |
+ |
|
| 349 |
+ // We must have least one layer in the spec |
|
| 350 |
+ if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
|
|
| 351 |
+ return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
|
|
| 348 | 352 |
} |
| 349 | 353 |
|
| 350 |
- // LayerFolderPath (writeable layer) + Layers (Guid + path) |
|
| 351 |
- configuration.LayerFolderPath = layerOpt.LayerFolderPath |
|
| 352 |
- for _, layerPath := range layerOpt.LayerPaths {
|
|
| 354 |
+ // Strip off the top-most layer as that's passed in separately to HCS |
|
| 355 |
+ configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 356 |
+ layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 357 |
+ |
|
| 358 |
+ for _, layerPath := range layerFolders {
|
|
| 353 | 359 |
_, filename := filepath.Split(layerPath) |
| 354 | 360 |
g, err := hcsshim.NameToGuid(filename) |
| 355 | 361 |
if err != nil {
|
| ... | ... |
@@ -361,16 +377,13 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin |
| 361 | 361 |
}) |
| 362 | 362 |
} |
| 363 | 363 |
|
| 364 |
- for _, option := range options {
|
|
| 365 |
- if n, ok := option.(*NetworkEndpointsOption); ok {
|
|
| 366 |
- configuration.EndpointList = n.Endpoints |
|
| 367 |
- configuration.AllowUnqualifiedDNSQuery = n.AllowUnqualifiedDNSQuery |
|
| 368 |
- if n.DNSSearchList != nil {
|
|
| 369 |
- configuration.DNSSearchList = strings.Join(n.DNSSearchList, ",") |
|
| 370 |
- } |
|
| 371 |
- configuration.NetworkSharedContainerName = n.NetworkSharedContainerID |
|
| 372 |
- break |
|
| 364 |
+ if spec.Windows.Network != nil {
|
|
| 365 |
+ configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 366 |
+ configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 367 |
+ if spec.Windows.Network.DNSSearchList != nil {
|
|
| 368 |
+ configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 373 | 369 |
} |
| 370 |
+ configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 374 | 371 |
} |
| 375 | 372 |
|
| 376 | 373 |
hcsContainer, err := hcsshim.CreateContainer(containerID, configuration) |
| ... | ... |
@@ -436,8 +449,10 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly |
| 436 | 436 |
} |
| 437 | 437 |
if procToAdd.Terminal {
|
| 438 | 438 |
createProcessParms.EmulateConsole = true |
| 439 |
- createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height) |
|
| 440 |
- createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width) |
|
| 439 |
+ if procToAdd.ConsoleSize != nil {
|
|
| 440 |
+ createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height) |
|
| 441 |
+ createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width) |
|
| 442 |
+ } |
|
| 441 | 443 |
} |
| 442 | 444 |
|
| 443 | 445 |
// Take working directory from the process to add if it is defined, |
| ... | ... |
@@ -450,7 +465,7 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly |
| 450 | 450 |
|
| 451 | 451 |
// Configure the environment for the process |
| 452 | 452 |
createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env) |
| 453 |
- if container.ociSpec.Platform.OS == "windows" {
|
|
| 453 |
+ if container.isWindows {
|
|
| 454 | 454 |
createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ") |
| 455 | 455 |
} else {
|
| 456 | 456 |
createProcessParms.CommandArgs = procToAdd.Args |
| ... | ... |
@@ -614,13 +629,8 @@ func (clnt *client) Pause(containerID string) error {
|
| 614 | 614 |
return err |
| 615 | 615 |
} |
| 616 | 616 |
|
| 617 |
- for _, option := range container.options {
|
|
| 618 |
- if h, ok := option.(*HyperVIsolationOption); ok {
|
|
| 619 |
- if !h.IsHyperV {
|
|
| 620 |
- return errors.New("cannot pause Windows Server Containers")
|
|
| 621 |
- } |
|
| 622 |
- break |
|
| 623 |
- } |
|
| 617 |
+ if container.ociSpec.Windows.HyperV == nil {
|
|
| 618 |
+ return errors.New("cannot pause Windows Server Containers")
|
|
| 624 | 619 |
} |
| 625 | 620 |
|
| 626 | 621 |
err = container.hcsContainer.Pause() |
| ... | ... |
@@ -654,13 +664,9 @@ func (clnt *client) Resume(containerID string) error {
|
| 654 | 654 |
} |
| 655 | 655 |
|
| 656 | 656 |
// This should never happen, since Windows Server Containers cannot be paused |
| 657 |
- for _, option := range container.options {
|
|
| 658 |
- if h, ok := option.(*HyperVIsolationOption); ok {
|
|
| 659 |
- if !h.IsHyperV {
|
|
| 660 |
- return errors.New("cannot resume Windows Server Containers")
|
|
| 661 |
- } |
|
| 662 |
- break |
|
| 663 |
- } |
|
| 657 |
+ |
|
| 658 |
+ if container.ociSpec.Windows.HyperV == nil {
|
|
| 659 |
+ return errors.New("cannot resume Windows Server Containers")
|
|
| 664 | 660 |
} |
| 665 | 661 |
|
| 666 | 662 |
err = container.hcsContainer.Resume() |
| ... | ... |
@@ -25,6 +25,7 @@ type container struct {
|
| 25 | 25 |
// otherwise have access to the Spec |
| 26 | 26 |
ociSpec specs.Spec |
| 27 | 27 |
|
| 28 |
+ isWindows bool |
|
| 28 | 29 |
manualStopRequested bool |
| 29 | 30 |
hcsContainer hcsshim.Container |
| 30 | 31 |
} |
| ... | ... |
@@ -43,13 +44,6 @@ func (ctr *container) newProcess(friendlyName string) *process {
|
| 43 | 43 |
// Caller needs to lock container ID before calling this method. |
| 44 | 44 |
func (ctr *container) start(attachStdio StdioCallback) error {
|
| 45 | 45 |
var err error |
| 46 |
- isServicing := false |
|
| 47 |
- |
|
| 48 |
- for _, option := range ctr.options {
|
|
| 49 |
- if s, ok := option.(*ServicingOption); ok && s.IsServicing {
|
|
| 50 |
- isServicing = true |
|
| 51 |
- } |
|
| 52 |
- } |
|
| 53 | 46 |
|
| 54 | 47 |
// Start the container. If this is a servicing container, this call will block |
| 55 | 48 |
// until the container is done with the servicing execution. |
| ... | ... |
@@ -69,27 +63,39 @@ func (ctr *container) start(attachStdio StdioCallback) error {
|
| 69 | 69 |
// docker can always grab the output through logs. We also tell HCS to always |
| 70 | 70 |
// create stdin, even if it's not used - it will be closed shortly. Stderr |
| 71 | 71 |
// is only created if it we're not -t. |
| 72 |
+ var ( |
|
| 73 |
+ emulateConsole bool |
|
| 74 |
+ createStdErrPipe bool |
|
| 75 |
+ ) |
|
| 76 |
+ if ctr.ociSpec.Process != nil {
|
|
| 77 |
+ emulateConsole = ctr.ociSpec.Process.Terminal |
|
| 78 |
+ createStdErrPipe = !ctr.ociSpec.Process.Terminal && !ctr.ociSpec.Windows.Servicing |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 72 | 81 |
createProcessParms := &hcsshim.ProcessConfig{
|
| 73 |
- EmulateConsole: ctr.ociSpec.Process.Terminal, |
|
| 82 |
+ EmulateConsole: emulateConsole, |
|
| 74 | 83 |
WorkingDirectory: ctr.ociSpec.Process.Cwd, |
| 75 |
- CreateStdInPipe: !isServicing, |
|
| 76 |
- CreateStdOutPipe: !isServicing, |
|
| 77 |
- CreateStdErrPipe: !ctr.ociSpec.Process.Terminal && !isServicing, |
|
| 84 |
+ CreateStdInPipe: !ctr.ociSpec.Windows.Servicing, |
|
| 85 |
+ CreateStdOutPipe: !ctr.ociSpec.Windows.Servicing, |
|
| 86 |
+ CreateStdErrPipe: createStdErrPipe, |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
|
|
| 90 |
+ createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height) |
|
| 91 |
+ createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width) |
|
| 78 | 92 |
} |
| 79 |
- createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height) |
|
| 80 |
- createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width) |
|
| 81 | 93 |
|
| 82 | 94 |
// Configure the environment for the process |
| 83 | 95 |
createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) |
| 84 |
- if ctr.ociSpec.Platform.OS == "windows" {
|
|
| 96 |
+ if ctr.isWindows {
|
|
| 85 | 97 |
createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ") |
| 86 | 98 |
} else {
|
| 87 | 99 |
createProcessParms.CommandArgs = ctr.ociSpec.Process.Args |
| 88 | 100 |
} |
| 89 | 101 |
createProcessParms.User = ctr.ociSpec.Process.User.Username |
| 90 | 102 |
|
| 91 |
- // Linux containers requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM. |
|
| 92 |
- if ctr.ociSpec.Platform.OS == "linux" {
|
|
| 103 |
+ // LCOW requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM. |
|
| 104 |
+ if !ctr.isWindows {
|
|
| 93 | 105 |
ociBuf, err := json.Marshal(ctr.ociSpec) |
| 94 | 106 |
if err != nil {
|
| 95 | 107 |
return err |
| ... | ... |
@@ -118,7 +124,7 @@ func (ctr *container) start(attachStdio StdioCallback) error {
|
| 118 | 118 |
|
| 119 | 119 |
// If this is a servicing container, wait on the process synchronously here and |
| 120 | 120 |
// if it succeeds, wait for it cleanly shutdown and merge into the parent container. |
| 121 |
- if isServicing {
|
|
| 121 |
+ if ctr.ociSpec.Windows.Servicing {
|
|
| 122 | 122 |
exitCode := ctr.waitProcessExitCode(&ctr.process) |
| 123 | 123 |
|
| 124 | 124 |
if exitCode != 0 {
|
| ... | ... |
@@ -244,7 +250,7 @@ func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) err |
| 244 | 244 |
si.State = StateExitProcess |
| 245 | 245 |
} else {
|
| 246 | 246 |
// Pending updates is only applicable for WCOW |
| 247 |
- if ctr.ociSpec.Platform.OS == "windows" {
|
|
| 247 |
+ if ctr.isWindows {
|
|
| 248 | 248 |
updatePending, err := ctr.hcsContainer.HasPendingUpdates() |
| 249 | 249 |
if err != nil {
|
| 250 | 250 |
logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
|
| ... | ... |
@@ -31,49 +31,6 @@ type LCOWOption struct {
|
| 31 | 31 |
Config *opengcs.Config |
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 |
-// ServicingOption is a CreateOption with a no-op application that signifies |
|
| 35 |
-// the container needs to be used for a Windows servicing operation. |
|
| 36 |
-type ServicingOption struct {
|
|
| 37 |
- IsServicing bool |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-// FlushOption is a CreateOption that signifies if the container should be |
|
| 41 |
-// started with flushes ignored until boot has completed. This is an optimisation |
|
| 42 |
-// for first boot of a container. |
|
| 43 |
-type FlushOption struct {
|
|
| 44 |
- IgnoreFlushesDuringBoot bool |
|
| 45 |
-} |
|
| 46 |
- |
|
| 47 |
-// HyperVIsolationOption is a CreateOption that indicates whether the runtime |
|
| 48 |
-// should start the container as a Hyper-V container. |
|
| 49 |
-type HyperVIsolationOption struct {
|
|
| 50 |
- IsHyperV bool |
|
| 51 |
-} |
|
| 52 |
- |
|
| 53 |
-// LayerOption is a CreateOption that indicates to the runtime the layer folder |
|
| 54 |
-// and layer paths for a container. |
|
| 55 |
-type LayerOption struct {
|
|
| 56 |
- // LayerFolderPath is the path to the current layer folder. Empty for Hyper-V containers. |
|
| 57 |
- LayerFolderPath string `json:",omitempty"` |
|
| 58 |
- // Layer paths of the parent layers |
|
| 59 |
- LayerPaths []string |
|
| 60 |
-} |
|
| 61 |
- |
|
| 62 |
-// NetworkEndpointsOption is a CreateOption that provides the runtime list |
|
| 63 |
-// of network endpoints to which a container should be attached during its creation. |
|
| 64 |
-type NetworkEndpointsOption struct {
|
|
| 65 |
- Endpoints []string |
|
| 66 |
- AllowUnqualifiedDNSQuery bool |
|
| 67 |
- DNSSearchList []string |
|
| 68 |
- NetworkSharedContainerID string |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-// CredentialsOption is a CreateOption that indicates the credentials from |
|
| 72 |
-// a credential spec to be used to the runtime |
|
| 73 |
-type CredentialsOption struct {
|
|
| 74 |
- Credentials string |
|
| 75 |
-} |
|
| 76 |
- |
|
| 77 | 34 |
// Checkpoint holds the details of a checkpoint (not supported in windows) |
| 78 | 35 |
type Checkpoint struct {
|
| 79 | 36 |
Name string |
| ... | ... |
@@ -15,36 +15,6 @@ func setupEnvironmentVariables(a []string) map[string]string {
|
| 15 | 15 |
return r |
| 16 | 16 |
} |
| 17 | 17 |
|
| 18 |
-// Apply for a servicing option is a no-op. |
|
| 19 |
-func (s *ServicingOption) Apply(interface{}) error {
|
|
| 20 |
- return nil |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-// Apply for the flush option is a no-op. |
|
| 24 |
-func (f *FlushOption) Apply(interface{}) error {
|
|
| 25 |
- return nil |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-// Apply for the hypervisolation option is a no-op. |
|
| 29 |
-func (h *HyperVIsolationOption) Apply(interface{}) error {
|
|
| 30 |
- return nil |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-// Apply for the layer option is a no-op. |
|
| 34 |
-func (h *LayerOption) Apply(interface{}) error {
|
|
| 35 |
- return nil |
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-// Apply for the network endpoints option is a no-op. |
|
| 39 |
-func (s *NetworkEndpointsOption) Apply(interface{}) error {
|
|
| 40 |
- return nil |
|
| 41 |
-} |
|
| 42 |
- |
|
| 43 |
-// Apply for the credentials option is a no-op. |
|
| 44 |
-func (s *CredentialsOption) Apply(interface{}) error {
|
|
| 45 |
- return nil |
|
| 46 |
-} |
|
| 47 |
- |
|
| 48 | 18 |
// Apply for the LCOW option is a no-op. |
| 49 | 19 |
func (s *LCOWOption) Apply(interface{}) error {
|
| 50 | 20 |
return nil |
| ... | ... |
@@ -51,6 +51,8 @@ func DefaultWindowsSpec() specs.Spec {
|
| 51 | 51 |
return specs.Spec{
|
| 52 | 52 |
Version: specs.Version, |
| 53 | 53 |
Windows: &specs.Windows{},
|
| 54 |
+ Process: &specs.Process{},
|
|
| 55 |
+ Root: &specs.Root{},
|
|
| 54 | 56 |
} |
| 55 | 57 |
} |
| 56 | 58 |
|
| ... | ... |
@@ -68,6 +70,7 @@ func DefaultLinuxSpec() specs.Spec {
|
| 68 | 68 |
s := specs.Spec{
|
| 69 | 69 |
Version: specs.Version, |
| 70 | 70 |
Process: &specs.Process{},
|
| 71 |
+ Root: &specs.Root{},
|
|
| 71 | 72 |
} |
| 72 | 73 |
s.Mounts = []specs.Mount{
|
| 73 | 74 |
{
|
| ... | ... |
@@ -113,11 +116,13 @@ func DefaultLinuxSpec() specs.Spec {
|
| 113 | 113 |
Options: []string{"nosuid", "noexec", "nodev", "mode=1777"},
|
| 114 | 114 |
}, |
| 115 | 115 |
} |
| 116 |
- s.Process.Capabilities = &specs.LinuxCapabilities{
|
|
| 117 |
- Bounding: defaultCapabilities(), |
|
| 118 |
- Permitted: defaultCapabilities(), |
|
| 119 |
- Inheritable: defaultCapabilities(), |
|
| 120 |
- Effective: defaultCapabilities(), |
|
| 116 |
+ s.Process = &specs.Process{
|
|
| 117 |
+ Capabilities: &specs.LinuxCapabilities{
|
|
| 118 |
+ Bounding: defaultCapabilities(), |
|
| 119 |
+ Permitted: defaultCapabilities(), |
|
| 120 |
+ Inheritable: defaultCapabilities(), |
|
| 121 |
+ Effective: defaultCapabilities(), |
|
| 122 |
+ }, |
|
| 121 | 123 |
} |
| 122 | 124 |
|
| 123 | 125 |
s.Linux = &specs.Linux{
|
| ... | ... |
@@ -207,6 +212,11 @@ func DefaultLinuxSpec() specs.Spec {
|
| 207 | 207 |
}, |
| 208 | 208 |
} |
| 209 | 209 |
|
| 210 |
+ // For LCOW support, populate a blank Windows spec |
|
| 211 |
+ if runtime.GOOS == "windows" {
|
|
| 212 |
+ s.Windows = &specs.Windows{}
|
|
| 213 |
+ } |
|
| 214 |
+ |
|
| 210 | 215 |
// For LCOW support, don't mask /sys/firmware |
| 211 | 216 |
if runtime.GOOS != "windows" {
|
| 212 | 217 |
s.Linux.MaskedPaths = append(s.Linux.MaskedPaths, "/sys/firmware") |