2014-02-11 23:04:39 -05:00
package runconfig
import (
"fmt"
2014-04-30 18:46:56 -04:00
"path"
2014-08-04 19:14:43 -04:00
"strconv"
2014-04-30 18:46:56 -04:00
"strings"
2014-07-24 18:19:50 -04:00
"github.com/docker/docker/nat"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
2014-07-28 20:23:38 -04:00
"github.com/docker/docker/pkg/parsers"
2014-07-24 18:19:50 -04:00
"github.com/docker/docker/pkg/units"
"github.com/docker/docker/utils"
2014-02-11 23:04:39 -05:00
)
var (
2014-09-10 23:24:16 -04:00
ErrInvalidWorkingDirectory = fmt . Errorf ( "The working directory is invalid. It needs to be an absolute path." )
ErrConflictContainerNetworkAndLinks = fmt . Errorf ( "Conflicting options: --net=container can't be used with links. This would result in undefined behavior." )
ErrConflictContainerNetworkAndDns = fmt . Errorf ( "Conflicting options: --net=container can't be used with --dns. This configuration is invalid." )
ErrConflictNetworkHostname = fmt . Errorf ( "Conflicting options: -h and the network mode (--net)" )
ErrConflictHostNetworkAndDns = fmt . Errorf ( "Conflicting options: --net=host can't be used with --dns. This configuration is invalid." )
ErrConflictHostNetworkAndLinks = fmt . Errorf ( "Conflicting options: --net=host can't be used with links. This would result in undefined behavior." )
2014-02-11 23:04:39 -05:00
)
2014-10-30 12:35:49 -04:00
func Parse ( cmd * flag . FlagSet , args [ ] string ) ( * Config , * HostConfig , * flag . FlagSet , error ) {
2014-02-11 23:04:39 -05:00
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
2014-08-09 21:13:44 -04:00
flPublish = opts . NewListOpts ( nil )
flExpose = opts . NewListOpts ( nil )
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-09-13 00:35:59 -04:00
flExtraHosts = opts . NewListOpts ( opts . ValidateExtraHost )
2014-08-09 21:13:44 -04:00
flVolumesFrom = opts . NewListOpts ( nil )
flLxcOpts = opts . NewListOpts ( nil )
flEnvFile = opts . NewListOpts ( nil )
flCapAdd = opts . NewListOpts ( nil )
flCapDrop = opts . NewListOpts ( nil )
2014-09-29 06:44:32 -04:00
flSecurityOpt = opts . NewListOpts ( nil )
2014-02-11 23:04:39 -05:00
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-11-03 05:43:11 -05:00
flMacAddress = cmd . String ( [ ] string { "-mac-address" } , "" , "Container MAC address (e.g. 92:d0:c6:0a:29:33)" )
2014-11-10 16:14:17 -05:00
flIpcMode = cmd . String ( [ ] string { "-ipc" } , "" , "Default is to create a private IPC namespace (POSIX SysV IPC) for the container\n'container:<name|id>': reuses another container shared memory, semaphores and message queues\n'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure." )
2014-08-13 21:21:15 -04:00
flRestartPolicy = cmd . String ( [ ] string { "-restart" } , "" , "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)" )
2014-02-11 23:04:39 -05:00
)
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-10-07 23:10:31 -04:00
cmd . Var ( & flDevices , [ ] string { "-device" } , "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)" )
2014-03-10 09:11:23 -04:00
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-09-16 21:08:30 -04:00
cmd . Var ( & flExpose , [ ] string { "#expose" , "-expose" } , "Expose a port or a range of ports (e.g. --expose=3300-3310) 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" )
2014-10-30 21:57:54 -04:00
cmd . Var ( & flDnsSearch , [ ] string { "-dns-search" } , "Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)" )
2014-09-13 00:35:59 -04:00
cmd . Var ( & flExtraHosts , [ ] string { "-add-host" } , "Add a custom host-to-IP mapping (host:ip)" )
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-09-29 06:44:32 -04:00
cmd . Var ( & flSecurityOpt , [ ] string { "-security-opt" } , "Security Options" )
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
}
// Validate input params
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
}
2014-03-10 09:11:23 -04:00
var (
attachStdin = flAttach . Get ( "stdin" )
attachStdout = flAttach . Get ( "stdout" )
attachStderr = flAttach . Get ( "stderr" )
)
2014-02-11 23:04:39 -05:00
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-07-16 17:09:30 -04:00
if * flNetMode == "host" && flLinks . Len ( ) > 0 {
return nil , nil , cmd , ErrConflictHostNetworkAndLinks
}
2014-07-30 10:51:28 -04:00
if * flNetMode == "container" && flLinks . Len ( ) > 0 {
return nil , nil , cmd , ErrConflictContainerNetworkAndLinks
}
if * flNetMode == "host" && flDns . Len ( ) > 0 {
return nil , nil , cmd , ErrConflictHostNetworkAndDns
}
if * flNetMode == "container" && flDns . Len ( ) > 0 {
return nil , nil , cmd , ErrConflictContainerNetworkAndDns
}
2014-02-11 23:04:39 -05:00
// If neither -d or -a are set, attach to everything by default
2014-03-10 09:11:23 -04:00
if flAttach . Len ( ) == 0 {
attachStdout = true
attachStderr = true
if * flStdin {
attachStdin = true
2014-02-11 23:04:39 -05:00
}
}
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
)
2014-08-27 14:59:13 -04:00
if len ( parsedArgs ) >= 1 {
image = cmd . Arg ( 0 )
2014-02-11 23:04:39 -05:00
}
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 )
}
2014-09-16 21:08:30 -04:00
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
if strings . Contains ( e , "-" ) {
proto , port := nat . SplitProtoPort ( e )
//parse the start and end port and create a sequence of ports to expose
parts := strings . Split ( port , "-" )
start , _ := strconv . Atoi ( parts [ 0 ] )
end , _ := strconv . Atoi ( parts [ 1 ] )
for i := start ; i <= end ; i ++ {
p := nat . NewPort ( proto , strconv . Itoa ( i ) )
if _ , exists := ports [ p ] ; ! exists {
ports [ p ] = struct { } { }
}
}
} else {
p := nat . NewPort ( nat . SplitProtoPort ( e ) )
if _ , exists := ports [ p ] ; ! exists {
ports [ p ] = struct { } { }
}
2014-02-11 23:04:39 -05:00
}
}
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
2014-11-10 16:14:17 -05:00
ipcMode := IpcMode ( * flIpcMode )
if ! ipcMode . Valid ( ) {
return nil , nil , cmd , fmt . Errorf ( "--ipc: invalid IPC mode: %v" , err )
}
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-08-04 19:14:43 -04:00
restartPolicy , err := parseRestartPolicy ( * flRestartPolicy )
if err != nil {
return nil , nil , cmd , err
}
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-03-10 09:11:23 -04:00
AttachStdin : attachStdin ,
AttachStdout : attachStdout ,
AttachStderr : attachStderr ,
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 ( ) ,
2014-10-03 17:02:17 -04:00
MacAddress : * flMacAddress ,
2014-02-11 23:04:39 -05:00
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 ( ) ,
2014-09-13 00:35:59 -04:00
ExtraHosts : flExtraHosts . GetAll ( ) ,
2014-05-02 17:06:05 -04:00
VolumesFrom : flVolumesFrom . GetAll ( ) ,
NetworkMode : netMode ,
2014-11-10 16:14:17 -05:00
IpcMode : ipcMode ,
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-08-04 19:14:43 -04:00
RestartPolicy : restartPolicy ,
2014-11-03 17:57:18 -05:00
SecurityOpt : flSecurityOpt . GetAll ( ) ,
2014-02-11 23:04:39 -05:00
}
// 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-08-04 19:14:43 -04:00
// parseRestartPolicy returns the parsed policy or an error indicating what is incorrect
func parseRestartPolicy ( policy string ) ( RestartPolicy , error ) {
p := RestartPolicy { }
if policy == "" {
return p , nil
}
var (
parts = strings . Split ( policy , ":" )
name = parts [ 0 ]
)
switch name {
2014-08-04 21:20:53 -04:00
case "always" :
p . Name = name
if len ( parts ) == 2 {
return p , fmt . Errorf ( "maximum restart count not valid with restart policy of \"always\"" )
}
case "no" :
// do nothing
case "on-failure" :
2014-08-04 19:14:43 -04:00
p . Name = name
if len ( parts ) == 2 {
count , err := strconv . Atoi ( parts [ 1 ] )
if err != nil {
return p , err
}
p . MaximumRetryCount = count
}
default :
return p , fmt . Errorf ( "invalid restart policy %s" , name )
}
return p , 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 ( ) {
2014-07-28 20:23:38 -04:00
k , v , err := parsers . ParseKeyValueOpt ( o )
2014-03-13 12:03:09 -04:00
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
}