Merge pull request #13554 from Microsoft/10662-winexec
Windows: The real Windows exec driver is here. Upstream-commit: b7e8169274ba4b897601e1025b91ebd361902213 Component: engine
This commit is contained in:
@ -74,6 +74,7 @@ type CommonContainer struct {
|
||||
MountLabel, ProcessLabel string
|
||||
RestartCount int
|
||||
UpdateDns bool
|
||||
HasBeenStartedBefore bool
|
||||
|
||||
MountPoints map[string]*mountPoint
|
||||
Volumes map[string]string // Deprecated since 1.7, kept for backwards compatibility
|
||||
@ -286,6 +287,7 @@ func (container *Container) Run() error {
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.HasBeenStartedBefore = true
|
||||
container.WaitStop(-1 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -92,11 +92,12 @@ func populateCommand(c *Container, env []string) error {
|
||||
|
||||
// TODO Windows. Further refactoring required (privileged/user)
|
||||
processConfig := execdriver.ProcessConfig{
|
||||
Privileged: c.hostConfig.Privileged,
|
||||
Entrypoint: c.Path,
|
||||
Arguments: c.Args,
|
||||
Tty: c.Config.Tty,
|
||||
User: c.Config.User,
|
||||
Privileged: c.hostConfig.Privileged,
|
||||
Entrypoint: c.Path,
|
||||
Arguments: c.Args,
|
||||
Tty: c.Config.Tty,
|
||||
User: c.Config.User,
|
||||
ConsoleSize: c.hostConfig.ConsoleSize,
|
||||
}
|
||||
|
||||
processConfig.Env = env
|
||||
@ -116,6 +117,7 @@ func populateCommand(c *Container, env []string) error {
|
||||
ProcessConfig: processConfig,
|
||||
ProcessLabel: c.GetProcessLabel(),
|
||||
MountLabel: c.GetMountLabel(),
|
||||
FirstStart: !c.HasBeenStartedBefore,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -138,13 +138,14 @@ type Mount struct {
|
||||
type ProcessConfig struct {
|
||||
exec.Cmd `json:"-"`
|
||||
|
||||
Privileged bool `json:"privileged"`
|
||||
User string `json:"user"`
|
||||
Tty bool `json:"tty"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Arguments []string `json:"arguments"`
|
||||
Terminal Terminal `json:"-"` // standard or tty terminal
|
||||
Console string `json:"-"` // dev/console path
|
||||
Privileged bool `json:"privileged"`
|
||||
User string `json:"user"`
|
||||
Tty bool `json:"tty"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Arguments []string `json:"arguments"`
|
||||
Terminal Terminal `json:"-"` // standard or tty terminal
|
||||
Console string `json:"-"` // dev/console path
|
||||
ConsoleSize [2]int `json:"-"` // h,w of initial console size
|
||||
}
|
||||
|
||||
// TODO Windows: Factor out unused fields such as LxcConfig, AppArmorProfile,
|
||||
@ -175,4 +176,7 @@ type Command struct {
|
||||
LxcConfig []string `json:"lxc_config"`
|
||||
AppArmorProfile string `json:"apparmor_profile"`
|
||||
CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command.
|
||||
FirstStart bool `json:"first_start"`
|
||||
LayerPaths []string `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
|
||||
LayerFolder string `json:"layer_folder"`
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
func NewDriver(name string, options []string, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
switch name {
|
||||
case "windows":
|
||||
return windows.NewDriver(root, initPath)
|
||||
return windows.NewDriver(root, initPath, options)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown exec driver %s", name)
|
||||
}
|
||||
|
||||
36
components/engine/daemon/execdriver/windows/checkoptions.go
Normal file
36
components/engine/daemon/execdriver/windows/checkoptions.go
Normal file
@ -0,0 +1,36 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
func checkSupportedOptions(c *execdriver.Command) error {
|
||||
// Windows doesn't support read-only root filesystem
|
||||
if c.ReadonlyRootfs {
|
||||
return errors.New("Windows does not support the read-only root filesystem option")
|
||||
}
|
||||
|
||||
// Windows doesn't support username
|
||||
if c.ProcessConfig.User != "" {
|
||||
return errors.New("Windows does not support the username option")
|
||||
}
|
||||
|
||||
// Windows doesn't support custom lxc options
|
||||
if c.LxcConfig != nil {
|
||||
return errors.New("Windows does not support lxc options")
|
||||
}
|
||||
|
||||
// Windows doesn't support ulimit
|
||||
if c.Resources.Rlimits != nil {
|
||||
return errors.New("Windows does not support ulimit options")
|
||||
}
|
||||
|
||||
// TODO Windows: Validate other fields which Windows doesn't support, factor
|
||||
// out where applicable per platform.
|
||||
|
||||
return nil
|
||||
}
|
||||
7
components/engine/daemon/execdriver/windows/clean.go
Normal file
7
components/engine/daemon/execdriver/windows/clean.go
Normal file
@ -0,0 +1,7 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
func (d *driver) Clean(id string) error {
|
||||
return nil
|
||||
}
|
||||
144
components/engine/daemon/execdriver/windows/exec.go
Normal file
144
components/engine/daemon/execdriver/windows/exec.go
Normal file
@ -0,0 +1,144 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/microsoft/hcsshim"
|
||||
"github.com/natefinch/npipe"
|
||||
)
|
||||
|
||||
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
|
||||
var (
|
||||
inListen, outListen, errListen *npipe.PipeListener
|
||||
term execdriver.Terminal
|
||||
err error
|
||||
randomID string = stringid.GenerateRandomID()
|
||||
serverPipeFormat, clientPipeFormat string
|
||||
pid uint32
|
||||
exitCode int32
|
||||
)
|
||||
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID)
|
||||
}
|
||||
|
||||
createProcessParms := hcsshim.CreateProcessParams{
|
||||
EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty
|
||||
WorkingDirectory: c.WorkingDir,
|
||||
}
|
||||
|
||||
// Configure the environment for the process // Note NOT c.ProcessConfig.Tty
|
||||
createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env)
|
||||
|
||||
// We use another unique ID here for each exec instance otherwise it
|
||||
// may conflict with the pipe name being used by RUN.
|
||||
|
||||
// We use a different pipe name between real and dummy mode in the HCS
|
||||
if dummyMode {
|
||||
clientPipeFormat = `\\.\pipe\docker-exec-%[1]s-%[2]s-%[3]s`
|
||||
serverPipeFormat = clientPipeFormat
|
||||
} else {
|
||||
clientPipeFormat = `\\.\pipe\docker-exec-%[2]s-%[3]s`
|
||||
serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-exec-%[2]s-%[3]s`
|
||||
}
|
||||
|
||||
// Connect stdin
|
||||
if pipes.Stdin != nil {
|
||||
stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdin")
|
||||
createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdin")
|
||||
|
||||
// Listen on the named pipe
|
||||
inListen, err = npipe.Listen(stdInPipe)
|
||||
if err != nil {
|
||||
logrus.Errorf("stdin failed to listen on %s %s ", stdInPipe, err)
|
||||
return -1, err
|
||||
}
|
||||
defer inListen.Close()
|
||||
|
||||
// Launch a goroutine to do the accept. We do this so that we can
|
||||
// cause an otherwise blocking goroutine to gracefully close when
|
||||
// the caller (us) closes the listener
|
||||
go stdinAccept(inListen, stdInPipe, pipes.Stdin)
|
||||
}
|
||||
|
||||
// Connect stdout
|
||||
stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdout")
|
||||
createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdout")
|
||||
|
||||
outListen, err = npipe.Listen(stdOutPipe)
|
||||
if err != nil {
|
||||
logrus.Errorf("stdout failed to listen on %s %s", stdOutPipe, err)
|
||||
return -1, err
|
||||
}
|
||||
defer outListen.Close()
|
||||
go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout)
|
||||
|
||||
// No stderr on TTY. Note NOT c.ProcessConfig.Tty
|
||||
if !processConfig.Tty {
|
||||
// Connect stderr
|
||||
stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stderr")
|
||||
createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stderr")
|
||||
|
||||
errListen, err = npipe.Listen(stdErrPipe)
|
||||
if err != nil {
|
||||
logrus.Errorf("Stderr failed to listen on %s %s", stdErrPipe, err)
|
||||
return -1, err
|
||||
}
|
||||
defer errListen.Close()
|
||||
go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr)
|
||||
}
|
||||
|
||||
// While this should get caught earlier, just in case, validate that we
|
||||
// have something to run.
|
||||
if processConfig.Entrypoint == "" {
|
||||
err = errors.New("No entrypoint specified")
|
||||
logrus.Error(err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Build the command line of the process
|
||||
createProcessParms.CommandLine = processConfig.Entrypoint
|
||||
for _, arg := range processConfig.Arguments {
|
||||
logrus.Debugln("appending ", arg)
|
||||
createProcessParms.CommandLine += " " + arg
|
||||
}
|
||||
logrus.Debugln("commandLine: ", createProcessParms.CommandLine)
|
||||
|
||||
// Start the command running in the container.
|
||||
pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Note NOT c.ProcessConfig.Tty
|
||||
if processConfig.Tty {
|
||||
term = NewTtyConsole(c.ID, pid)
|
||||
} else {
|
||||
term = NewStdConsole()
|
||||
}
|
||||
processConfig.Terminal = term
|
||||
|
||||
// Invoke the start callback
|
||||
if startCallback != nil {
|
||||
startCallback(&c.ProcessConfig, int(pid))
|
||||
}
|
||||
|
||||
if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil {
|
||||
logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// TODO Windows - Do something with this exit code
|
||||
logrus.Debugln("Exiting Run() with ExitCode 0", c.ID)
|
||||
return int(exitCode), nil
|
||||
}
|
||||
10
components/engine/daemon/execdriver/windows/getpids.go
Normal file
10
components/engine/daemon/execdriver/windows/getpids.go
Normal file
@ -0,0 +1,10 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
// TODO Windows: Implementation required.
|
||||
return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented")
|
||||
}
|
||||
23
components/engine/daemon/execdriver/windows/info.go
Normal file
23
components/engine/daemon/execdriver/windows/info.go
Normal file
@ -0,0 +1,23 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import "github.com/docker/docker/daemon/execdriver"
|
||||
|
||||
type info struct {
|
||||
ID string
|
||||
driver *driver
|
||||
}
|
||||
|
||||
func (d *driver) Info(id string) execdriver.Info {
|
||||
return &info{
|
||||
ID: id,
|
||||
driver: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *info) IsRunning() bool {
|
||||
var running bool
|
||||
running = true // TODO Need an HCS API
|
||||
return running
|
||||
}
|
||||
82
components/engine/daemon/execdriver/windows/namedpipes.go
Normal file
82
components/engine/daemon/execdriver/windows/namedpipes.go
Normal file
@ -0,0 +1,82 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/natefinch/npipe"
|
||||
)
|
||||
|
||||
// stdinAccept runs as a go function. It waits for the container system
|
||||
// to accept our offer of a named pipe for stdin. Once accepted, if we are
|
||||
// running "attached" to the container (eg docker run -i), then we spin up
|
||||
// a further thread to copy anything from the client into the container.
|
||||
//
|
||||
// Important design note. This function is run as a go function for a very
|
||||
// good reason. The named pipe Accept call is blocking until one of two things
|
||||
// happen. Either someone connects to it, or it is forcibly closed. Let's
|
||||
// assume that no-one connects to it, the only way otherwise the Run()
|
||||
// method would continue is by closing it. However, as that would be the same
|
||||
// thread, it can't close it. Hence we run as another thread allowing Run()
|
||||
// to close the named pipe.
|
||||
func stdinAccept(inListen *npipe.PipeListener, pipeName string, copyfrom io.ReadCloser) {
|
||||
|
||||
// Wait for the pipe to be connected to by the shim
|
||||
logrus.Debugln("stdinAccept: Waiting on ", pipeName)
|
||||
stdinConn, err := inListen.Accept()
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to accept on pipe %s %s", pipeName, err)
|
||||
return
|
||||
}
|
||||
logrus.Debugln("Connected to ", stdinConn.RemoteAddr())
|
||||
|
||||
// Anything that comes from the client stdin should be copied
|
||||
// across to the stdin named pipe of the container.
|
||||
if copyfrom != nil {
|
||||
go func() {
|
||||
defer stdinConn.Close()
|
||||
logrus.Debugln("Calling io.Copy on stdin")
|
||||
bytes, err := io.Copy(stdinConn, copyfrom)
|
||||
logrus.Debugf("Finished io.Copy on stdin bytes=%d err=%s pipe=%s", bytes, err, stdinConn.RemoteAddr())
|
||||
}()
|
||||
} else {
|
||||
defer stdinConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// stdouterrAccept runs as a go function. It waits for the container system to
|
||||
// accept our offer of a named pipe - in fact two of them - one for stdout
|
||||
// and one for stderr (we are called twice). Once the named pipe is accepted,
|
||||
// if we are running "attached" to the container (eg docker run -i), then we
|
||||
// spin up a further thread to copy anything from the containers output channels
|
||||
// to the client.
|
||||
func stdouterrAccept(outerrListen *npipe.PipeListener, pipeName string, copyto io.Writer) {
|
||||
|
||||
// Wait for the pipe to be connected to by the shim
|
||||
logrus.Debugln("out/err: Waiting on ", pipeName)
|
||||
outerrConn, err := outerrListen.Accept()
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to accept on pipe %s %s", pipeName, err)
|
||||
return
|
||||
}
|
||||
logrus.Debugln("Connected to ", outerrConn.RemoteAddr())
|
||||
|
||||
// Anything that comes from the container named pipe stdout/err should be copied
|
||||
// across to the stdout/err of the client
|
||||
if copyto != nil {
|
||||
go func() {
|
||||
defer outerrConn.Close()
|
||||
logrus.Debugln("Calling io.Copy on ", pipeName)
|
||||
bytes, err := io.Copy(copyto, outerrConn)
|
||||
logrus.Debugf("Copied %d bytes from pipe=%s", bytes, outerrConn.RemoteAddr())
|
||||
if err != nil {
|
||||
// Not fatal, just debug log it
|
||||
logrus.Debugf("Error hit during copy %s", err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
defer outerrConn.Close()
|
||||
}
|
||||
}
|
||||
17
components/engine/daemon/execdriver/windows/pauseunpause.go
Normal file
17
components/engine/daemon/execdriver/windows/pauseunpause.go
Normal file
@ -0,0 +1,17 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
func (d *driver) Pause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
||||
|
||||
func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
||||
271
components/engine/daemon/execdriver/windows/run.go
Normal file
271
components/engine/daemon/execdriver/windows/run.go
Normal file
@ -0,0 +1,271 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
// Note this is alpha code for the bring up of containers on Windows.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/microsoft/hcsshim"
|
||||
"github.com/natefinch/npipe"
|
||||
)
|
||||
|
||||
type layer struct {
|
||||
Id string
|
||||
Path string
|
||||
}
|
||||
|
||||
type defConfig struct {
|
||||
DefFile string
|
||||
}
|
||||
|
||||
type networkConnection struct {
|
||||
NetworkName string
|
||||
EnableNat bool
|
||||
}
|
||||
type networkSettings struct {
|
||||
MacAddress string
|
||||
}
|
||||
|
||||
type device struct {
|
||||
DeviceType string
|
||||
Connection interface{}
|
||||
Settings interface{}
|
||||
}
|
||||
|
||||
type containerInit struct {
|
||||
SystemType string
|
||||
Name string
|
||||
IsDummy bool
|
||||
VolumePath string
|
||||
Devices []device
|
||||
IgnoreFlushesDuringBoot bool
|
||||
LayerFolderPath string
|
||||
Layers []layer
|
||||
}
|
||||
|
||||
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
|
||||
|
||||
var (
|
||||
term execdriver.Terminal
|
||||
err error
|
||||
inListen, outListen, errListen *npipe.PipeListener
|
||||
)
|
||||
|
||||
// Make sure the client isn't asking for options which aren't supported
|
||||
err = checkSupportedOptions(c)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
cu := &containerInit{
|
||||
SystemType: "Container",
|
||||
Name: c.ID,
|
||||
IsDummy: dummyMode,
|
||||
VolumePath: c.Rootfs,
|
||||
IgnoreFlushesDuringBoot: c.FirstStart,
|
||||
LayerFolderPath: c.LayerFolder,
|
||||
}
|
||||
|
||||
for i := 0; i < len(c.LayerPaths); i++ {
|
||||
cu.Layers = append(cu.Layers, layer{
|
||||
Id: hcsshim.NewGUID(c.LayerPaths[i]).ToString(),
|
||||
Path: c.LayerPaths[i],
|
||||
})
|
||||
}
|
||||
|
||||
if c.Network.Interface != nil {
|
||||
|
||||
// TODO Windows: Temporary
|
||||
c.Network.Interface.Bridge = "Virtual Switch"
|
||||
|
||||
dev := device{
|
||||
DeviceType: "Network",
|
||||
Connection: &networkConnection{
|
||||
NetworkName: c.Network.Interface.Bridge,
|
||||
EnableNat: false,
|
||||
},
|
||||
}
|
||||
|
||||
if c.Network.Interface.MacAddress != "" {
|
||||
windowsStyleMAC := strings.Replace(
|
||||
c.Network.Interface.MacAddress, ":", "-", -1)
|
||||
dev.Settings = networkSettings{
|
||||
MacAddress: windowsStyleMAC,
|
||||
}
|
||||
}
|
||||
|
||||
cu.Devices = append(cu.Devices, dev)
|
||||
}
|
||||
|
||||
configurationb, err := json.Marshal(cu)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
configuration := string(configurationb)
|
||||
|
||||
err = hcsshim.CreateComputeSystem(c.ID, configuration)
|
||||
if err != nil {
|
||||
logrus.Debugln("Failed to create temporary container ", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
// Start the container
|
||||
logrus.Debugln("Starting container ", c.ID)
|
||||
err = hcsshim.StartComputeSystem(c.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to start compute system: %s", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer func() {
|
||||
// Stop the container
|
||||
|
||||
if terminateMode {
|
||||
logrus.Debugf("Terminating container %s", c.ID)
|
||||
if err := hcsshim.TerminateComputeSystem(c.ID); err != nil {
|
||||
// IMPORTANT: Don't fail if fails to change state. It could already
|
||||
// have been stopped through kill().
|
||||
// Otherwise, the docker daemon will hang in job wait()
|
||||
logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err)
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("Shutting down container %s", c.ID)
|
||||
if err := hcsshim.ShutdownComputeSystem(c.ID); err != nil {
|
||||
// IMPORTANT: Don't fail if fails to change state. It could already
|
||||
// have been stopped through kill().
|
||||
// Otherwise, the docker daemon will hang in job wait()
|
||||
logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// We use a different pipe name between real and dummy mode in the HCS
|
||||
var serverPipeFormat, clientPipeFormat string
|
||||
if dummyMode {
|
||||
clientPipeFormat = `\\.\pipe\docker-run-%[1]s-%[2]s`
|
||||
serverPipeFormat = clientPipeFormat
|
||||
} else {
|
||||
clientPipeFormat = `\\.\pipe\docker-run-%[2]s`
|
||||
serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-run-%[2]s`
|
||||
}
|
||||
|
||||
createProcessParms := hcsshim.CreateProcessParams{
|
||||
EmulateConsole: c.ProcessConfig.Tty,
|
||||
WorkingDirectory: c.WorkingDir,
|
||||
ConsoleSize: c.ProcessConfig.ConsoleSize,
|
||||
}
|
||||
|
||||
// Configure the environment for the process
|
||||
createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
|
||||
|
||||
// Connect stdin
|
||||
if pipes.Stdin != nil {
|
||||
stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdin")
|
||||
createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdin")
|
||||
|
||||
// Listen on the named pipe
|
||||
inListen, err = npipe.Listen(stdInPipe)
|
||||
if err != nil {
|
||||
logrus.Errorf("stdin failed to listen on %s err=%s", stdInPipe, err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer inListen.Close()
|
||||
|
||||
// Launch a goroutine to do the accept. We do this so that we can
|
||||
// cause an otherwise blocking goroutine to gracefully close when
|
||||
// the caller (us) closes the listener
|
||||
go stdinAccept(inListen, stdInPipe, pipes.Stdin)
|
||||
}
|
||||
|
||||
// Connect stdout
|
||||
stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdout")
|
||||
createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdout")
|
||||
|
||||
outListen, err = npipe.Listen(stdOutPipe)
|
||||
if err != nil {
|
||||
logrus.Errorf("stdout failed to listen on %s err=%s", stdOutPipe, err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer outListen.Close()
|
||||
go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout)
|
||||
|
||||
// No stderr on TTY.
|
||||
if !c.ProcessConfig.Tty {
|
||||
// Connect stderr
|
||||
stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stderr")
|
||||
createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stderr")
|
||||
errListen, err = npipe.Listen(stdErrPipe)
|
||||
if err != nil {
|
||||
logrus.Errorf("stderr failed to listen on %s err=%s", stdErrPipe, err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer errListen.Close()
|
||||
go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr)
|
||||
}
|
||||
|
||||
// This should get caught earlier, but just in case - validate that we
|
||||
// have something to run
|
||||
if c.ProcessConfig.Entrypoint == "" {
|
||||
err = errors.New("No entrypoint specified")
|
||||
logrus.Error(err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
// Build the command line of the process
|
||||
createProcessParms.CommandLine = c.ProcessConfig.Entrypoint
|
||||
for _, arg := range c.ProcessConfig.Arguments {
|
||||
logrus.Debugln("appending ", arg)
|
||||
createProcessParms.CommandLine += " " + arg
|
||||
}
|
||||
logrus.Debugf("CommandLine: %s", createProcessParms.CommandLine)
|
||||
|
||||
// Start the command running in the container.
|
||||
var pid uint32
|
||||
pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
//Save the PID as we'll need this in Kill()
|
||||
logrus.Debugf("PID %d", pid)
|
||||
c.ContainerPid = int(pid)
|
||||
|
||||
if c.ProcessConfig.Tty {
|
||||
term = NewTtyConsole(c.ID, pid)
|
||||
} else {
|
||||
term = NewStdConsole()
|
||||
}
|
||||
c.ProcessConfig.Terminal = term
|
||||
|
||||
// Maintain our list of active containers. We'll need this later for exec
|
||||
// and other commands.
|
||||
d.Lock()
|
||||
d.activeContainers[c.ID] = &activeContainer{
|
||||
command: c,
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
// Invoke the start callback
|
||||
if startCallback != nil {
|
||||
startCallback(&c.ProcessConfig, int(pid))
|
||||
}
|
||||
|
||||
var exitCode int32
|
||||
exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
|
||||
return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
|
||||
}
|
||||
13
components/engine/daemon/execdriver/windows/stats.go
Normal file
13
components/engine/daemon/execdriver/windows/stats.go
Normal file
@ -0,0 +1,13 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
return nil, fmt.Errorf("Windows: Stats not implemented")
|
||||
}
|
||||
21
components/engine/daemon/execdriver/windows/stdconsole.go
Normal file
21
components/engine/daemon/execdriver/windows/stdconsole.go
Normal file
@ -0,0 +1,21 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
// StdConsole is for when using a container non-interactively
|
||||
type StdConsole struct {
|
||||
}
|
||||
|
||||
func NewStdConsole() *StdConsole {
|
||||
return &StdConsole{}
|
||||
}
|
||||
|
||||
func (s *StdConsole) Resize(h, w int) error {
|
||||
// we do not need to resize a non tty
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StdConsole) Close() error {
|
||||
// nothing to close here
|
||||
return nil
|
||||
}
|
||||
45
components/engine/daemon/execdriver/windows/terminatekill.go
Normal file
45
components/engine/daemon/execdriver/windows/terminatekill.go
Normal file
@ -0,0 +1,45 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/microsoft/hcsshim"
|
||||
)
|
||||
|
||||
func (d *driver) Terminate(p *execdriver.Command) error {
|
||||
logrus.Debugf("WindowsExec: Terminate() id=%s", p.ID)
|
||||
return kill(p.ID, p.ContainerPid)
|
||||
}
|
||||
|
||||
func (d *driver) Kill(p *execdriver.Command, sig int) error {
|
||||
logrus.Debugf("WindowsExec: Kill() id=%s sig=%d", p.ID, sig)
|
||||
return kill(p.ID, p.ContainerPid)
|
||||
}
|
||||
|
||||
func kill(id string, pid int) error {
|
||||
logrus.Debugln("kill() ", id, pid)
|
||||
var err error
|
||||
|
||||
// Terminate Process
|
||||
if err = hcsshim.TerminateProcessInComputeSystem(id, uint32(pid)); err != nil {
|
||||
logrus.Warnf("Failed to terminate pid %d in %s", id, pid, err)
|
||||
// Ignore errors
|
||||
err = nil
|
||||
}
|
||||
|
||||
if terminateMode {
|
||||
// Terminate the compute system
|
||||
if err = hcsshim.TerminateComputeSystem(id); err != nil {
|
||||
logrus.Errorf("Failed to terminate %s - %s", id, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Shutdown the compute system
|
||||
if err = hcsshim.TerminateComputeSystem(id); err != nil {
|
||||
logrus.Errorf("Failed to shutdown %s - %s", id, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
31
components/engine/daemon/execdriver/windows/ttyconsole.go
Normal file
31
components/engine/daemon/execdriver/windows/ttyconsole.go
Normal file
@ -0,0 +1,31 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/microsoft/hcsshim"
|
||||
)
|
||||
|
||||
// TtyConsole is for when using a container interactively
|
||||
type TtyConsole struct {
|
||||
id string
|
||||
processid uint32
|
||||
}
|
||||
|
||||
func NewTtyConsole(id string, processid uint32) *TtyConsole {
|
||||
tty := &TtyConsole{
|
||||
id: id,
|
||||
processid: processid,
|
||||
}
|
||||
return tty
|
||||
}
|
||||
|
||||
func (t *TtyConsole) Resize(h, w int) error {
|
||||
// TODO Windows: This is not implemented in HCS. Needs plumbing through
|
||||
// along with mechanism for buffering
|
||||
return hcsshim.ResizeConsoleInComputeSystem(t.id, t.processid, h, w)
|
||||
}
|
||||
|
||||
func (t *TtyConsole) Close() error {
|
||||
return nil
|
||||
}
|
||||
@ -1,23 +1,28 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
This is the Windows driver for containers.
|
||||
|
||||
TODO Windows: It is currently a placeholder to allow compilation of the
|
||||
daemon. Future PRs will have an implementation of this driver.
|
||||
*/
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/autogen/dockerversion"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
)
|
||||
|
||||
const (
|
||||
DriverName = "Windows"
|
||||
Version = "Placeholder"
|
||||
// This is a daemon development variable only and should not be
|
||||
// used for running production containers on Windows.
|
||||
var dummyMode bool
|
||||
|
||||
// This allows the daemon to terminate containers rather than shutdown
|
||||
var terminateMode bool
|
||||
|
||||
var (
|
||||
DriverName = "Windows 1854"
|
||||
Version = dockerversion.VERSION + " " + dockerversion.GITCOMMIT
|
||||
)
|
||||
|
||||
type activeContainer struct {
|
||||
@ -25,73 +30,61 @@ type activeContainer struct {
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
root string
|
||||
initPath string
|
||||
}
|
||||
|
||||
type info struct {
|
||||
ID string
|
||||
driver *driver
|
||||
}
|
||||
|
||||
func NewDriver(root, initPath string) (*driver, error) {
|
||||
return &driver{
|
||||
root: root,
|
||||
initPath: initPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
|
||||
return execdriver.ExitStatus{ExitCode: 0}, nil
|
||||
}
|
||||
|
||||
func (d *driver) Terminate(p *execdriver.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) Kill(p *execdriver.Command, sig int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func kill(ID string, PID int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) Pause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
||||
|
||||
func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
||||
|
||||
func (i *info) IsRunning() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *driver) Info(id string) execdriver.Info {
|
||||
return &info{
|
||||
ID: id,
|
||||
driver: d,
|
||||
}
|
||||
root string
|
||||
initPath string
|
||||
activeContainers map[string]*activeContainer
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (d *driver) Name() string {
|
||||
return fmt.Sprintf("%s Date %s", DriverName, Version)
|
||||
return fmt.Sprintf("%s %s", DriverName, Version)
|
||||
}
|
||||
|
||||
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented")
|
||||
func NewDriver(root, initPath string, options []string) (*driver, error) {
|
||||
|
||||
for _, option := range options {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
|
||||
case "dummy":
|
||||
switch val {
|
||||
case "1":
|
||||
dummyMode = true
|
||||
logrus.Warn("Using dummy mode in Windows exec driver. This is for development use only!")
|
||||
}
|
||||
|
||||
case "terminate":
|
||||
switch val {
|
||||
case "1":
|
||||
terminateMode = true
|
||||
logrus.Warn("Using terminate mode in Windows exec driver. This is for testing purposes only.")
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("Unrecognised exec driver option %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
return &driver{
|
||||
root: root,
|
||||
initPath: initPath,
|
||||
activeContainers: make(map[string]*activeContainer),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *driver) Clean(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
return nil, fmt.Errorf("Windows: Stats not implemented")
|
||||
}
|
||||
|
||||
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
return 0, nil
|
||||
// setupEnvironmentVariables convert a string array of environment variables
|
||||
// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
|
||||
func setupEnvironmentVariables(a []string) map[string]string {
|
||||
r := make(map[string]string)
|
||||
for _, s := range a {
|
||||
arr := strings.Split(s, "=")
|
||||
if len(arr) == 2 {
|
||||
r[arr[0]] = arr[1]
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user