Update Windows and LCOW to use v1.0.0 runtime-spec
Signed-off-by: Darren Stahl <darst@microsoft.com> Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
This commit is contained in:
parent
45d85c9913
commit
7c29103ad9
8 changed files with 332 additions and 391 deletions
|
@ -22,22 +22,17 @@ func (daemon *Daemon) postRunProcessing(container *container.Container, e libcon
|
|||
return err
|
||||
}
|
||||
|
||||
newOpts := []libcontainerd.CreateOption{&libcontainerd.ServicingOption{
|
||||
IsServicing: true,
|
||||
}}
|
||||
// Turn on servicing
|
||||
spec.Windows.Servicing = true
|
||||
|
||||
copts, err := daemon.getLibcontainerdCreateOptions(container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if copts != nil {
|
||||
newOpts = append(newOpts, copts...)
|
||||
}
|
||||
|
||||
// Create a new servicing container, which will start, complete the update, and merge back the
|
||||
// results if it succeeded, all as part of the below function call.
|
||||
if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, container.InitializeStdio, newOpts...); err != nil {
|
||||
if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, container.InitializeStdio, copts...); err != nil {
|
||||
container.SetExitCode(-1)
|
||||
return fmt.Errorf("Post-run update servicing failed: %s", err)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/oci"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
|
||||
credentialSpecFileLocation = "CredentialSpecs"
|
||||
)
|
||||
|
||||
func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
||||
|
@ -53,6 +65,10 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
|||
isHyperV = c.HostConfig.Isolation.IsHyperV()
|
||||
}
|
||||
|
||||
if isHyperV {
|
||||
s.Windows.HyperV = &specs.WindowsHyperV{}
|
||||
}
|
||||
|
||||
// If the container has not been started, and has configs or secrets
|
||||
// secrets, create symlinks to each config and secret. If it has been
|
||||
// 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) {
|
|||
s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
|
||||
if c.Config.Tty {
|
||||
s.Process.Terminal = c.Config.Tty
|
||||
s.Process.ConsoleSize.Height = c.HostConfig.ConsoleSize[0]
|
||||
s.Process.ConsoleSize.Width = c.HostConfig.ConsoleSize[1]
|
||||
s.Process.ConsoleSize = &specs.Box{
|
||||
Height: c.HostConfig.ConsoleSize[0],
|
||||
Width: c.HostConfig.ConsoleSize[1],
|
||||
}
|
||||
}
|
||||
s.Process.User.Username = c.Config.User
|
||||
|
||||
// Get the layer path for each layer.
|
||||
max := len(img.RootFS.DiffIDs)
|
||||
for i := 1; i <= max; i++ {
|
||||
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
|
||||
layerPath, err := layer.GetLayerPath(daemon.stores[c.Platform].layerStore, img.RootFS.ChainID())
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
// Reverse order, expecting parent most first
|
||||
s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
|
||||
}
|
||||
m, err := c.RWLayer.Metadata()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer metadata - %s", err)
|
||||
}
|
||||
s.Windows.LayerFolders = append(s.Windows.LayerFolders, m["dir"])
|
||||
|
||||
dnsSearch := daemon.getDNSSearchSettings(c)
|
||||
|
||||
// Get endpoints for the libnetwork allocated networks to the container
|
||||
var epList []string
|
||||
AllowUnqualifiedDNSQuery := false
|
||||
gwHNSID := ""
|
||||
if c.NetworkSettings != nil {
|
||||
for n := range c.NetworkSettings.Networks {
|
||||
sn, err := daemon.FindNetwork(n)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ep, err := c.GetEndpointInNetwork(sn)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := ep.DriverInfo()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if data["GW_INFO"] != nil {
|
||||
gwInfo := data["GW_INFO"].(map[string]interface{})
|
||||
if gwInfo["hnsid"] != nil {
|
||||
gwHNSID = gwInfo["hnsid"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if data["hnsid"] != nil {
|
||||
epList = append(epList, data["hnsid"].(string))
|
||||
}
|
||||
|
||||
if data["AllowUnqualifiedDNSQuery"] != nil {
|
||||
AllowUnqualifiedDNSQuery = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var networkSharedContainerID string
|
||||
if c.HostConfig.NetworkMode.IsContainer() {
|
||||
networkSharedContainerID = c.NetworkSharedContainerID
|
||||
for _, ep := range c.SharedEndpointList {
|
||||
epList = append(epList, ep)
|
||||
}
|
||||
}
|
||||
|
||||
if gwHNSID != "" {
|
||||
epList = append(epList, gwHNSID)
|
||||
}
|
||||
|
||||
s.Windows.Network = &specs.WindowsNetwork{
|
||||
AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery,
|
||||
DNSSearchList: dnsSearch,
|
||||
EndpointList: epList,
|
||||
NetworkSharedContainerName: networkSharedContainerID,
|
||||
}
|
||||
|
||||
if img.OS == "windows" {
|
||||
daemon.createSpecWindowsFields(c, &s, isHyperV)
|
||||
if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode
|
||||
if system.LCOWSupported() && img.OS == "linux" {
|
||||
|
@ -123,7 +219,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
|||
}
|
||||
|
||||
// Sets the Windows-specific fields of the OCI spec
|
||||
func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) {
|
||||
func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
|
||||
if len(s.Process.Cwd) == 0 {
|
||||
// We default to C:\ to workaround the oddity of the case that the
|
||||
// default directory for cmd running as LocalSystem (or
|
||||
|
@ -138,8 +234,14 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
|
|||
s.Root.Readonly = false // Windows does not support a read-only root filesystem
|
||||
if !isHyperV {
|
||||
s.Root.Path = c.BaseFS // This is not set for Hyper-V containers
|
||||
if !strings.HasSuffix(s.Root.Path, `\`) {
|
||||
s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
|
||||
}
|
||||
}
|
||||
|
||||
// First boot optimization
|
||||
s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore
|
||||
|
||||
// In s.Windows.Resources
|
||||
cpuShares := uint16(c.HostConfig.CPUShares)
|
||||
cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100
|
||||
|
@ -179,6 +281,54 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
|
|||
Iops: &c.HostConfig.IOMaximumIOps,
|
||||
},
|
||||
}
|
||||
|
||||
// Read and add credentials from the security options if a credential spec has been provided.
|
||||
if c.HostConfig.SecurityOpt != nil {
|
||||
cs := ""
|
||||
for _, sOpt := range c.HostConfig.SecurityOpt {
|
||||
sOpt = strings.ToLower(sOpt)
|
||||
if !strings.Contains(sOpt, "=") {
|
||||
return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
|
||||
}
|
||||
var splitsOpt []string
|
||||
splitsOpt = strings.SplitN(sOpt, "=", 2)
|
||||
if len(splitsOpt) != 2 {
|
||||
return fmt.Errorf("invalid security option: %s", sOpt)
|
||||
}
|
||||
if splitsOpt[0] != "credentialspec" {
|
||||
return fmt.Errorf("security option not supported: %s", splitsOpt[0])
|
||||
}
|
||||
|
||||
var (
|
||||
match bool
|
||||
csValue string
|
||||
err error
|
||||
)
|
||||
if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
|
||||
if csValue == "" {
|
||||
return fmt.Errorf("no value supplied for file:// credential spec security option")
|
||||
}
|
||||
if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
|
||||
if csValue == "" {
|
||||
return fmt.Errorf("no value supplied for registry:// credential spec security option")
|
||||
}
|
||||
if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
|
||||
}
|
||||
}
|
||||
s.Windows.CredentialSpec = cs
|
||||
}
|
||||
|
||||
// Assume we are not starting a container for a servicing operation
|
||||
s.Windows.Servicing = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets the Linux-specific fields of the OCI spec
|
||||
|
@ -205,3 +355,52 @@ func escapeArgs(args []string) []string {
|
|||
func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
// getCredentialSpec is a helper function to get the value of a credential spec supplied
|
||||
// on the CLI, stripping the prefix
|
||||
func getCredentialSpec(prefix, value string) (bool, string) {
|
||||
if strings.HasPrefix(value, prefix) {
|
||||
return true, strings.TrimPrefix(value, prefix)
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// readCredentialSpecRegistry is a helper function to read a credential spec from
|
||||
// the registry. If not found, we return an empty string and warn in the log.
|
||||
// This allows for staging on machines which do not have the necessary components.
|
||||
func readCredentialSpecRegistry(id, name string) (string, error) {
|
||||
var (
|
||||
k registry.Key
|
||||
err error
|
||||
val string
|
||||
)
|
||||
if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
|
||||
return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
|
||||
}
|
||||
if val, _, err = k.GetStringValue(name); err != nil {
|
||||
if err == registry.ErrNotExist {
|
||||
return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
|
||||
}
|
||||
return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// readCredentialSpecFile is a helper function to read a credential spec from
|
||||
// a file. If not found, we return an empty string and warn in the log.
|
||||
// This allows for staging on machines which do not have the necessary components.
|
||||
func readCredentialSpecFile(id, root, location string) (string, error) {
|
||||
if filepath.IsAbs(location) {
|
||||
return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
|
||||
}
|
||||
base := filepath.Join(root, credentialSpecFileLocation)
|
||||
full := filepath.Join(base, location)
|
||||
if !strings.HasPrefix(full, base) {
|
||||
return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
|
||||
}
|
||||
bcontents, err := ioutil.ReadFile(full)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
|
||||
}
|
||||
return string(bcontents[:]), nil
|
||||
}
|
||||
|
|
|
@ -1,148 +1,14 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/opengcs/client"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/libcontainerd"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
|
||||
credentialSpecFileLocation = "CredentialSpecs"
|
||||
)
|
||||
|
||||
func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Container) ([]libcontainerd.CreateOption, error) {
|
||||
createOptions := []libcontainerd.CreateOption{}
|
||||
|
||||
// Are we going to run as a Hyper-V container?
|
||||
hvOpts := &libcontainerd.HyperVIsolationOption{}
|
||||
if container.HostConfig.Isolation.IsDefault() {
|
||||
// Container is set to use the default, so take the default from the daemon configuration
|
||||
hvOpts.IsHyperV = daemon.defaultIsolation.IsHyperV()
|
||||
} else {
|
||||
// Container is requesting an isolation mode. Honour it.
|
||||
hvOpts.IsHyperV = container.HostConfig.Isolation.IsHyperV()
|
||||
}
|
||||
|
||||
dnsSearch := daemon.getDNSSearchSettings(container)
|
||||
|
||||
// Generate the layer folder of the layer options
|
||||
layerOpts := &libcontainerd.LayerOption{}
|
||||
m, err := container.RWLayer.Metadata()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer metadata - %s", err)
|
||||
}
|
||||
layerOpts.LayerFolderPath = m["dir"]
|
||||
|
||||
// Generate the layer paths of the layer options
|
||||
img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to graph.Get on ImageID %s - %s", container.ImageID, err)
|
||||
}
|
||||
// Get the layer path for each layer.
|
||||
max := len(img.RootFS.DiffIDs)
|
||||
for i := 1; i <= max; i++ {
|
||||
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
|
||||
layerPath, err := layer.GetLayerPath(daemon.stores[container.Platform].layerStore, img.RootFS.ChainID())
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
// Reverse order, expecting parent most first
|
||||
layerOpts.LayerPaths = append([]string{layerPath}, layerOpts.LayerPaths...)
|
||||
}
|
||||
|
||||
// Get endpoints for the libnetwork allocated networks to the container
|
||||
var epList []string
|
||||
AllowUnqualifiedDNSQuery := false
|
||||
gwHNSID := ""
|
||||
if container.NetworkSettings != nil {
|
||||
for n := range container.NetworkSettings.Networks {
|
||||
sn, err := daemon.FindNetwork(n)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ep, err := container.GetEndpointInNetwork(sn)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := ep.DriverInfo()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if data["GW_INFO"] != nil {
|
||||
gwInfo := data["GW_INFO"].(map[string]interface{})
|
||||
if gwInfo["hnsid"] != nil {
|
||||
gwHNSID = gwInfo["hnsid"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if data["hnsid"] != nil {
|
||||
epList = append(epList, data["hnsid"].(string))
|
||||
}
|
||||
|
||||
if data["AllowUnqualifiedDNSQuery"] != nil {
|
||||
AllowUnqualifiedDNSQuery = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gwHNSID != "" {
|
||||
epList = append(epList, gwHNSID)
|
||||
}
|
||||
|
||||
// Read and add credentials from the security options if a credential spec has been provided.
|
||||
if container.HostConfig.SecurityOpt != nil {
|
||||
for _, sOpt := range container.HostConfig.SecurityOpt {
|
||||
sOpt = strings.ToLower(sOpt)
|
||||
if !strings.Contains(sOpt, "=") {
|
||||
return nil, fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
|
||||
}
|
||||
var splitsOpt []string
|
||||
splitsOpt = strings.SplitN(sOpt, "=", 2)
|
||||
if len(splitsOpt) != 2 {
|
||||
return nil, fmt.Errorf("invalid security option: %s", sOpt)
|
||||
}
|
||||
if splitsOpt[0] != "credentialspec" {
|
||||
return nil, fmt.Errorf("security option not supported: %s", splitsOpt[0])
|
||||
}
|
||||
|
||||
credentialsOpts := &libcontainerd.CredentialsOption{}
|
||||
var (
|
||||
match bool
|
||||
csValue string
|
||||
err error
|
||||
)
|
||||
if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
|
||||
if csValue == "" {
|
||||
return nil, fmt.Errorf("no value supplied for file:// credential spec security option")
|
||||
}
|
||||
if credentialsOpts.Credentials, err = readCredentialSpecFile(container.ID, daemon.root, filepath.Clean(csValue)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
|
||||
if csValue == "" {
|
||||
return nil, fmt.Errorf("no value supplied for registry:// credential spec security option")
|
||||
}
|
||||
if credentialsOpts.Credentials, err = readCredentialSpecRegistry(container.ID, csValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
|
||||
}
|
||||
createOptions = append(createOptions, credentialsOpts)
|
||||
}
|
||||
}
|
||||
|
||||
// LCOW options.
|
||||
if container.Platform == "linux" {
|
||||
config := &client.Config{}
|
||||
|
@ -173,73 +39,5 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
|
|||
createOptions = append(createOptions, lcowOpts)
|
||||
}
|
||||
|
||||
// Now add the remaining options.
|
||||
createOptions = append(createOptions, &libcontainerd.FlushOption{IgnoreFlushesDuringBoot: !container.HasBeenStartedBefore})
|
||||
createOptions = append(createOptions, hvOpts)
|
||||
createOptions = append(createOptions, layerOpts)
|
||||
|
||||
var networkSharedContainerID string
|
||||
if container.HostConfig.NetworkMode.IsContainer() {
|
||||
networkSharedContainerID = container.NetworkSharedContainerID
|
||||
for _, ep := range container.SharedEndpointList {
|
||||
epList = append(epList, ep)
|
||||
}
|
||||
}
|
||||
|
||||
createOptions = append(createOptions, &libcontainerd.NetworkEndpointsOption{
|
||||
Endpoints: epList,
|
||||
AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery,
|
||||
DNSSearchList: dnsSearch,
|
||||
NetworkSharedContainerID: networkSharedContainerID,
|
||||
})
|
||||
return createOptions, nil
|
||||
}
|
||||
|
||||
// getCredentialSpec is a helper function to get the value of a credential spec supplied
|
||||
// on the CLI, stripping the prefix
|
||||
func getCredentialSpec(prefix, value string) (bool, string) {
|
||||
if strings.HasPrefix(value, prefix) {
|
||||
return true, strings.TrimPrefix(value, prefix)
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// readCredentialSpecRegistry is a helper function to read a credential spec from
|
||||
// the registry. If not found, we return an empty string and warn in the log.
|
||||
// This allows for staging on machines which do not have the necessary components.
|
||||
func readCredentialSpecRegistry(id, name string) (string, error) {
|
||||
var (
|
||||
k registry.Key
|
||||
err error
|
||||
val string
|
||||
)
|
||||
if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
|
||||
return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
|
||||
}
|
||||
if val, _, err = k.GetStringValue(name); err != nil {
|
||||
if err == registry.ErrNotExist {
|
||||
return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
|
||||
}
|
||||
return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// readCredentialSpecFile is a helper function to read a credential spec from
|
||||
// a file. If not found, we return an empty string and warn in the log.
|
||||
// This allows for staging on machines which do not have the necessary components.
|
||||
func readCredentialSpecFile(id, root, location string) (string, error) {
|
||||
if filepath.IsAbs(location) {
|
||||
return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
|
||||
}
|
||||
base := filepath.Join(root, credentialSpecFileLocation)
|
||||
full := filepath.Join(base, location)
|
||||
if !strings.HasPrefix(full, base) {
|
||||
return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
|
||||
}
|
||||
bcontents, err := ioutil.ReadFile(full)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
|
||||
}
|
||||
return string(bcontents[:]), nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -102,8 +103,11 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
|
|||
if b, err := json.Marshal(spec); err == nil {
|
||||
logrus.Debugln("libcontainerd: client.Create() with spec", string(b))
|
||||
}
|
||||
osName := spec.Platform.OS
|
||||
if osName == "windows" {
|
||||
|
||||
// spec.Linux must be nil for Windows containers, but spec.Windows will be filled in regardless of container platform.
|
||||
// This is a temporary workaround due to LCOW requiring layer folder paths, which are stored under spec.Windows.
|
||||
// TODO: @darrenstahlmsft fix this once the OCI spec is updated to support layer folder paths for LCOW
|
||||
if spec.Linux == nil {
|
||||
return clnt.createWindows(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
|
||||
}
|
||||
return clnt.createLinux(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
|
||||
|
@ -114,9 +118,10 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
|
|||
SystemType: "Container",
|
||||
Name: containerID,
|
||||
Owner: defaultOwner,
|
||||
IgnoreFlushesDuringBoot: false,
|
||||
IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot,
|
||||
HostName: spec.Hostname,
|
||||
HvPartition: false,
|
||||
Servicing: spec.Windows.Servicing,
|
||||
}
|
||||
|
||||
if spec.Windows.Resources != nil {
|
||||
|
@ -155,49 +160,43 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
|
|||
}
|
||||
}
|
||||
|
||||
var layerOpt *LayerOption
|
||||
for _, option := range options {
|
||||
if s, ok := option.(*ServicingOption); ok {
|
||||
configuration.Servicing = s.IsServicing
|
||||
continue
|
||||
}
|
||||
if f, ok := option.(*FlushOption); ok {
|
||||
configuration.IgnoreFlushesDuringBoot = f.IgnoreFlushesDuringBoot
|
||||
continue
|
||||
}
|
||||
if h, ok := option.(*HyperVIsolationOption); ok {
|
||||
configuration.HvPartition = h.IsHyperV
|
||||
continue
|
||||
}
|
||||
if l, ok := option.(*LayerOption); ok {
|
||||
layerOpt = l
|
||||
}
|
||||
if n, ok := option.(*NetworkEndpointsOption); ok {
|
||||
configuration.EndpointList = n.Endpoints
|
||||
configuration.AllowUnqualifiedDNSQuery = n.AllowUnqualifiedDNSQuery
|
||||
if n.DNSSearchList != nil {
|
||||
configuration.DNSSearchList = strings.Join(n.DNSSearchList, ",")
|
||||
}
|
||||
configuration.NetworkSharedContainerName = n.NetworkSharedContainerID
|
||||
continue
|
||||
}
|
||||
if c, ok := option.(*CredentialsOption); ok {
|
||||
configuration.Credentials = c.Credentials
|
||||
continue
|
||||
}
|
||||
if spec.Windows.HyperV != nil {
|
||||
configuration.HvPartition = true
|
||||
}
|
||||
|
||||
// We must have a layer option with at least one path
|
||||
if layerOpt == nil || layerOpt.LayerPaths == nil {
|
||||
return fmt.Errorf("no layer option or paths were supplied to the runtime")
|
||||
if spec.Windows.Network != nil {
|
||||
configuration.EndpointList = spec.Windows.Network.EndpointList
|
||||
configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
|
||||
if spec.Windows.Network.DNSSearchList != nil {
|
||||
configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
|
||||
}
|
||||
configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
|
||||
}
|
||||
|
||||
if cs, ok := spec.Windows.CredentialSpec.(string); ok {
|
||||
configuration.Credentials = cs
|
||||
}
|
||||
|
||||
// We must have least two layers in the spec, the bottom one being a base image,
|
||||
// the top one being the RW layer.
|
||||
if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
|
||||
return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
|
||||
}
|
||||
|
||||
// Strip off the top-most layer as that's passed in separately to HCS
|
||||
configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
|
||||
layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
|
||||
|
||||
if configuration.HvPartition {
|
||||
// Find the upper-most utility VM image, since the utility VM does not
|
||||
// use layering in RS1.
|
||||
// TODO @swernli/jhowardmsft at some point post RS1 this may be re-locatable.
|
||||
// We don't currently support setting the utility VM image explicitly.
|
||||
// TODO @swernli/jhowardmsft circa RS3/4, this may be re-locatable.
|
||||
if spec.Windows.HyperV.UtilityVMPath != "" {
|
||||
return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
|
||||
}
|
||||
|
||||
// Find the upper-most utility VM image.
|
||||
var uvmImagePath string
|
||||
for _, path := range layerOpt.LayerPaths {
|
||||
for _, path := range layerFolders {
|
||||
fullPath := filepath.Join(path, "UtilityVM")
|
||||
_, err := os.Stat(fullPath)
|
||||
if err == nil {
|
||||
|
@ -212,13 +211,24 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
|
|||
return errors.New("utility VM image could not be found")
|
||||
}
|
||||
configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
|
||||
|
||||
if spec.Root.Path != "" {
|
||||
return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
|
||||
}
|
||||
} else {
|
||||
configuration.VolumePath = spec.Root.Path
|
||||
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}\}\\$`
|
||||
if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
|
||||
return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
|
||||
}
|
||||
// HCS API requires the trailing backslash to be removed
|
||||
configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1]
|
||||
}
|
||||
|
||||
configuration.LayerFolderPath = layerOpt.LayerFolderPath
|
||||
if spec.Root.Readonly {
|
||||
return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`)
|
||||
}
|
||||
|
||||
for _, layerPath := range layerOpt.LayerPaths {
|
||||
for _, layerPath := range layerFolders {
|
||||
_, filename := filepath.Split(layerPath)
|
||||
g, err := hcsshim.NameToGuid(filename)
|
||||
if err != nil {
|
||||
|
@ -235,6 +245,9 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
|
|||
var mps []hcsshim.MappedPipe
|
||||
for _, mount := range spec.Mounts {
|
||||
const pipePrefix = `\\.\pipe\`
|
||||
if mount.Type != "" {
|
||||
return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
|
||||
}
|
||||
if strings.HasPrefix(mount.Destination, pipePrefix) {
|
||||
mp := hcsshim.MappedPipe{
|
||||
HostPath: mount.Source,
|
||||
|
@ -278,6 +291,7 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
|
|||
},
|
||||
processes: make(map[string]*process),
|
||||
},
|
||||
isWindows: true,
|
||||
ociSpec: spec,
|
||||
hcsContainer: hcsContainer,
|
||||
}
|
||||
|
@ -306,12 +320,8 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
|
|||
func (clnt *client) createLinux(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
|
||||
logrus.Debugf("libcontainerd: createLinux(): containerId %s ", containerID)
|
||||
|
||||
var layerOpt *LayerOption
|
||||
var lcowOpt *LCOWOption
|
||||
for _, option := range options {
|
||||
if layer, ok := option.(*LayerOption); ok {
|
||||
layerOpt = layer
|
||||
}
|
||||
if lcow, ok := option.(*LCOWOption); ok {
|
||||
lcowOpt = lcow
|
||||
}
|
||||
|
@ -342,14 +352,20 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin
|
|||
}
|
||||
}
|
||||
|
||||
// We must have a layer option with at least one path
|
||||
if layerOpt == nil || layerOpt.LayerPaths == nil {
|
||||
return fmt.Errorf("no layer option or paths were supplied to the runtime")
|
||||
if spec.Windows == nil {
|
||||
return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
|
||||
}
|
||||
|
||||
// LayerFolderPath (writeable layer) + Layers (Guid + path)
|
||||
configuration.LayerFolderPath = layerOpt.LayerFolderPath
|
||||
for _, layerPath := range layerOpt.LayerPaths {
|
||||
// We must have least one layer in the spec
|
||||
if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
|
||||
return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
|
||||
}
|
||||
|
||||
// Strip off the top-most layer as that's passed in separately to HCS
|
||||
configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
|
||||
layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
|
||||
|
||||
for _, layerPath := range layerFolders {
|
||||
_, filename := filepath.Split(layerPath)
|
||||
g, err := hcsshim.NameToGuid(filename)
|
||||
if err != nil {
|
||||
|
@ -361,16 +377,13 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin
|
|||
})
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
if n, ok := option.(*NetworkEndpointsOption); ok {
|
||||
configuration.EndpointList = n.Endpoints
|
||||
configuration.AllowUnqualifiedDNSQuery = n.AllowUnqualifiedDNSQuery
|
||||
if n.DNSSearchList != nil {
|
||||
configuration.DNSSearchList = strings.Join(n.DNSSearchList, ",")
|
||||
}
|
||||
configuration.NetworkSharedContainerName = n.NetworkSharedContainerID
|
||||
break
|
||||
if spec.Windows.Network != nil {
|
||||
configuration.EndpointList = spec.Windows.Network.EndpointList
|
||||
configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
|
||||
if spec.Windows.Network.DNSSearchList != nil {
|
||||
configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
|
||||
}
|
||||
configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
|
||||
}
|
||||
|
||||
hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
|
||||
|
@ -436,8 +449,10 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
|
|||
}
|
||||
if procToAdd.Terminal {
|
||||
createProcessParms.EmulateConsole = true
|
||||
createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
|
||||
createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
|
||||
if procToAdd.ConsoleSize != nil {
|
||||
createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
|
||||
createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
|||
|
||||
// Configure the environment for the process
|
||||
createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
|
||||
if container.ociSpec.Platform.OS == "windows" {
|
||||
if container.isWindows {
|
||||
createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
|
||||
} else {
|
||||
createProcessParms.CommandArgs = procToAdd.Args
|
||||
|
@ -614,13 +629,8 @@ func (clnt *client) Pause(containerID string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, option := range container.options {
|
||||
if h, ok := option.(*HyperVIsolationOption); ok {
|
||||
if !h.IsHyperV {
|
||||
return errors.New("cannot pause Windows Server Containers")
|
||||
}
|
||||
break
|
||||
}
|
||||
if container.ociSpec.Windows.HyperV == nil {
|
||||
return errors.New("cannot pause Windows Server Containers")
|
||||
}
|
||||
|
||||
err = container.hcsContainer.Pause()
|
||||
|
@ -654,13 +664,9 @@ func (clnt *client) Resume(containerID string) error {
|
|||
}
|
||||
|
||||
// This should never happen, since Windows Server Containers cannot be paused
|
||||
for _, option := range container.options {
|
||||
if h, ok := option.(*HyperVIsolationOption); ok {
|
||||
if !h.IsHyperV {
|
||||
return errors.New("cannot resume Windows Server Containers")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if container.ociSpec.Windows.HyperV == nil {
|
||||
return errors.New("cannot resume Windows Server Containers")
|
||||
}
|
||||
|
||||
err = container.hcsContainer.Resume()
|
||||
|
|
|
@ -25,6 +25,7 @@ type container struct {
|
|||
// otherwise have access to the Spec
|
||||
ociSpec specs.Spec
|
||||
|
||||
isWindows bool
|
||||
manualStopRequested bool
|
||||
hcsContainer hcsshim.Container
|
||||
}
|
||||
|
@ -43,13 +44,6 @@ func (ctr *container) newProcess(friendlyName string) *process {
|
|||
// Caller needs to lock container ID before calling this method.
|
||||
func (ctr *container) start(attachStdio StdioCallback) error {
|
||||
var err error
|
||||
isServicing := false
|
||||
|
||||
for _, option := range ctr.options {
|
||||
if s, ok := option.(*ServicingOption); ok && s.IsServicing {
|
||||
isServicing = true
|
||||
}
|
||||
}
|
||||
|
||||
// Start the container. If this is a servicing container, this call will block
|
||||
// until the container is done with the servicing execution.
|
||||
|
@ -69,27 +63,39 @@ func (ctr *container) start(attachStdio StdioCallback) error {
|
|||
// docker can always grab the output through logs. We also tell HCS to always
|
||||
// create stdin, even if it's not used - it will be closed shortly. Stderr
|
||||
// is only created if it we're not -t.
|
||||
createProcessParms := &hcsshim.ProcessConfig{
|
||||
EmulateConsole: ctr.ociSpec.Process.Terminal,
|
||||
WorkingDirectory: ctr.ociSpec.Process.Cwd,
|
||||
CreateStdInPipe: !isServicing,
|
||||
CreateStdOutPipe: !isServicing,
|
||||
CreateStdErrPipe: !ctr.ociSpec.Process.Terminal && !isServicing,
|
||||
var (
|
||||
emulateConsole bool
|
||||
createStdErrPipe bool
|
||||
)
|
||||
if ctr.ociSpec.Process != nil {
|
||||
emulateConsole = ctr.ociSpec.Process.Terminal
|
||||
createStdErrPipe = !ctr.ociSpec.Process.Terminal && !ctr.ociSpec.Windows.Servicing
|
||||
}
|
||||
|
||||
createProcessParms := &hcsshim.ProcessConfig{
|
||||
EmulateConsole: emulateConsole,
|
||||
WorkingDirectory: ctr.ociSpec.Process.Cwd,
|
||||
CreateStdInPipe: !ctr.ociSpec.Windows.Servicing,
|
||||
CreateStdOutPipe: !ctr.ociSpec.Windows.Servicing,
|
||||
CreateStdErrPipe: createStdErrPipe,
|
||||
}
|
||||
|
||||
if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
|
||||
createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
|
||||
createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
|
||||
}
|
||||
createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
|
||||
createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
|
||||
|
||||
// Configure the environment for the process
|
||||
createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
|
||||
if ctr.ociSpec.Platform.OS == "windows" {
|
||||
if ctr.isWindows {
|
||||
createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
|
||||
} else {
|
||||
createProcessParms.CommandArgs = ctr.ociSpec.Process.Args
|
||||
}
|
||||
createProcessParms.User = ctr.ociSpec.Process.User.Username
|
||||
|
||||
// Linux containers requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM.
|
||||
if ctr.ociSpec.Platform.OS == "linux" {
|
||||
// LCOW requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM.
|
||||
if !ctr.isWindows {
|
||||
ociBuf, err := json.Marshal(ctr.ociSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -118,7 +124,7 @@ func (ctr *container) start(attachStdio StdioCallback) error {
|
|||
|
||||
// If this is a servicing container, wait on the process synchronously here and
|
||||
// if it succeeds, wait for it cleanly shutdown and merge into the parent container.
|
||||
if isServicing {
|
||||
if ctr.ociSpec.Windows.Servicing {
|
||||
exitCode := ctr.waitProcessExitCode(&ctr.process)
|
||||
|
||||
if exitCode != 0 {
|
||||
|
@ -244,7 +250,7 @@ func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) err
|
|||
si.State = StateExitProcess
|
||||
} else {
|
||||
// Pending updates is only applicable for WCOW
|
||||
if ctr.ociSpec.Platform.OS == "windows" {
|
||||
if ctr.isWindows {
|
||||
updatePending, err := ctr.hcsContainer.HasPendingUpdates()
|
||||
if err != nil {
|
||||
logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
|
||||
|
|
|
@ -31,49 +31,6 @@ type LCOWOption struct {
|
|||
Config *opengcs.Config
|
||||
}
|
||||
|
||||
// ServicingOption is a CreateOption with a no-op application that signifies
|
||||
// the container needs to be used for a Windows servicing operation.
|
||||
type ServicingOption struct {
|
||||
IsServicing bool
|
||||
}
|
||||
|
||||
// FlushOption is a CreateOption that signifies if the container should be
|
||||
// started with flushes ignored until boot has completed. This is an optimisation
|
||||
// for first boot of a container.
|
||||
type FlushOption struct {
|
||||
IgnoreFlushesDuringBoot bool
|
||||
}
|
||||
|
||||
// HyperVIsolationOption is a CreateOption that indicates whether the runtime
|
||||
// should start the container as a Hyper-V container.
|
||||
type HyperVIsolationOption struct {
|
||||
IsHyperV bool
|
||||
}
|
||||
|
||||
// LayerOption is a CreateOption that indicates to the runtime the layer folder
|
||||
// and layer paths for a container.
|
||||
type LayerOption struct {
|
||||
// LayerFolderPath is the path to the current layer folder. Empty for Hyper-V containers.
|
||||
LayerFolderPath string `json:",omitempty"`
|
||||
// Layer paths of the parent layers
|
||||
LayerPaths []string
|
||||
}
|
||||
|
||||
// NetworkEndpointsOption is a CreateOption that provides the runtime list
|
||||
// of network endpoints to which a container should be attached during its creation.
|
||||
type NetworkEndpointsOption struct {
|
||||
Endpoints []string
|
||||
AllowUnqualifiedDNSQuery bool
|
||||
DNSSearchList []string
|
||||
NetworkSharedContainerID string
|
||||
}
|
||||
|
||||
// CredentialsOption is a CreateOption that indicates the credentials from
|
||||
// a credential spec to be used to the runtime
|
||||
type CredentialsOption struct {
|
||||
Credentials string
|
||||
}
|
||||
|
||||
// Checkpoint holds the details of a checkpoint (not supported in windows)
|
||||
type Checkpoint struct {
|
||||
Name string
|
||||
|
|
|
@ -15,36 +15,6 @@ func setupEnvironmentVariables(a []string) map[string]string {
|
|||
return r
|
||||
}
|
||||
|
||||
// Apply for a servicing option is a no-op.
|
||||
func (s *ServicingOption) Apply(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply for the flush option is a no-op.
|
||||
func (f *FlushOption) Apply(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply for the hypervisolation option is a no-op.
|
||||
func (h *HyperVIsolationOption) Apply(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply for the layer option is a no-op.
|
||||
func (h *LayerOption) Apply(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply for the network endpoints option is a no-op.
|
||||
func (s *NetworkEndpointsOption) Apply(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply for the credentials option is a no-op.
|
||||
func (s *CredentialsOption) Apply(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply for the LCOW option is a no-op.
|
||||
func (s *LCOWOption) Apply(interface{}) error {
|
||||
return nil
|
||||
|
|
|
@ -51,6 +51,8 @@ func DefaultWindowsSpec() specs.Spec {
|
|||
return specs.Spec{
|
||||
Version: specs.Version,
|
||||
Windows: &specs.Windows{},
|
||||
Process: &specs.Process{},
|
||||
Root: &specs.Root{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,6 +70,7 @@ func DefaultLinuxSpec() specs.Spec {
|
|||
s := specs.Spec{
|
||||
Version: specs.Version,
|
||||
Process: &specs.Process{},
|
||||
Root: &specs.Root{},
|
||||
}
|
||||
s.Mounts = []specs.Mount{
|
||||
{
|
||||
|
@ -113,11 +116,13 @@ func DefaultLinuxSpec() specs.Spec {
|
|||
Options: []string{"nosuid", "noexec", "nodev", "mode=1777"},
|
||||
},
|
||||
}
|
||||
s.Process.Capabilities = &specs.LinuxCapabilities{
|
||||
Bounding: defaultCapabilities(),
|
||||
Permitted: defaultCapabilities(),
|
||||
Inheritable: defaultCapabilities(),
|
||||
Effective: defaultCapabilities(),
|
||||
s.Process = &specs.Process{
|
||||
Capabilities: &specs.LinuxCapabilities{
|
||||
Bounding: defaultCapabilities(),
|
||||
Permitted: defaultCapabilities(),
|
||||
Inheritable: defaultCapabilities(),
|
||||
Effective: defaultCapabilities(),
|
||||
},
|
||||
}
|
||||
|
||||
s.Linux = &specs.Linux{
|
||||
|
@ -207,6 +212,11 @@ func DefaultLinuxSpec() specs.Spec {
|
|||
},
|
||||
}
|
||||
|
||||
// For LCOW support, populate a blank Windows spec
|
||||
if runtime.GOOS == "windows" {
|
||||
s.Windows = &specs.Windows{}
|
||||
}
|
||||
|
||||
// For LCOW support, don't mask /sys/firmware
|
||||
if runtime.GOOS != "windows" {
|
||||
s.Linux.MaskedPaths = append(s.Linux.MaskedPaths, "/sys/firmware")
|
||||
|
|
Loading…
Reference in a new issue