2014-02-12 08:04:39 +04:00
package runconfig
import (
"fmt"
2014-05-01 02:46:56 +04:00
"path"
2014-08-05 03:14:43 +04:00
"strconv"
2014-05-01 02:46:56 +04:00
"strings"
2014-07-25 02:19:50 +04:00
"github.com/docker/docker/nat"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
2014-07-29 04:23:38 +04:00
"github.com/docker/docker/pkg/parsers"
2015-02-11 22:21:38 +03:00
"github.com/docker/docker/pkg/ulimit"
2014-07-25 02:19:50 +04:00
"github.com/docker/docker/pkg/units"
"github.com/docker/docker/utils"
2014-02-12 08:04:39 +04:00
)
var (
2014-09-11 07: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-12 08:04:39 +04:00
)
2014-10-30 19:35:49 +03:00
func Parse ( cmd * flag . FlagSet , args [ ] string ) ( * Config , * HostConfig , * flag . FlagSet , error ) {
2014-02-12 08:04:39 +04: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 08:00:47 +04:00
flDevices = opts . NewListOpts ( opts . ValidatePath )
2014-02-12 08:04:39 +04:00
2015-02-11 22:21:38 +03:00
ulimits = make ( map [ string ] * ulimit . Ulimit )
flUlimits = opts . NewUlimitOpt ( ulimits )
2014-08-10 05:13:44 +04:00
flPublish = opts . NewListOpts ( nil )
flExpose = opts . NewListOpts ( nil )
2014-07-10 01:47:55 +04:00
flDns = opts . NewListOpts ( opts . ValidateIPAddress )
2014-06-26 15:03:23 +04:00
flDnsSearch = opts . NewListOpts ( opts . ValidateDnsSearch )
2014-09-13 08:35:59 +04:00
flExtraHosts = opts . NewListOpts ( opts . ValidateExtraHost )
2014-08-10 05: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 14:44:32 +04:00
flSecurityOpt = opts . NewListOpts ( nil )
2014-02-12 08:04:39 +04:00
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" )
2015-02-04 06:51:35 +03:00
flPidMode = cmd . String ( [ ] string { "-pid" } , "" , "PID namespace to use" )
flPublishAll = cmd . Bool ( [ ] string { "P" , "-publish-all" } , false , "Publish all exposed ports to random ports" )
2014-07-09 04: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-12 08:04:39 +04:00
flContainerIDFile = cmd . String ( [ ] string { "#cidfile" , "-cidfile" } , "" , "Write the container ID to the file" )
2014-07-09 04:23:12 +04:00
flEntrypoint = cmd . String ( [ ] string { "#entrypoint" , "-entrypoint" } , "" , "Overwrite the default ENTRYPOINT of the image" )
2014-02-12 08:04:39 +04:00
flHostname = cmd . String ( [ ] string { "h" , "-hostname" } , "" , "Container host name" )
2015-02-04 06:51:35 +03:00
flMemoryString = cmd . String ( [ ] string { "m" , "-memory" } , "" , "Memory limit" )
flMemorySwap = cmd . String ( [ ] string { "-memory-swap" } , "" , "Total memory (memory + swap), '-1' to disable swap" )
2015-02-03 12:33:09 +03:00
flUser = cmd . String ( [ ] string { "u" , "-user" } , "" , "Username or UID (format: <name|uid>[:<group|gid>])" )
2014-02-12 08:04:39 +04:00
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)" )
2015-02-04 06:51:35 +03:00
flNetMode = cmd . String ( [ ] string { "-net" } , "bridge" , "Set the Network mode for the container" )
2014-11-03 13:43:11 +03:00
flMacAddress = cmd . String ( [ ] string { "-mac-address" } , "" , "Container MAC address (e.g. 92:d0:c6:0a:29:33)" )
2015-02-04 06:51:35 +03:00
flIpcMode = cmd . String ( [ ] string { "-ipc" } , "" , "IPC namespace to use" )
2015-03-10 05:09:12 +03:00
flRestartPolicy = cmd . String ( [ ] string { "-restart" } , "no" , "Restart policy to apply when a container exits" )
2015-01-14 00:52:51 +03:00
flReadonlyRootfs = cmd . Bool ( [ ] string { "-read-only" } , false , "Mount the container's root filesystem as read only" )
2014-02-12 08:04:39 +04:00
)
2015-03-03 20:04:06 +03:00
cmd . Var ( & flAttach , [ ] string { "a" , "-attach" } , "Attach to STDIN, STDOUT or STDERR" )
2015-02-04 06:51:35 +03:00
cmd . Var ( & flVolumes , [ ] string { "v" , "-volume" } , "Bind mount a volume" )
cmd . Var ( & flLinks , [ ] string { "#link" , "-link" } , "Add link to another container" )
cmd . Var ( & flDevices , [ ] string { "-device" } , "Add a host device to the container" )
2014-02-12 08:04:39 +04:00
cmd . Var ( & flEnv , [ ] string { "e" , "-env" } , "Set environment variables" )
2015-02-04 06:51:35 +03:00
cmd . Var ( & flEnvFile , [ ] string { "-env-file" } , "Read in a file of environment variables" )
cmd . Var ( & flPublish , [ ] string { "p" , "-publish" } , "Publish a container's port(s) to the host" )
cmd . Var ( & flExpose , [ ] string { "#expose" , "-expose" } , "Expose a port or a range of ports" )
2014-07-09 04:23:12 +04:00
cmd . Var ( & flDns , [ ] string { "#dns" , "-dns" } , "Set custom DNS servers" )
2015-02-04 06:51:35 +03:00
cmd . Var ( & flDnsSearch , [ ] string { "-dns-search" } , "Set custom DNS search domains" )
2014-09-13 08:35:59 +04:00
cmd . Var ( & flExtraHosts , [ ] string { "-add-host" } , "Add a custom host-to-IP mapping (host:ip)" )
2014-02-12 08:04:39 +04:00
cmd . Var ( & flVolumesFrom , [ ] string { "#volumes-from" , "-volumes-from" } , "Mount volumes from the specified container(s)" )
2015-02-04 06:51:35 +03:00
cmd . Var ( & flLxcOpts , [ ] string { "#lxc-conf" , "-lxc-conf" } , "Add custom lxc options" )
2014-07-11 03: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 14:44:32 +04:00
cmd . Var ( & flSecurityOpt , [ ] string { "-security-opt" } , "Security Options" )
2015-02-11 22:21:38 +03:00
cmd . Var ( flUlimits , [ ] string { "-ulimit" } , "Ulimit options" )
2014-07-10 22:41:11 +04:00
2014-11-05 19:57:51 +03:00
cmd . Require ( flag . Min , 1 )
if err := utils . ParseFlags ( cmd , args , true ) ; err != nil {
2014-02-12 08:04:39 +04:00
return nil , nil , cmd , err
}
// Validate input params
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
}
2014-03-10 17:11:23 +04:00
2015-02-27 18:27:12 +03:00
// Validate the input mac address
if * flMacAddress != "" {
if _ , err := opts . ValidateMACAddress ( * flMacAddress ) ; err != nil {
return nil , nil , cmd , fmt . Errorf ( "%s is not a valid mac address" , * flMacAddress )
}
}
2014-03-10 17:11:23 +04:00
var (
attachStdin = flAttach . Get ( "stdin" )
attachStdout = flAttach . Get ( "stdout" )
attachStderr = flAttach . Get ( "stderr" )
)
2014-02-12 08:04:39 +04:00
2014-06-12 00:54:40 +04:00
if * flNetMode != "bridge" && * flNetMode != "none" && * flHostname != "" {
2014-05-10 01:41:18 +04:00
return nil , nil , cmd , ErrConflictNetworkHostname
}
2014-07-17 01:09:30 +04:00
if * flNetMode == "host" && flLinks . Len ( ) > 0 {
return nil , nil , cmd , ErrConflictHostNetworkAndLinks
}
2014-07-30 18: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-12 08:04:39 +04:00
// If neither -d or -a are set, attach to everything by default
2014-03-10 17:11:23 +04:00
if flAttach . Len ( ) == 0 {
attachStdout = true
attachStderr = true
if * flStdin {
attachStdin = true
2014-02-12 08:04:39 +04:00
}
}
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
}
2014-12-11 03:53:43 +03:00
var MemorySwap int64
if * flMemorySwap != "" {
2015-02-09 05:36:49 +03:00
if * flMemorySwap == "-1" {
MemorySwap = - 1
} else {
parsedMemorySwap , err := units . RAMInBytes ( * flMemorySwap )
if err != nil {
return nil , nil , cmd , err
}
MemorySwap = parsedMemorySwap
2014-12-11 03:53:43 +03:00
}
}
2014-02-12 08:04:39 +04:00
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 21:50:18 +04:00
if arr [ 1 ] == "/" {
2014-06-27 20:49:40 +04:00
return nil , nil , cmd , fmt . Errorf ( "Invalid bind mount: destination can't be '/'" )
2014-02-12 08:04:39 +04:00
}
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
2014-10-16 01:14:12 +04:00
image = cmd . Arg ( 0 )
2014-02-12 08:04:39 +04:00
)
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 )
}
2014-09-17 05:08:30 +04:00
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
2015-02-17 18:12:02 +03:00
proto , port := nat . SplitProtoPort ( e )
//parse the start and end port and create a sequence of ports to expose
//if expose a port, the start and end port are the same
start , end , err := parsers . ParsePortRange ( port )
if err != nil {
return nil , nil , cmd , fmt . Errorf ( "Invalid range format for --expose: %s, error: %s" , e , err )
}
for i := start ; i <= end ; i ++ {
p := nat . NewPort ( proto , strconv . FormatUint ( i , 10 ) )
2014-09-17 05:08:30 +04:00
if _ , exists := ports [ p ] ; ! exists {
ports [ p ] = struct { } { }
}
2014-02-12 08:04:39 +04:00
}
}
2014-05-31 08: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-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
2014-11-11 00:14:17 +03:00
ipcMode := IpcMode ( * flIpcMode )
if ! ipcMode . Valid ( ) {
2014-11-25 23:10:53 +03:00
return nil , nil , cmd , fmt . Errorf ( "--ipc: invalid IPC mode" )
}
pidMode := PidMode ( * flPidMode )
if ! pidMode . Valid ( ) {
return nil , nil , cmd , fmt . Errorf ( "--pid: invalid PID mode" )
2014-11-11 00:14:17 +03:00
}
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-08-05 03:14:43 +04:00
restartPolicy , err := parseRestartPolicy ( * flRestartPolicy )
if err != nil {
return nil , nil , cmd , err
}
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 ,
2014-12-11 03:53:43 +03:00
MemorySwap : MemorySwap ,
2014-02-12 08:04:39 +04:00
CpuShares : * flCpuShares ,
2014-05-13 04:44:57 +04:00
Cpuset : * flCpuset ,
2014-03-10 17:11:23 +04:00
AttachStdin : attachStdin ,
AttachStdout : attachStdout ,
AttachStderr : attachStderr ,
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 ( ) ,
2014-10-04 01:02:17 +04:00
MacAddress : * flMacAddress ,
2014-02-12 08:04:39 +04:00
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 ( ) ,
2014-09-13 08:35:59 +04:00
ExtraHosts : flExtraHosts . GetAll ( ) ,
2014-05-03 01:06:05 +04:00
VolumesFrom : flVolumesFrom . GetAll ( ) ,
NetworkMode : netMode ,
2014-11-11 00:14:17 +03:00
IpcMode : ipcMode ,
2014-11-25 23:10:53 +03:00
PidMode : pidMode ,
2014-05-31 08:00:47 +04:00
Devices : deviceMappings ,
2014-07-10 22:41:11 +04:00
CapAdd : flCapAdd . GetAll ( ) ,
CapDrop : flCapDrop . GetAll ( ) ,
2014-08-05 03:14:43 +04:00
RestartPolicy : restartPolicy ,
2014-11-04 01:57:18 +03:00
SecurityOpt : flSecurityOpt . GetAll ( ) ,
2015-01-14 00:52:51 +03:00
ReadonlyRootfs : * flReadonlyRootfs ,
2015-02-11 22:21:38 +03:00
Ulimits : flUlimits . GetList ( ) ,
2014-02-12 08:04:39 +04: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-05 03: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 ]
)
2015-01-08 12:15:55 +03:00
p . Name = name
2014-08-05 03:14:43 +04:00
switch name {
2014-08-05 05:20:53 +04:00
case "always" :
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-05 03:14:43 +04:00
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 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 ( ) {
2014-07-29 04:23:38 +04:00
k , v , err := parsers . ParseKeyValueOpt ( o )
2014-03-13 20:03:09 +04:00
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
}
2014-05-31 08: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
}