2014-02-12 08:04:39 +04:00
package runconfig
import (
"fmt"
2014-05-01 02:46:56 +04:00
"io/ioutil"
"path"
"strings"
2014-02-12 08:04:39 +04:00
"github.com/dotcloud/docker/nat"
2014-03-11 01:10:23 +04:00
"github.com/dotcloud/docker/opts"
2014-02-12 08:04:39 +04:00
flag "github.com/dotcloud/docker/pkg/mflag"
"github.com/dotcloud/docker/pkg/sysinfo"
2014-05-13 03:40:19 +04:00
"github.com/dotcloud/docker/pkg/units"
2014-02-12 08:04:39 +04:00
"github.com/dotcloud/docker/utils"
)
var (
2014-05-08 03:28:51 +04:00
ErrInvalidWorkingDirectory = fmt . Errorf ( "The working directory is invalid. It needs to be an absolute path." )
2014-02-12 08:04:39 +04:00
ErrConflictAttachDetach = fmt . Errorf ( "Conflicting options: -a and -d" )
2014-03-13 21:46:02 +04:00
ErrConflictDetachAutoRemove = fmt . Errorf ( "Conflicting options: --rm and -d" )
2014-05-10 01:41:18 +04:00
ErrConflictNetworkHostname = fmt . Errorf ( "Conflicting options: -h and --net" )
2014-02-12 08:04:39 +04: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 )
flPublish opts . ListOpts
flExpose opts . ListOpts
flDns opts . ListOpts
2014-02-07 20:48:14 +04:00
flDnsSearch = opts . NewListOpts ( opts . ValidateDomain )
2014-02-12 08:04:39 +04:00
flVolumesFrom opts . ListOpts
flLxcOpts opts . ListOpts
2014-03-06 21:55:47 +04:00
flEnvFile opts . ListOpts
2014-02-12 08:04:39 +04:00
flAutoRemove = cmd . Bool ( [ ] string { "#rm" , "-rm" } , false , "Automatically remove the container when it exits (incompatible with -d)" )
flDetach = cmd . Bool ( [ ] string { "d" , "-detach" } , false , "Detached mode: Run container in the background, print new container id" )
2014-05-03 01:06:05 +04:00
flNetwork = cmd . Bool ( [ ] string { "#n" , "#-networking" } , true , "Enable networking for this container" )
2014-02-12 08:04:39 +04: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" )
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" )
flContainerIDFile = cmd . String ( [ ] string { "#cidfile" , "-cidfile" } , "" , "Write the container ID to the file" )
flEntrypoint = cmd . String ( [ ] string { "#entrypoint" , "-entrypoint" } , "" , "Overwrite the default entrypoint of the image" )
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-13 04:44:57 +04:00
flCpuset = cmd . String ( [ ] string { "-cpuset" } , "" , "CPUs in which to allow execution (0-3, 0,1)" )
2014-05-06 21:51:20 +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 contaner" )
2014-02-12 08:04:39 +04:00
// For documentation purpose
_ = cmd . Bool ( [ ] string { "#sig-proxy" , "-sig-proxy" } , true , "Proxify all received signal to the process (even in non-tty mode)" )
_ = cmd . String ( [ ] string { "#name" , "-name" } , "" , "Assign a name to the container" )
)
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 (name:alias)" )
cmd . Var ( & flEnv , [ ] string { "e" , "-env" } , "Set environment variables" )
2014-03-08 01:18:42 +04:00
cmd . Var ( & flEnvFile , [ ] string { "-env-file" } , "Read in a line delimited file of ENV variables" )
2014-02-12 08:04:39 +04:00
2014-05-06 21: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-12 08:04:39 +04:00
cmd . Var ( & flExpose , [ ] string { "#expose" , "-expose" } , "Expose a port from the container without publishing it to your host" )
cmd . Var ( & flDns , [ ] string { "#dns" , "-dns" } , "Set custom dns servers" )
2014-02-07 20:48:14 +04:00
cmd . Var ( & flDnsSearch , [ ] string { "-dns-search" } , "Set custom dns search domains" )
2014-02-12 08:04:39 +04:00
cmd . Var ( & flVolumesFrom , [ ] string { "#volumes-from" , "-volumes-from" } , "Mount volumes from the specified container(s)" )
2014-04-07 23: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-12 08:04:39 +04: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-08 03:28:51 +04:00
return nil , nil , cmd , ErrInvalidWorkingDirectory
2014-02-12 08:04:39 +04:00
}
if * flDetach && * flAutoRemove {
return nil , nil , cmd , ErrConflictDetachAutoRemove
}
2014-05-10 01:41:18 +04:00
if * flNetMode != "bridge" && * flHostname != "" {
return nil , nil , cmd , ErrConflictNetworkHostname
}
2014-02-12 08:04:39 +04: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-13 03:40:19 +04:00
parsedMemory , err := units . RAMInBytes ( * flMemoryString )
2014-02-12 08:04:39 +04: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 {
if arr [ 0 ] == "/" {
return nil , nil , cmd , fmt . Errorf ( "Invalid bind mount: source can't be '/'" )
}
2014-05-20 02: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-12 08:04:39 +04: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 20:03:09 +04:00
lxcConf , err := parseKeyValueOpts ( flLxcOpts )
2014-02-12 08:04:39 +04: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-02-17 04:24:22 +04:00
// collect all the environment variables for the container
envVariables := [ ] string { }
2014-03-06 21:55:47 +04: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-17 04:24:22 +04:00
}
2014-03-07 02:49:47 +04:00
// parse the '-e' and '--env' after, to allow override
envVariables = append ( envVariables , flEnv . GetAll ( ) ... )
2014-02-17 04:24:22 +04:00
// boo, there's no debug output for docker run
//utils.Debugf("Environment variables for the container: %#v", envVariables)
2014-05-03 01:06:05 +04:00
netMode , err := parseNetMode ( * flNetMode )
2014-05-01 02:46:56 +04:00
if err != nil {
2014-05-03 03:59:28 +04:00
return nil , nil , cmd , fmt . Errorf ( "--net: invalid net mode: %v" , err )
2014-05-01 02:46:56 +04:00
}
2014-02-12 08:04:39 +04:00
config := & Config {
Hostname : hostname ,
Domainname : domainname ,
PortSpecs : nil , // Deprecated
ExposedPorts : ports ,
User : * flUser ,
Tty : * flTty ,
2014-05-03 01:06:05 +04:00
NetworkDisabled : ! * flNetwork ,
2014-02-12 08:04:39 +04:00
OpenStdin : * flStdin ,
Memory : flMemory ,
CpuShares : * flCpuShares ,
2014-05-13 04:44:57 +04:00
Cpuset : * flCpuset ,
2014-02-12 08:04:39 +04:00
AttachStdin : flAttach . Get ( "stdin" ) ,
AttachStdout : flAttach . Get ( "stdout" ) ,
AttachStderr : flAttach . Get ( "stderr" ) ,
2014-02-17 04:24:22 +04:00
Env : envVariables ,
2014-02-12 08:04:39 +04:00
Cmd : runCmd ,
Image : image ,
Volumes : flVolumes . GetMap ( ) ,
Entrypoint : entrypoint ,
WorkingDir : * flWorkingDir ,
}
hostConfig := & HostConfig {
2014-05-03 01: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-02-12 08:04:39 +04: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 20: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-04-01 03:12:08 +04:00
} else if strings . TrimSpace ( parts [ 0 ] ) == "" {
return nil , fmt . Errorf ( "key cannot be empty %s" , o )
2014-02-12 08:04:39 +04:00
}
2014-03-13 20:03:09 +04:00
values , exists := out [ parts [ 0 ] ]
if ! exists {
values = [ ] string { }
}
out [ parts [ 0 ] ] = append ( values , parts [ 1 ] )
2014-02-12 08:04:39 +04:00
}
return out , nil
}
2014-03-13 20: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-12 08:04:39 +04:00
}
2014-03-13 20:03:09 +04:00
return out , nil
2014-02-12 08:04:39 +04:00
}
2014-05-01 02:46:56 +04:00
2014-05-03 03:59:28 +04:00
func parseNetMode ( netMode string ) ( NetworkMode , error ) {
2014-05-01 02:46:56 +04:00
parts := strings . Split ( netMode , ":" )
2014-05-02 12:47:12 +04:00
switch mode := parts [ 0 ] ; mode {
2014-05-03 03:59:28 +04:00
case "bridge" , "none" , "host" :
2014-05-02 12:47:12 +04:00
case "container" :
if len ( parts ) < 2 || parts [ 1 ] == "" {
2014-05-03 03:59:28 +04:00
return "" , fmt . Errorf ( "invalid container format container:<name|id>" )
2014-05-01 02:46:56 +04:00
}
2014-05-02 12:47:12 +04:00
default :
2014-05-03 03:59:28 +04:00
return "" , fmt . Errorf ( "invalid --net: %s" , netMode )
2014-05-01 02:46:56 +04:00
}
2014-05-03 03:59:28 +04:00
return NetworkMode ( netMode ) , nil
2014-05-01 02:46:56 +04:00
}