Merge pull request #7110 from tiborvass/merge-6907

Docker create (rebase of 6907)
This commit is contained in:
Alexandr Morozov 2014-09-17 03:23:50 +04:00
commit ca39a3e36b
12 changed files with 646 additions and 106 deletions

View file

@ -1965,9 +1965,112 @@ func (cli *DockerCli) pullImage(image string) error {
return nil
}
func (cli *DockerCli) CmdRun(args ...string) error {
type cidFile struct {
path string
file *os.File
written bool
}
func newCIDFile(path string) (*cidFile, error) {
if _, err := os.Stat(path); err == nil {
return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
}
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
}
return &cidFile{path: path, file: f}, nil
}
func (cid *cidFile) Close() error {
cid.file.Close()
if !cid.written {
if err := os.Remove(cid.path); err != nil {
return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
}
}
return nil
}
func (cid *cidFile) Write(id string) error {
if _, err := cid.file.Write([]byte(id)); err != nil {
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
}
cid.written = true
return nil
}
func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (engine.Env, error) {
containerValues := url.Values{}
if name != "" {
containerValues.Set("name", name)
}
var data interface{}
if hostConfig != nil {
data = runconfig.MergeConfigs(config, hostConfig)
} else {
data = config
}
var containerIDFile *cidFile
if cidfile != "" {
var err error
if containerIDFile, err = newCIDFile(cidfile); err != nil {
return nil, err
}
defer containerIDFile.Close()
}
//create the container
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), data, false)
//if image not found try to pull it
if statusCode == 404 {
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
if err = cli.pullImage(config.Image); err != nil {
return nil, err
}
// Retry
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), data, false); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
var result engine.Env
if err := result.Decode(stream); err != nil {
return nil, err
}
for _, warning := range result.GetList("Warnings") {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}
if containerIDFile != nil {
if err = containerIDFile.Write(result.Get("Id")); err != nil {
return nil, err
}
}
return result, nil
}
func (cli *DockerCli) CmdCreate(args ...string) error {
// FIXME: just use runconfig.Parse already
config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container")
// These are flags not stored in Config/HostConfig
var (
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
)
config, hostConfig, cmd, err := runconfig.ParseSubcommand(cmd, args, nil)
if err != nil {
return err
}
@ -1976,82 +2079,70 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return nil
}
// Retrieve relevant client-side config
createResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", createResult.Get("Id"))
return nil
}
func (cli *DockerCli) CmdRun(args ...string) error {
// FIXME: just use runconfig.Parse already
cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
// These are flags not stored in Config/HostConfig
var (
flName = cmd.Lookup("name")
flRm = cmd.Lookup("rm")
flSigProxy = cmd.Lookup("sig-proxy")
autoRemove, _ = strconv.ParseBool(flRm.Value.String())
sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String())
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 the container in the background and print the new container ID")
flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (even in non-TTY mode). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.")
flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
flAttach *opts.ListOpts
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
)
// Disable sigProxy in case on TTY
config, hostConfig, cmd, err := runconfig.ParseSubcommand(cmd, args, nil)
if err != nil {
return err
}
if config.Image == "" {
cmd.Usage()
return nil
}
if *flDetach {
if fl := cmd.Lookup("attach"); fl != nil {
flAttach = fl.Value.(*opts.ListOpts)
if flAttach.Len() != 0 {
return ErrConflictAttachDetach
}
}
if *flAutoRemove {
return ErrConflictDetachAutoRemove
}
config.AttachStdin = false
config.AttachStdout = false
config.AttachStderr = false
config.StdinOnce = false
}
// Disable flSigProxy in case on TTY
sigProxy := *flSigProxy
if config.Tty {
sigProxy = false
}
var containerIDFile io.WriteCloser
if len(hostConfig.ContainerIDFile) > 0 {
if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
return fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
}
if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
return fmt.Errorf("Failed to create the container ID file: %s", err)
}
defer func() {
containerIDFile.Close()
var (
cidFileInfo os.FileInfo
err error
)
if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil {
return
}
if cidFileInfo.Size() == 0 {
if err := os.Remove(hostConfig.ContainerIDFile); err != nil {
fmt.Printf("failed to remove Container ID file '%s': %s \n", hostConfig.ContainerIDFile, err)
}
}
}()
}
containerValues := url.Values{}
if name := flName.Value.String(); name != "" {
containerValues.Set("name", name)
}
//create the container
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
//if image not found try to pull it
if statusCode == 404 {
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
if err = cli.pullImage(config.Image); err != nil {
return err
}
// Retry
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil {
return err
}
} else if err != nil {
runResult, err := cli.createContainer(config, nil, hostConfig.ContainerIDFile, *flName)
if err != nil {
return err
}
var runResult engine.Env
if err := runResult.Decode(stream); err != nil {
return err
}
for _, warning := range runResult.GetList("Warnings") {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}
if len(hostConfig.ContainerIDFile) > 0 {
if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil {
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
}
}
if sigProxy {
sigc := cli.forwardAllSignals(runResult.Get("Id"))
defer signal.StopCatch(sigc)
@ -2071,6 +2162,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}()
}
if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") {
return ErrConflictRestartPolicyAndAutoRemove
}
// We need to instanciate the chan because the select needs it. It can
// be closed but can't be uninitialized.
hijacked := make(chan io.Closer)
@ -2158,7 +2253,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
var status int
// Attached mode
if autoRemove {
if *flAutoRemove {
// Autoremove: wait for the container to finish, retrieve
// the exit code and remove the container
if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil {

View file

@ -217,6 +217,75 @@ _docker_cp()
fi
}
_docker_create()
{
case "$prev" in
-a|--attach)
COMPREPLY=( $( compgen -W 'stdin stdout stderr' -- "$cur" ) )
return
;;
--cidfile|--env-file)
_filedir
return
;;
--volumes-from)
__docker_containers_all
return
;;
-v|--volume)
case "$cur" in
*:*)
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
;;
'')
COMPREPLY=( $( compgen -W '/' -- "$cur" ) )
compopt -o nospace
;;
/*)
_filedir
compopt -o nospace
;;
esac
return
;;
-e|--env)
COMPREPLY=( $( compgen -e -- "$cur" ) )
compopt -o nospace
return
;;
--link)
case "$cur" in
*:*)
;;
*)
__docker_containers_running
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
compopt -o nospace
;;
esac
return
;;
--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-p|--publish|--expose|--dns|--lxc-conf)
return
;;
*)
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir -c --cpu-shares --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf')
if [ $cword -eq $counter ]; then
__docker_image_repos_and_tags_and_ids
fi
;;
esac
}
_docker_diff()
{
local counter=$(__docker_pos_first_nonflag)
@ -670,6 +739,7 @@ _docker()
build
commit
cp
create
diff
events
exec

View file

@ -16,7 +16,7 @@
function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand'
for i in (commandline -opc)
if contains -- $i attach build commit cp diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
if contains -- $i attach build commit cp create diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
return 1
end
end
@ -88,6 +88,33 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_pri
# cp
complete -c docker -f -n '__fish_docker_no_subcommand' -a cp -d "Copy files/folders from a container's filesystem to the host path"
# create
complete -c docker -f -n '__fish_docker_no_subcommand' -a run -d 'Run a command in a new container'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to the host interfaces'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s a -l attach -d 'Attach to stdin, stdout or stderr.'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s c -l cpu-shares -d 'CPU shares (relative weight)'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Write the container ID to the file'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom dns servers'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s e -l env -d 'Set environment variables'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l entrypoint -d 'Overwrite the default entrypoint of the image'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expose a port from the container without publishing it to your host'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number><optional unit>, where unit = b, k, m or g)'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s p -l publish -d "Publish a container's port to the host (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort) (use 'docker port' to see the actual mapping)"
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l privileged -d 'Give extended privileges to this container'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-tty'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l volumes-from -d 'Mount volumes from the specified container(s)'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -a '(__fish_print_docker_images)' -d "Image"
# diff
complete -c docker -f -n '__fish_docker_no_subcommand' -a diff -d "Inspect changes on a container's filesystem"
complete -c docker -A -f -n '__fish_seen_subcommand_from diff' -a '(__fish_print_docker_containers all)' -d "Container"

View file

@ -224,6 +224,32 @@ __docker_subcommand () {
;;
esac
;;
(create)
_arguments \
'-P[Publish all exposed ports to the host]' \
'-a[Attach to stdin, stdout or stderr]' \
'-c=-[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
'--cidfile=-[Write the container ID to the file]:CID file:_files' \
'*--dns=-[Set custom dns servers]:dns server: ' \
'*-e=-[Set environment variables]:environment variable: ' \
'--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
'*--expose=-[Expose a port from the container without publishing it]: ' \
'-h=-[Container host name]:hostname:_hosts' \
'-i[Keep stdin open even if not attached]' \
'--link=-[Add link to another container]:link:->link' \
'--lxc-conf=-[Add custom lxc options]:lxc options: ' \
'-m=-[Memory limit (in bytes)]:limit: ' \
'--name=-[Container name]:name: ' \
'*-p=-[Expose a container'"'"'s port to the host]:port:_ports' \
'--privileged[Give extended privileges to this container]' \
'-t[Allocate a pseudo-tty]' \
'-u=-[Username or UID]:user:_users' \
'*-v=-[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
'--volumes-from=-[Mount volumes from the specified container]:volume: ' \
'-w=-[Working directory inside the container]:directory:_directories' \
'(-):images:__docker_images' \
'(-):command: _command_names -e' \
'*::arguments: _normal'
(diff|export)
_arguments '*:containers:__docker_containers'
;;

View file

@ -50,6 +50,14 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
for _, warning := range buildWarnings {
job.Errorf("%s\n", warning)
}
if job.EnvExists("HostConfig") {
hostConfig := runconfig.ContainerHostConfigFromJob(job)
if err := daemon.setHostConfig(container, hostConfig); err != nil {
return job.Error(err)
}
}
return engine.StatusOK
}

View file

@ -53,6 +53,7 @@ func init() {
{"build", "Build an image from a Dockerfile"},
{"commit", "Create a new image from a container's changes"},
{"cp", "Copy files/folders from a container's filesystem to the host path"},
{"create", "Create a new container"},
{"diff", "Inspect changes on a container's filesystem"},
{"events", "Get real time events from the server"},
{"exec", "Run a command in an existing container"},

131
docs/man/docker-create.1.md Normal file
View file

@ -0,0 +1,131 @@
% DOCKER(1) Docker User Manuals
% Docker Community
% JUNE 2014
# NAME
docker-create - Create a new container
# SYNOPSIS
**docker create**
[**-a**|**--attach**[=*[]*]]
[**-c**|**--cpu-shares**[=*0*]]
[**--cap-add**[=*[]*]]
[**--cap-drop**[=*[]*]]
[**--cidfile**[=*CIDFILE*]]
[**--cpuset**[=*CPUSET*]]
[**--device**[=*[]*]]
[**--dns-search**[=*[]*]]
[**--dns**[=*[]*]]
[**-e**|**--env**[=*[]*]]
[**--entrypoint**[=*ENTRYPOINT*]]
[**--env-file**[=*[]*]]
[**--expose**[=*[]*]]
[**-h**|**--hostname**[=*HOSTNAME*]]
[**-i**|**--interactive**[=*false*]]
[**--link**[=*[]*]]
[**--lxc-conf**[=*[]*]]
[**-m**|**--memory**[=*MEMORY*]]
[**--name**[=*NAME*]]
[**--net**[=*"bridge"*]]
[**-P**|**--publish-all**[=*false*]]
[**-p**|**--publish**[=*[]*]]
[**--privileged**[=*false*]]
[**-t**|**--tty**[=*false*]]
[**-u**|**--user**[=*USER*]]
[**-v**|**--volume**[=*[]*]]
[**--volumes-from**[=*[]*]]
[**-w**|**--workdir**[=*WORKDIR*]]
IMAGE [COMMAND] [ARG...]
# OPTIONS
**-a**, **--attach**=[]
Attach to STDIN, STDOUT or STDERR.
**-c**, **--cpu-shares**=0
CPU shares (relative weight)
**--cap-add**=[]
Add Linux capabilities
**--cap-drop**=[]
Drop Linux capabilities
**--cidfile**=""
Write the container ID to the file
**--cpuset**=""
CPUs in which to allow execution (0-3, 0,1)
**--device**=[]
Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)
**--dns-search**=[]
Set custom DNS search domains
**--dns**=[]
Set custom DNS servers
**-e**, **--env**=[]
Set environment variables
**--entrypoint**=""
Overwrite the default ENTRYPOINT of the image
**--env-file**=[]
Read in a line delimited file of environment variables
**--expose**=[]
Expose a port from the container without publishing it to your host
**-h**, **--hostname**=""
Container host name
**-i**, **--interactive**=*true*|*false*
Keep STDIN open even if not attached. The default is *false*.
**--link**=[]
Add link to another container in the form of name:alias
**--lxc-conf**=[]
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
**-m**, **--memory**=""
Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
**--name**=""
Assign a name to the container
**--net**="bridge"
Set the Network mode for the container
'bridge': creates a new network stack for the container on the docker bridge
'none': no networking for this container
'container:<name|id>': reuses another container network stack
'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.
**-P**, **--publish-all**=*true*|*false*
Publish all exposed ports to the host interfaces. The default is *false*.
**-p**, **--publish**=[]
Publish a container's port to the host
format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
(use 'docker port' to see the actual mapping)
**--privileged**=*true*|*false*
Give extended privileges to this container. The default is *false*.
**-t**, **--tty**=*true*|*false*
Allocate a pseudo-TTY. The default is *false*.
**-u**, **--user**=""
Username or UID
**-v**, **--volume**=[]
Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)
**--volumes-from**=[]
Mount volumes from the specified container(s)
**-w**, **--workdir**=""
Working directory inside the container
# HISTORY
August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>

View file

@ -89,10 +89,12 @@ unix://[/path/to/socket] to use.
**docker-cp(1)**
Copy files/folders from a container's filesystem to the host at path
**docker-create(1)**
Create a new container
**docker-diff(1)**
Inspect changes on a container's filesystem
**docker-events(1)**
Get real time events from the server

View file

@ -370,6 +370,63 @@ path. Paths are relative to the root of the filesystem.
Copy files/folders from the PATH to the HOSTPATH
## create
Creates a new container.
Usage: docker create [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
-a, --attach=[] Attach to STDIN, STDOUT, STDERR.
-c, --cpu-shares=0 CPU shares (relative weight)
--cidfile="" Write the container ID to the file
--dns=[] Set custom DNS servers
--dns-search=[] Set custom DNS search domains
-e, --env=[] Set environment variables
--entrypoint="" Overwrite the default entrypoint of the image
--env-file=[] Read in a line delimited file of environment variables
--expose=[] Expose a port from the container without publishing it to your host
-h, --hostname="" Container host name
-i, --interactive=false Keep `STDIN` open even if not attached
--link=[] Add link to another container (name:alias)
--lxc-conf=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
-m, --memory="" Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
--name="" Assign a name to the container
--net="bridge" Set the Network mode for the container
'bridge': creates a new network stack for the container on the docker bridge
'none': no networking for this container
'container:<name|id>': reuses another container network stack
'host': use the host network stack inside the container
-p, --publish=[] Publish a container's port to the host
format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
(use 'docker port' to see the actual mapping)
-P, --publish-all=false Publish all exposed ports to the host interfaces
--privileged=false Give extended privileges to this container
-t, --tty=false Allocate a pseudo-TTY
-u, --user="" Username or UID
-v, --volume=[] Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)
--volumes-from=[] Mount volumes from the specified container(s)
-w, --workdir="" Working directory inside the container
The `docker create` command creates a writeable container layer over
the specified image and prepares it for running the specified command.
The container ID is then printed to `STDOUT`.
This is similar to `docker run -d` except the container is never started.
You can then use the `docker start <container_id>` command to start the
container at any point.
This is useful when you want to set up a container configuration ahead
of time so that it is ready to start when you need it.
### Example:
$ sudo docker create -t -i fedora bash
6d8af538ec541dd581ebc2a24153a28329acb5268abe5ef868c1f1a261221752
$ sudo docker start -a -i 6d8af538ec5
bash-4.2#
## diff
List the changed files and directories in a container᾿s filesystem

View file

@ -0,0 +1,116 @@
package main
import (
"encoding/json"
"fmt"
"os/exec"
"testing"
"time"
)
// Make sure we can create a simple container with some args
func TestDockerCreateArgs(t *testing.T) {
runCmd := exec.Command(dockerBinary, "create", "busybox", "command", "arg1", "arg2", "arg with space")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
errorOut(err, t, out)
cleanedContainerID := stripTrailingCharacters(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
inspectOut, _, err := runCommandWithOutput(inspectCmd)
errorOut(err, t, fmt.Sprintf("out should've been a container id: %v %v", inspectOut, err))
containers := []struct {
ID string
Created time.Time
Path string
Args []string
Image string
}{}
if err := json.Unmarshal([]byte(inspectOut), &containers); err != nil {
t.Fatalf("Error inspecting the container: %s", err)
}
if len(containers) != 1 {
t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
}
c := containers[0]
if c.Path != "command" {
t.Fatalf("Unexpected container path. Expected command, received: %s", c.Path)
}
b := false
expected := []string{"arg1", "arg2", "arg with space"}
for i, arg := range expected {
if arg != c.Args[i] {
b = true
break
}
}
if len(c.Args) != len(expected) || b {
t.Fatalf("Unexpected args. Expected %v, received: %v", expected, c.Args)
}
deleteAllContainers()
logDone("create - args")
}
// Make sure we can set hostconfig options too
func TestDockerCreateHostConfig(t *testing.T) {
runCmd := exec.Command(dockerBinary, "create", "-P", "busybox", "echo")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
errorOut(err, t, out)
cleanedContainerID := stripTrailingCharacters(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
inspectOut, _, err := runCommandWithOutput(inspectCmd)
errorOut(err, t, fmt.Sprintf("out should've been a container id: %v %v", inspectOut, err))
containers := []struct {
HostConfig *struct {
PublishAllPorts bool
}
}{}
if err := json.Unmarshal([]byte(inspectOut), &containers); err != nil {
t.Fatalf("Error inspecting the container: %s", err)
}
if len(containers) != 1 {
t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
}
c := containers[0]
if c.HostConfig == nil {
t.Fatalf("Expected HostConfig, got none")
}
if !c.HostConfig.PublishAllPorts {
t.Fatalf("Expected PublishAllPorts, got false")
}
deleteAllContainers()
logDone("create - hostconfig")
}
// "test123" should be printed by docker create + start
func TestDockerCreateEchoStdout(t *testing.T) {
runCmd := exec.Command(dockerBinary, "create", "busybox", "echo", "test123")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
errorOut(err, t, out)
cleanedContainerID := stripTrailingCharacters(out)
runCmd = exec.Command(dockerBinary, "start", "-ai", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(runCmd)
errorOut(err, t, out)
if out != "test123\n" {
t.Errorf("container should've printed 'test123', got '%s'", out)
}
deleteAllContainers()
logDone("create - echo test123")
}

View file

@ -57,7 +57,27 @@ type HostConfig struct {
RestartPolicy RestartPolicy
}
// This is used by the create command when you want to set both the
// Config and the HostConfig in the same call
type ConfigAndHostConfig struct {
Config
HostConfig HostConfig
}
func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig {
return &ConfigAndHostConfig{
*config,
*hostConfig,
}
}
func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
if job.EnvExists("HostConfig") {
hostConfig := HostConfig{}
job.GetenvJson("HostConfig", &hostConfig)
return &hostConfig
}
hostConfig := &HostConfig{
ContainerIDFile: job.Getenv("ContainerIDFile"),
Privileged: job.GetenvBool("Privileged"),

View file

@ -17,18 +17,15 @@ import (
)
var (
ErrInvalidWorkingDirectory = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.")
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
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.")
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
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.")
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
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.")
)
//FIXME Only used in tests
// 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)
@ -60,8 +57,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
flCapAdd = opts.NewListOpts(nil)
flCapDrop = opts.NewListOpts(nil)
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 and print new container ID")
flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
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")
@ -77,15 +72,13 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
flCpuset = cmd.String([]string{"-cpuset"}, "", "CPUs in which to allow execution (0-3, 0,1)")
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.")
flRestartPolicy = cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)")
// For documentation purpose
_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (even in non-TTY mode). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.")
_ = 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 in the form of name:alias")
cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of environment variables")
@ -109,15 +102,15 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
}
// Validate input params
if *flDetach && flAttach.Len() > 0 {
return nil, nil, cmd, ErrConflictAttachDetach
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvalidWorkingDirectory
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, ErrConflictDetachAutoRemove
}
var (
attachStdin = flAttach.Get("stdin")
attachStdout = flAttach.Get("stdout")
attachStderr = flAttach.Get("stderr")
)
if *flNetMode != "bridge" && *flNetMode != "none" && *flHostname != "" {
return nil, nil, cmd, ErrConflictNetworkHostname
@ -140,13 +133,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
}
// 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")
}
if flAttach.Len() == 0 {
attachStdout = true
attachStderr = true
if *flStdin {
attachStdin = true
}
}
@ -254,10 +245,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
return nil, nil, cmd, err
}
if *flAutoRemove && (restartPolicy.Name == "always" || restartPolicy.Name == "on-failure") {
return nil, nil, cmd, ErrConflictRestartPolicyAndAutoRemove
}
config := &Config{
Hostname: hostname,
Domainname: domainname,
@ -270,9 +257,9 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
Memory: flMemory,
CpuShares: *flCpuShares,
Cpuset: *flCpuset,
AttachStdin: flAttach.Get("stdin"),
AttachStdout: flAttach.Get("stdout"),
AttachStderr: flAttach.Get("stderr"),
AttachStdin: attachStdin,
AttachStdout: attachStdout,
AttachStderr: attachStderr,
Env: envVariables,
Cmd: runCmd,
Image: image,