2015-05-27 20:15:14 +00:00
|
|
|
// +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 {
|
2015-08-03 01:54:02 +00:00
|
|
|
ID string
|
2015-05-27 20:15:14 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-08-03 01:54:02 +00:00
|
|
|
// Run implements the exec driver Driver interface
|
|
|
|
func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
|
2015-05-27 20:15:14 +00:00
|
|
|
|
|
|
|
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{
|
2015-08-03 01:54:02 +00:00
|
|
|
ID: hcsshim.NewGUID(c.LayerPaths[i]).ToString(),
|
2015-05-27 20:15:14 +00:00
|
|
|
Path: c.LayerPaths[i],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Network.Interface != nil {
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-13 19:34:58 +00:00
|
|
|
logrus.Debugf("Virtual switch '%s', mac='%s'", c.Network.Interface.Bridge, c.Network.Interface.MacAddress)
|
|
|
|
|
2015-05-27 20:15:14 +00:00
|
|
|
cu.Devices = append(cu.Devices, dev)
|
2015-07-13 19:34:58 +00:00
|
|
|
} else {
|
|
|
|
logrus.Debugln("No network interface")
|
2015-05-27 20:15:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|