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") |