2014-02-11 23:04:39 -05:00
package runconfig
import (
"fmt"
2014-04-30 18:46:56 -04:00
"io/ioutil"
"path"
"strings"
2014-02-11 23:04:39 -05:00
"github.com/dotcloud/docker/nat"
2014-03-10 17:10:23 -04:00
"github.com/dotcloud/docker/opts"
2014-02-11 23:04:39 -05:00
flag "github.com/dotcloud/docker/pkg/mflag"
"github.com/dotcloud/docker/pkg/sysinfo"
2014-05-12 19:40:19 -04:00
"github.com/dotcloud/docker/pkg/units"
2014-02-11 23:04:39 -05:00
"github.com/dotcloud/docker/utils"
)
var (
2014-05-07 19:28:51 -04:00
ErrInvalidWorkingDirectory = fmt . Errorf ( "The working directory is invalid. It needs to be an absolute path." )
2014-02-11 23:04:39 -05:00
ErrConflictAttachDetach = fmt . Errorf ( "Conflicting options: -a and -d" )
2014-03-13 13:46:02 -04:00
ErrConflictDetachAutoRemove = fmt . Errorf ( "Conflicting options: --rm and -d" )
2014-06-11 16:54:40 -04:00
ErrConflictNetworkHostname = fmt . Errorf ( "Conflicting options: -h and the network mode (--net)" )
2014-02-11 23:04:39 -05:00
)
//FIXME Only used in tests
func Parse ( args [ ] string , sysInfo * sysinfo . SysInfo ) ( * Config , * HostConfig , * flag . FlagSet , error ) {
cmd := flag . NewFlagSet ( "run" , flag . ContinueOnError )
cmd . SetOutput ( ioutil . Discard )
cmd . Usage = nil
return parseRun ( cmd , args , sysInfo )
}
// FIXME: this maps the legacy commands.go code. It should be merged with Parse to only expose a single parse function.
func ParseSubcommand ( cmd * flag . FlagSet , args [ ] string , sysInfo * sysinfo . SysInfo ) ( * Config , * HostConfig , * flag . FlagSet , error ) {
return parseRun ( cmd , args , sysInfo )
}
func parseRun ( cmd * flag . FlagSet , args [ ] string , sysInfo * sysinfo . SysInfo ) ( * Config , * HostConfig , * flag . FlagSet , error ) {
var (
// FIXME: use utils.ListOpts for attach and volumes?
flAttach = opts . NewListOpts ( opts . ValidateAttach )
flVolumes = opts . NewListOpts ( opts . ValidatePath )
flLinks = opts . NewListOpts ( opts . ValidateLink )
flEnv = opts . NewListOpts ( opts . ValidateEnv )
2014-05-31 00:00:47 -04:00
flDevices = opts . NewListOpts ( opts . ValidatePath )
2014-02-11 23:04:39 -05:00
flPublish opts . ListOpts
flExpose opts . ListOpts
2014-07-09 17:47:55 -04:00
flDns = opts . NewListOpts ( opts . ValidateIPAddress )
2014-06-26 07:03:23 -04:00
flDnsSearch = opts . NewListOpts ( opts . ValidateDnsSearch )
2014-02-11 23:04:39 -05:00
flVolumesFrom opts . ListOpts
flLxcOpts opts . ListOpts
2014-03-06 12:55:47 -05:00
flEnvFile opts . ListOpts
2014-07-10 14:41:11 -04:00
flCapAdd opts . ListOpts
flCapDrop opts . ListOpts
2014-02-11 23:04:39 -05:00
flAutoRemove = cmd . Bool ( [ ] string { "#rm" , "-rm" } , false , "Automatically remove the container when it exits (incompatible with -d)" )
2014-07-08 20:23:12 -04:00
flDetach = cmd . Bool ( [ ] string { "d" , "-detach" } , false , "Detached mode: run container in the background and print new container ID" )
2014-05-02 17:06:05 -04:00
flNetwork = cmd . Bool ( [ ] string { "#n" , "#-networking" } , true , "Enable networking for this container" )
2014-02-11 23:04:39 -05:00
flPrivileged = cmd . Bool ( [ ] string { "#privileged" , "-privileged" } , false , "Give extended privileges to this container" )
flPublishAll = cmd . Bool ( [ ] string { "P" , "-publish-all" } , false , "Publish all exposed ports to the host interfaces" )
2014-07-08 20:23:12 -04:00
flStdin = cmd . Bool ( [ ] string { "i" , "-interactive" } , false , "Keep STDIN open even if not attached" )
flTty = cmd . Bool ( [ ] string { "t" , "-tty" } , false , "Allocate a pseudo-TTY" )
2014-02-11 23:04:39 -05:00
flContainerIDFile = cmd . String ( [ ] string { "#cidfile" , "-cidfile" } , "" , "Write the container ID to the file" )
2014-07-08 20:23:12 -04:00
flEntrypoint = cmd . String ( [ ] string { "#entrypoint" , "-entrypoint" } , "" , "Overwrite the default ENTRYPOINT of the image" )
2014-02-11 23:04:39 -05:00
flHostname = cmd . String ( [ ] string { "h" , "-hostname" } , "" , "Container host name" )
flMemoryString = cmd . String ( [ ] string { "m" , "-memory" } , "" , "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)" )
flUser = cmd . String ( [ ] string { "u" , "-user" } , "" , "Username or UID" )
flWorkingDir = cmd . String ( [ ] string { "w" , "-workdir" } , "" , "Working directory inside the container" )
flCpuShares = cmd . Int64 ( [ ] string { "c" , "-cpu-shares" } , 0 , "CPU shares (relative weight)" )
2014-05-12 20:44:57 -04:00
flCpuset = cmd . String ( [ ] string { "-cpuset" } , "" , "CPUs in which to allow execution (0-3, 0,1)" )
2014-06-19 03:11:26 -04:00
flNetMode = cmd . String ( [ ] string { "-net" } , "bridge" , "Set the Network mode for the container\n'bridge': creates a new network stack for the container on the docker bridge\n'none': no networking for this container\n'container:<name|id>': reuses another container network stack\n'host': use the host network stack inside the container. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure." )
2014-02-11 23:04:39 -05:00
// For documentation purpose
2014-07-08 20:23:12 -04:00
_ = cmd . Bool ( [ ] string { "#sig-proxy" , "-sig-proxy" } , true , "Proxify received signals to the process (even in non-TTY mode). SIGCHLD is not proxied." )
2014-02-11 23:04:39 -05:00
_ = cmd . String ( [ ] string { "#name" , "-name" } , "" , "Assign a name to the container" )
)
2014-07-08 20:23:12 -04:00
cmd . Var ( & flAttach , [ ] string { "a" , "-attach" } , "Attach to STDIN, STDOUT or STDERR." )
cmd . Var ( & flVolumes , [ ] string { "v" , "-volume" } , "Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)" )
cmd . Var ( & flLinks , [ ] string { "#link" , "-link" } , "Add link to another container in the form of name:alias" )
2014-05-31 00:00:47 -04:00
cmd . Var ( & flDevices , [ ] string { "-device" } , "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)" )
2014-02-11 23:04:39 -05:00
cmd . Var ( & flEnv , [ ] string { "e" , "-env" } , "Set environment variables" )
2014-07-08 20:23:12 -04:00
cmd . Var ( & flEnvFile , [ ] string { "-env-file" } , "Read in a line delimited file of environment variables" )
2014-02-11 23:04:39 -05:00
2014-05-06 13:51:20 -04:00
cmd . Var ( & flPublish , [ ] string { "p" , "-publish" } , fmt . Sprintf ( "Publish a container's port to the host\nformat: %s\n(use 'docker port' to see the actual mapping)" , nat . PortSpecTemplateFormat ) )
2014-02-11 23:04:39 -05:00
cmd . Var ( & flExpose , [ ] string { "#expose" , "-expose" } , "Expose a port from the container without publishing it to your host" )
2014-07-08 20:23:12 -04:00
cmd . Var ( & flDns , [ ] string { "#dns" , "-dns" } , "Set custom DNS servers" )
cmd . Var ( & flDnsSearch , [ ] string { "-dns-search" } , "Set custom DNS search domains" )
2014-02-11 23:04:39 -05:00
cmd . Var ( & flVolumesFrom , [ ] string { "#volumes-from" , "-volumes-from" } , "Mount volumes from the specified container(s)" )
2014-04-07 15:40:41 -04:00
cmd . Var ( & flLxcOpts , [ ] string { "#lxc-conf" , "-lxc-conf" } , "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"" )
2014-02-11 23:04:39 -05:00
2014-07-10 19:50:45 -04:00
cmd . Var ( & flCapAdd , [ ] string { "-cap-add" } , "Add Linux capabilities" )
cmd . Var ( & flCapDrop , [ ] string { "-cap-drop" } , "Drop Linux capabilities" )
2014-07-10 14:41:11 -04:00
2014-02-11 23:04:39 -05:00
if err := cmd . Parse ( args ) ; err != nil {
return nil , nil , cmd , err
}
// Check if the kernel supports memory limit cgroup.
if sysInfo != nil && * flMemoryString != "" && ! sysInfo . MemoryLimit {
* flMemoryString = ""
}
// Validate input params
if * flDetach && flAttach . Len ( ) > 0 {
return nil , nil , cmd , ErrConflictAttachDetach
}
if * flWorkingDir != "" && ! path . IsAbs ( * flWorkingDir ) {
2014-05-07 19:28:51 -04:00
return nil , nil , cmd , ErrInvalidWorkingDirectory
2014-02-11 23:04:39 -05:00
}
if * flDetach && * flAutoRemove {
return nil , nil , cmd , ErrConflictDetachAutoRemove
}
2014-06-11 16:54:40 -04:00
if * flNetMode != "bridge" && * flNetMode != "none" && * flHostname != "" {
2014-05-09 17:41:18 -04:00
return nil , nil , cmd , ErrConflictNetworkHostname
}
2014-02-11 23:04:39 -05:00
// If neither -d or -a are set, attach to everything by default
if flAttach . Len ( ) == 0 && ! * flDetach {
if ! * flDetach {
flAttach . Set ( "stdout" )
flAttach . Set ( "stderr" )
if * flStdin {
flAttach . Set ( "stdin" )
}
}
}
var flMemory int64
if * flMemoryString != "" {
2014-05-12 19:40:19 -04:00
parsedMemory , err := units . RAMInBytes ( * flMemoryString )
2014-02-11 23:04:39 -05:00
if err != nil {
return nil , nil , cmd , err
}
flMemory = parsedMemory
}
var binds [ ] string
// add any bind targets to the list of container volumes
for bind := range flVolumes . GetMap ( ) {
if arr := strings . Split ( bind , ":" ) ; len ( arr ) > 1 {
2014-06-26 13:50:18 -04:00
if arr [ 1 ] == "/" {
2014-06-27 12:49:40 -04:00
return nil , nil , cmd , fmt . Errorf ( "Invalid bind mount: destination can't be '/'" )
2014-02-11 23:04:39 -05:00
}
2014-05-19 18:18:37 -04:00
// after creating the bind mount we want to delete it from the flVolumes values because
// we do not want bind mounts being committed to image configs
2014-02-11 23:04:39 -05:00
binds = append ( binds , bind )
flVolumes . Delete ( bind )
} else if bind == "/" {
return nil , nil , cmd , fmt . Errorf ( "Invalid volume: path can't be '/'" )
}
}
var (
parsedArgs = cmd . Args ( )
runCmd [ ] string
entrypoint [ ] string
image string
)
if len ( parsedArgs ) >= 1 {
image = cmd . Arg ( 0 )
}
if len ( parsedArgs ) > 1 {
runCmd = parsedArgs [ 1 : ]
}
if * flEntrypoint != "" {
entrypoint = [ ] string { * flEntrypoint }
}
2014-03-13 12:03:09 -04:00
lxcConf , err := parseKeyValueOpts ( flLxcOpts )
2014-02-11 23:04:39 -05:00
if err != nil {
return nil , nil , cmd , err
}
var (
domainname string
hostname = * flHostname
parts = strings . SplitN ( hostname , "." , 2 )
)
if len ( parts ) > 1 {
hostname = parts [ 0 ]
domainname = parts [ 1 ]
}
ports , portBindings , err := nat . ParsePortSpecs ( flPublish . GetAll ( ) )
if err != nil {
return nil , nil , cmd , err
}
// Merge in exposed ports to the map of published ports
for _ , e := range flExpose . GetAll ( ) {
if strings . Contains ( e , ":" ) {
return nil , nil , cmd , fmt . Errorf ( "Invalid port format for --expose: %s" , e )
}
p := nat . NewPort ( nat . SplitProtoPort ( e ) )
if _ , exists := ports [ p ] ; ! exists {
ports [ p ] = struct { } { }
}
}
2014-05-31 00:00:47 -04:00
// parse device mappings
deviceMappings := [ ] DeviceMapping { }
for _ , device := range flDevices . GetAll ( ) {
deviceMapping , err := ParseDevice ( device )
if err != nil {
return nil , nil , cmd , err
}
deviceMappings = append ( deviceMappings , deviceMapping )
}
2014-02-16 19:24:22 -05:00
// collect all the environment variables for the container
envVariables := [ ] string { }
2014-03-06 12:55:47 -05:00
for _ , ef := range flEnvFile . GetAll ( ) {
parsedVars , err := opts . ParseEnvFile ( ef )
if err != nil {
return nil , nil , cmd , err
}
envVariables = append ( envVariables , parsedVars ... )
2014-02-16 19:24:22 -05:00
}
2014-03-06 17:49:47 -05:00
// parse the '-e' and '--env' after, to allow override
envVariables = append ( envVariables , flEnv . GetAll ( ) ... )
2014-02-16 19:24:22 -05:00
// boo, there's no debug output for docker run
//utils.Debugf("Environment variables for the container: %#v", envVariables)
2014-05-02 17:06:05 -04:00
netMode , err := parseNetMode ( * flNetMode )
2014-04-30 18:46:56 -04:00
if err != nil {
2014-05-02 19:59:28 -04:00
return nil , nil , cmd , fmt . Errorf ( "--net: invalid net mode: %v" , err )
2014-04-30 18:46:56 -04:00
}
2014-02-11 23:04:39 -05:00
config := & Config {
Hostname : hostname ,
Domainname : domainname ,
PortSpecs : nil , // Deprecated
ExposedPorts : ports ,
User : * flUser ,
Tty : * flTty ,
2014-05-02 17:06:05 -04:00
NetworkDisabled : ! * flNetwork ,
2014-02-11 23:04:39 -05:00
OpenStdin : * flStdin ,
Memory : flMemory ,
CpuShares : * flCpuShares ,
2014-05-12 20:44:57 -04:00
Cpuset : * flCpuset ,
2014-02-11 23:04:39 -05:00
AttachStdin : flAttach . Get ( "stdin" ) ,
AttachStdout : flAttach . Get ( "stdout" ) ,
AttachStderr : flAttach . Get ( "stderr" ) ,
2014-02-16 19:24:22 -05:00
Env : envVariables ,
2014-02-11 23:04:39 -05:00
Cmd : runCmd ,
Image : image ,
Volumes : flVolumes . GetMap ( ) ,
Entrypoint : entrypoint ,
WorkingDir : * flWorkingDir ,
}
hostConfig := & HostConfig {
2014-05-02 17:06:05 -04:00
Binds : binds ,
ContainerIDFile : * flContainerIDFile ,
LxcConf : lxcConf ,
Privileged : * flPrivileged ,
PortBindings : portBindings ,
Links : flLinks . GetAll ( ) ,
PublishAllPorts : * flPublishAll ,
Dns : flDns . GetAll ( ) ,
DnsSearch : flDnsSearch . GetAll ( ) ,
VolumesFrom : flVolumesFrom . GetAll ( ) ,
NetworkMode : netMode ,
2014-05-31 00:00:47 -04:00
Devices : deviceMappings ,
2014-07-10 14:41:11 -04:00
CapAdd : flCapAdd . GetAll ( ) ,
CapDrop : flCapDrop . GetAll ( ) ,
2014-02-11 23:04:39 -05:00
}
if sysInfo != nil && flMemory > 0 && ! sysInfo . SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config . MemorySwap = - 1
}
// When allocating stdin in attached mode, close stdin at client disconnect
if config . OpenStdin && config . AttachStdin {
config . StdinOnce = true
}
return config , hostConfig , cmd , nil
}
2014-03-13 12:03:09 -04:00
// options will come in the format of name.key=value or name.option
func parseDriverOpts ( opts opts . ListOpts ) ( map [ string ] [ ] string , error ) {
out := make ( map [ string ] [ ] string , len ( opts . GetAll ( ) ) )
for _ , o := range opts . GetAll ( ) {
parts := strings . SplitN ( o , "." , 2 )
if len ( parts ) < 2 {
return nil , fmt . Errorf ( "invalid opt format %s" , o )
2014-03-31 19:12:08 -04:00
} else if strings . TrimSpace ( parts [ 0 ] ) == "" {
return nil , fmt . Errorf ( "key cannot be empty %s" , o )
2014-02-11 23:04:39 -05:00
}
2014-03-13 12:03:09 -04:00
values , exists := out [ parts [ 0 ] ]
if ! exists {
values = [ ] string { }
}
out [ parts [ 0 ] ] = append ( values , parts [ 1 ] )
2014-02-11 23:04:39 -05:00
}
return out , nil
}
2014-03-13 12:03:09 -04:00
func parseKeyValueOpts ( opts opts . ListOpts ) ( [ ] utils . KeyValuePair , error ) {
out := make ( [ ] utils . KeyValuePair , opts . Len ( ) )
for i , o := range opts . GetAll ( ) {
k , v , err := utils . ParseKeyValueOpt ( o )
if err != nil {
return nil , err
}
out [ i ] = utils . KeyValuePair { Key : k , Value : v }
2014-02-11 23:04:39 -05:00
}
2014-03-13 12:03:09 -04:00
return out , nil
2014-02-11 23:04:39 -05:00
}
2014-04-30 18:46:56 -04:00
2014-05-02 19:59:28 -04:00
func parseNetMode ( netMode string ) ( NetworkMode , error ) {
2014-04-30 18:46:56 -04:00
parts := strings . Split ( netMode , ":" )
2014-05-02 04:47:12 -04:00
switch mode := parts [ 0 ] ; mode {
2014-05-02 19:59:28 -04:00
case "bridge" , "none" , "host" :
2014-05-02 04:47:12 -04:00
case "container" :
if len ( parts ) < 2 || parts [ 1 ] == "" {
2014-05-02 19:59:28 -04:00
return "" , fmt . Errorf ( "invalid container format container:<name|id>" )
2014-04-30 18:46:56 -04:00
}
2014-05-02 04:47:12 -04:00
default :
2014-05-02 19:59:28 -04:00
return "" , fmt . Errorf ( "invalid --net: %s" , netMode )
2014-04-30 18:46:56 -04:00
}
2014-05-02 19:59:28 -04:00
return NetworkMode ( netMode ) , nil
2014-04-30 18:46:56 -04:00
}
2014-05-31 00:00:47 -04:00
func ParseDevice ( device string ) ( DeviceMapping , error ) {
src := ""
dst := ""
permissions := "rwm"
arr := strings . Split ( device , ":" )
switch len ( arr ) {
case 3 :
permissions = arr [ 2 ]
fallthrough
case 2 :
dst = arr [ 1 ]
fallthrough
case 1 :
src = arr [ 0 ]
default :
return DeviceMapping { } , fmt . Errorf ( "Invalid device specification: %s" , device )
}
if dst == "" {
dst = src
}
deviceMapping := DeviceMapping {
PathOnHost : src ,
PathInContainer : dst ,
CgroupPermissions : permissions ,
}
return deviceMapping , nil
}