+
+6. Make sure the uninstall.sh script is executable:
+
+ $ chmod +x uninstall.sh
+
+7. Run the uninstall.sh script:
+
+ $ ./uninstall.sh
+
+
+## Learning more and acknowledgement
Use `boot2docker help` to list the full command line reference. For more
information about using SSH or SCP to access the Boot2Docker VM, see the README
@@ -324,4 +349,4 @@ at [Boot2Docker repository](https://github.com/boot2docker/boot2docker).
Thanks to Chris Jones whose [blog](http://goo.gl/Be6cCk) inspired me to redo
this page.
-Continue with the [Docker User Guide](/userguide/).
\ No newline at end of file
+Continue with the [Docker User Guide](/userguide/).
diff --git a/docs/sources/installation/oracle.md b/docs/sources/installation/oracle.md
index 6d2f782b4..e74decd9b 100644
--- a/docs/sources/installation/oracle.md
+++ b/docs/sources/installation/oracle.md
@@ -43,35 +43,35 @@ To enable the *addons* repository:
`/etc/yum.repos.d/public-yum-ol7.repo`
and set `enabled=1` in the `[ol6_addons]` or the `[ol7_addons]` stanza.
-## To install Docker:
+## Installation
1. Ensure the appropriate *addons* channel or repository has been enabled.
2. Use yum to install the Docker package:
- $ sudo yum install docker
+ $ sudo yum install docker
-## To start Docker:
+## Starting Docker
1. Now that it's installed, start the Docker daemon:
- 1. On Oracle Linux 6:
+ 1. On Oracle Linux 6:
- $ sudo service docker start
+ $ sudo service docker start
- 2. On Oracle Linux 7:
+ 2. On Oracle Linux 7:
- $ sudo systemctl start docker.service
+ $ sudo systemctl start docker.service
2. If you want the Docker daemon to start automatically at boot:
- 1. On Oracle Linux 6:
+ 1. On Oracle Linux 6:
- $ sudo chkconfig docker on
+ $ sudo chkconfig docker on
- 2. On Oracle Linux 7:
+ 2. On Oracle Linux 7:
- $ sudo systemctl enable docker.service
+ $ sudo systemctl enable docker.service
**Done!**
@@ -99,6 +99,20 @@ To enable btrfs support on Oracle Linux:
You can now continue with the [Docker User Guide](/userguide/).
+## Uninstallation
+
+To uninstall the Docker package:
+
+ $ sudo yum -y remove docker
+
+The above command will not remove images, containers, volumes, or user created
+configuration files on your host. If you wish to delete all images, containers,
+and volumes run the following command:
+
+ $ rm -rf /var/lib/docker
+
+You must delete the user created configuration files manually.
+
## Known issues
### Docker unmounts btrfs filesystem on shutdown
@@ -110,7 +124,7 @@ service.
On Oracle Linux 7, you can use a `systemd.mount` definition and modify the
Docker `systemd.service` to depend on the btrfs mount defined in systemd.
-### SElinux Support on Oracle Linux 7
+### SElinux support on Oracle Linux 7
SElinux must be set to `Permissive` or `Disabled` in `/etc/sysconfig/selinux` to
use the btrfs storage engine on Oracle Linux 7.
diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md
index 58b2316c6..9b1734692 100644
--- a/docs/sources/installation/rhel.md
+++ b/docs/sources/installation/rhel.md
@@ -7,7 +7,7 @@ page_keywords: Docker, Docker documentation, requirements, linux, rhel
Docker is supported on the following versions of RHEL:
- [*Red Hat Enterprise Linux 7 (64-bit)*](#red-hat-enterprise-linux-7-installation)
-- [*Red Hat Enterprise Linux 6.5 (64-bit)*](#red-hat-enterprise-linux-6.5-installation) or later
+- [*Red Hat Enterprise Linux 6.6 (64-bit)*](#red-hat-enterprise-linux-66-installation) or later
## Kernel support
@@ -16,7 +16,9 @@ running on kernels shipped by the distribution. There are kernel changes which
will cause issues if one decides to step outside that box and run
non-distribution kernel packages.
-## Red Hat Enterprise Linux 7 Installation
+## Red Hat Enterprise Linux 7
+
+### Installation
**Red Hat Enterprise Linux 7 (64 bit)** has [shipped with
Docker](https://access.redhat.com/site/products/red-hat-enterprise-linux/docker-and-containers).
@@ -41,14 +43,28 @@ Portal](https://access.redhat.com/).
Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon).
-## Red Hat Enterprise Linux 6.5 Installation
+### Uninstallation
+
+To uninstall the Docker package:
+
+ $ sudo yum -y remove docker
+
+The above command will not remove images, containers, volumes, or user created
+configuration files on your host. If you wish to delete all images, containers,
+and volumes run the following command:
+
+ $ rm -rf /var/lib/docker
+
+You must delete the user created configuration files manually.
+
+## Red Hat Enterprise Linux 6.6
You will need **64 bit** [RHEL
-6.5](https://access.redhat.com/site/articles/3078#RHEL6) or later, with
-a RHEL 6 kernel version 2.6.32-431 or higher as this has specific kernel
-fixes to allow Docker to work.
+6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with
+a RHEL 6 kernel version 2.6.32-504.16.2 or higher as this has specific kernel
+fixes to allow Docker to work. Related issues: [#9856](https://github.com/docker/docker/issues/9856).
-Docker is available for **RHEL6.5** on EPEL. Please note that
+Docker is available for **RHEL6.6** on EPEL. Please note that
this package is part of [Extra Packages for Enterprise Linux
(EPEL)](https://fedoraproject.org/wiki/EPEL), a community effort to
create and maintain additional packages for the RHEL distribution.
@@ -66,7 +82,7 @@ non-distro kernel packages.
> vulnerabilities and severe bugs (such as those found in kernel 2.6.32)
> are fixed.
-## Installation
+### Installation
Firstly, you need to install the EPEL repository. Please follow the
[EPEL installation
@@ -90,6 +106,20 @@ To update the `docker-io` package
Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon).
+### Uninstallation
+
+To uninstall the Docker package:
+
+ $ sudo yum -y remove docker-io
+
+The above command will not remove images, containers, volumes, or user created
+configuration files on your host. If you wish to delete all images, containers,
+and volumes run the following command:
+
+ $ rm -rf /var/lib/docker
+
+You must delete the user created configuration files manually.
+
## Starting the Docker daemon
Now that it's installed, let's start the Docker daemon.
@@ -118,7 +148,6 @@ If you need to add an HTTP Proxy, set a different directory or partition for the
Docker runtime files, or make other customizations, read our Systemd article to
learn how to [customize your Systemd Docker daemon options](/articles/systemd/).
-
## Issues?
If you have any issues - please report them directly in the
diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md
index 85a37d768..652edc9fd 100644
--- a/docs/sources/installation/ubuntulinux.md
+++ b/docs/sources/installation/ubuntulinux.md
@@ -28,8 +28,8 @@ and frequently panic under certain conditions.
To check your current kernel version, open a terminal and use `uname -r` to display
your kernel version:
- $ uname -r
- 3.11.0-15-generic
+ $ uname -r
+ 3.11.0-15-generic
>**Caution** Some Ubuntu OS versions **require a version higher than 3.10** to
>run Docker, see the prerequisites on this page that apply to your Ubuntu
@@ -72,17 +72,17 @@ To upgrade your kernel and install the additional packages, do the following:
2. Update your package manager.
- $ sudo apt-get update
+ $ sudo apt-get update
3. Install both the required and optional packages.
- $ sudo apt-get install linux-image-generic-lts-trusty
+ $ sudo apt-get install linux-image-generic-lts-trusty
- Depending on your environment, you may install more as described in the preceding table.
+ Depending on your environment, you may install more as described in the preceding table.
4. Reboot your host.
- $ sudo reboot
+ $ sudo reboot
5. After your system reboots, go ahead and [install Docker](#installing-docker-on-ubuntu).
@@ -92,35 +92,42 @@ To upgrade your kernel and install the additional packages, do the following:
Docker uses AUFS as the default storage backend. If you don't have this
prerequisite installed, Docker's installation process adds it.
-##Installing Docker on Ubuntu
+##Installation
-Make sure you have intalled the prerequisites for your Ubuntu version. Then,
+Make sure you have installed the prerequisites for your Ubuntu version. Then,
install Docker using the following:
1. Log into your Ubuntu installation as a user with `sudo` privileges.
2. Verify that you have `wget` installed.
- $ which wget
+ $ which wget
- If `wget` isn't installed, install it after updating your manager:
+ If `wget` isn't installed, install it after updating your manager:
- $ sudo apt-get update $ sudo apt-get install wget
+ $ sudo apt-get update
+ $ sudo apt-get install wget
3. Get the latest Docker package.
- $ wget -qO- https://get.docker.com/ | sh
+ $ wget -qO- https://get.docker.com/ | sh
- The system prompts you for your `sudo` password. Then, it downloads and
- installs Docker and its dependencies.
+ The system prompts you for your `sudo` password. Then, it downloads and
+ installs Docker and its dependencies.
+>**Note**: If your company is behind a filtering proxy, you may find that the
+>`apt-key`
+>command fails for the Docker repo during installation. To work around this,
+>add the key directly using the following:
+>
+> $ wget -qO- https://get.docker.com/gpg | sudo apt-key add -
4. Verify `docker` is installed correctly.
- $ sudo docker run hello-world
+ $ sudo docker run hello-world
- This command downloads a test image and runs it in a container.
+ This command downloads a test image and runs it in a container.
-## Optional Configurations for Docker on Ubuntu
+## Optional configurations for Docker on Ubuntu
This section contains optional procedures for configuring your Ubuntu to work
better with Docker.
@@ -130,7 +137,7 @@ better with Docker.
* [Enable UFW forwarding](#enable-ufw-forwarding)
* [Configure a DNS server for use by Docker](#configure-a-dns-server-for-docker)
-### Create a docker group
+### Create a Docker group
The `docker` daemon binds to a Unix socket instead of a TCP port. By default
that Unix socket is owned by the user `root` and other users can access it with
@@ -148,19 +155,19 @@ To create the `docker` group and add your user:
1. Log into Ubuntu as a user with `sudo` privileges.
- This procedure assumes you log in as the `ubuntu` user.
+ This procedure assumes you log in as the `ubuntu` user.
3. Create the `docker` group and add your user.
- $ sudo usermod -aG docker ubuntu
+ $ sudo usermod -aG docker ubuntu
3. Log out and log back in.
- This ensures your user is running with the correct permissions.
+ This ensures your user is running with the correct permissions.
4. Verify your work by running `docker` without `sudo`.
- $ docker run hello-world
+ $ docker run hello-world
### Adjust memory and swap accounting
@@ -180,13 +187,13 @@ following.
3. Set the `GRUB_CMDLINE_LINUX` value as follows:
- GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
+ GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
4. Save and close the file.
5. Update GRUB.
- $ sudo update-grub
+ $ sudo update-grub
6. Reboot your system.
@@ -209,25 +216,25 @@ To configure UFW and allow incoming connections on the Docker port:
2. Verify that UFW is installed and enabled.
- $ sudo ufw status
+ $ sudo ufw status
3. Open the `/etc/default/ufw` file for editing.
- $ sudo nano /etc/default/ufw
+ $ sudo nano /etc/default/ufw
4. Set the `DEFAULT_FORWARD_POLICY` policy to:
- DEFAULT_FORWARD_POLICY="ACCEPT"
+ DEFAULT_FORWARD_POLICY="ACCEPT"
5. Save and close the file.
6. Reload UFW to use the new setting.
- $ sudo ufw reload
+ $ sudo ufw reload
7. Allow incoming connections on the Docker port.
- $ sudo ufw allow 2375/tcp
+ $ sudo ufw allow 2375/tcp
### Configure a DNS server for use by Docker
@@ -246,7 +253,7 @@ The warning occurs because Docker containers can't use the local DNS nameserver.
Instead, Docker defaults to using an external nameserver.
To avoid this warning, you can specify a DNS server for use by Docker
-containers. Or, you can disable `dnsmasq` in NetworkManager. Though, disabiling
+containers. Or, you can disable `dnsmasq` in NetworkManager. Though, disabling
`dnsmasq` might make DNS resolution slower on some networks.
To specify a DNS server for use by Docker:
@@ -255,25 +262,25 @@ To specify a DNS server for use by Docker:
2. Open the `/etc/default/docker` file for editing.
- $ sudo nano /etc/default/docker
+ $ sudo nano /etc/default/docker
3. Add a setting for Docker.
- DOCKER_OPTS="--dns 8.8.8.8"
+ DOCKER_OPTS="--dns 8.8.8.8"
Replace `8.8.8.8` with a local DNS server such as `192.168.1.1`. You can also
specify multiple DNS servers. Separated them with spaces, for example:
- --dns 8.8.8.8 --dns 192.168.1.1
+ --dns 8.8.8.8 --dns 192.168.1.1
- >**Warning**: If you're doing this on a laptop which connects to various
- >networks, make sure to choose a public DNS server.
+ >**Warning**: If you're doing this on a laptop which connects to various
+ >networks, make sure to choose a public DNS server.
4. Save and close the file.
5. Restart the Docker daemon.
- $ sudo restart docker
+ $ sudo restart docker
@@ -282,24 +289,41 @@ To specify a DNS server for use by Docker:
**Or, as an alternative to the previous procedure,** disable `dnsmasq` in
NetworkManager (this might slow your network).
-1. Open the `/etc/default/docker` file for editing.
+1. Open the `/etc/NetworkManager/NetworkManager.conf` file for editing.
- $ sudo nano /etc/NetworkManager/NetworkManager.conf
+ $ sudo nano /etc/NetworkManager/NetworkManager.conf
2. Comment out the `dns=dsnmasq` line:
- dns=dnsmasq
+ dns=dnsmasq
3. Save and close the file.
4. Restart both the NetworkManager and Docker.
- $ sudo restart network-manager $ sudo restart docker
+ $ sudo restart network-manager $ sudo restart docker
## Upgrade Docker
-To install the latest version of Docker, use the standard `-N` flag with `wget`:
+To install the latest version of Docker with `wget`:
- $ wget -N https://get.docker.com/ | sh
+ $ wget -qO- https://get.docker.com/ | sh
+## Uninstallation
+
+To uninstall the Docker package:
+
+ $ sudo apt-get purge lxc-docker
+
+To uninstall the Docker package and dependencies that are no longer needed:
+
+ $ sudo apt-get autoremove --purge lxc-docker
+
+The above commands will not remove images, containers, volumes, or user created
+configuration files on your host. If you wish to delete all images, containers,
+and volumes run the following command:
+
+ $ rm -rf /var/lib/docker
+
+You must delete the user created configuration files manually.
diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md
index 95f55afeb..b5a148417 100644
--- a/docs/sources/installation/windows.md
+++ b/docs/sources/installation/windows.md
@@ -20,6 +20,8 @@ Although you will be using Windows Docker client, the docker engine hosting the
containers will still be running on Linux. Until the Docker engine for Windows
is developed, you can launch only Linux containers from your Windows machine.
+
+
## Demonstration
@@ -28,7 +30,7 @@ is developed, you can launch only Linux containers from your Windows machine.
1. Download the latest release of the
[Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest).
-2. Run the installer, which will install Docker Client or Windows, VirtualBox,
+2. Run the installer, which will install Docker Client for Windows, VirtualBox,
Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker
management tool.

@@ -57,7 +59,7 @@ Let's try the `hello-world` example image. Run
This should download the very small `hello-world` image and print a
`Hello from Docker.` message.
-## Using docker from Windows Command Line Prompt (cmd.exe)
+## Using Docker from Windows Command Line Prompt (cmd.exe)
Launch a Windows Command Line Prompt (cmd.exe).
@@ -65,7 +67,7 @@ Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to
include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%`
environment variable by running:
- set PATH=%PATH%;"c:\Program Files (x86)\Git\bin"
+ set PATH=%PATH%;"c:\Program Files (x86)\Git\bin"
and then we can run the `boot2docker start` command to start the Boot2Docker VM.
(Run `boot2docker init` command if you get an error saying machine does not
@@ -75,11 +77,11 @@ to your console window and you are ready to run docker commands such as

-## Using docker from PowerShell
+## Using Docker from PowerShell
Launch a PowerShell window, then you need to add `ssh.exe` to your PATH:
- $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin"
+ $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin"
and after running `boot2docker start` command it will print PowerShell commands
to set the environment variables to connect Docker running inside VM. Run these
@@ -148,6 +150,12 @@ You can do this with
- then click: "Save Private Key".
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
+## Uninstallation
+
+You can uninstall Boot2Docker using Window's standard process for removing programs.
+This process does not remove the `docker-install.exe` file. You must delete that file
+yourself.
+
## References
If you have Docker hosts running and if you don't wish to do a
diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md
index 9c9995972..060428ecc 100644
--- a/docs/sources/introduction/understanding-docker.md
+++ b/docs/sources/introduction/understanding-docker.md
@@ -109,7 +109,7 @@ Docker containers. Docker provides a simple way to build new images or update ex
images, or you can download Docker images that other people have already created.
Docker images are the **build** component of Docker.
-#### Docker Registries
+#### Docker registries
Docker registries hold images. These are public or private stores from which you upload
or download images. The public Docker registry is called
[Docker Hub](http://hub.docker.com). It provides a huge collection of existing
@@ -135,7 +135,7 @@ So far, we've learned that:
Let's look at how these elements combine together to make Docker work.
-### How does a Docker Image work?
+### How does a Docker image work?
We've already seen that Docker images are read-only templates from which Docker
containers are launched. Each image consists of a series of layers. Docker
makes use of [union file systems](http://en.wikipedia.org/wiki/UnionFS) to
@@ -198,7 +198,7 @@ then run.
Either by using the `docker` binary or via the API, the Docker client tells the Docker
daemon to run a container.
- $ sudo docker run -i -t ubuntu /bin/bash
+ $ docker run -i -t ubuntu /bin/bash
Let's break down this command. The Docker client is launched using the `docker`
binary with the `run` option telling it to launch a new container. The bare
@@ -280,7 +280,7 @@ BSD Jails or Solaris Zones.
### Installing Docker
Visit the [installation section](/installation/#installation).
-### The Docker User Guide
+### The Docker user guide
[Learn Docker in depth](/userguide/).
diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md
index df5756d9d..7ee7a86cb 100644
--- a/docs/sources/project/advanced-contributing.md
+++ b/docs/sources/project/advanced-contributing.md
@@ -67,7 +67,7 @@ The following provides greater detail on the process:
The design proposals are all online in our GitHub pull requests.
+ 3Akind%2Fproposal" target="_blank">all online in our GitHub pull requests.
3. Talk to the community about your idea.
@@ -89,7 +89,7 @@ The following provides greater detail on the process:
This is a Markdown file that describes your idea. Your proposal
should include information like:
- * Why is this changed needed or what are the use cases?
+ * Why is this change needed or what are the use cases?
* What are the requirements this change should meet?
* What are some ways to design/implement this feature?
* Which design/implementation do you think is best and why?
@@ -137,3 +137,16 @@ The following provides greater detail on the process:
14. Acceptance and merge!
+## About the advanced process
+
+Docker is a large project. Our core team gets a great many design proposals.
+Design proposal discussions can span days, weeks, and longer. The number of comments can reach the 100s.
+In that situation, following the discussion flow and the decisions reached is crucial.
+
+Making a pull request with a design proposal simplifies this process:
+* you can leave comments on specific design proposal line
+* replies around line are easy to track
+* as a proposal changes and is updated, pages reset as line items resolve
+* Github maintains the entire history
+
+While proposals in pull requests do not end up merged into a master repository, they provide a convenient tool for managing the design process.
diff --git a/docs/sources/project/coding-style.md b/docs/sources/project/coding-style.md
index e5b6f5fe9..57f638936 100644
--- a/docs/sources/project/coding-style.md
+++ b/docs/sources/project/coding-style.md
@@ -1,13 +1,13 @@
-page_title: Coding Style Checklist
+page_title: Coding style checklist
page_description: List of guidelines for coding Docker contributions
page_keywords: change, commit, squash, request, pull request, test, unit test, integration tests, Go, gofmt, LGTM
-# Coding Style Checklist
+# Coding style checklist
This checklist summarizes the material you experienced working through [make a
code contribution](/project/make-a-contribution) and [advanced
-contributing](/project/advanced-contributing). The checklist applies to code
-that is program code or code that is documentation code.
+contributing](/project/advanced-contributing). The checklist applies to both
+program code and documentation code.
## Change and commit code
diff --git a/docs/sources/project/create-pr.md b/docs/sources/project/create-pr.md
index 197aee849..613ab6911 100644
--- a/docs/sources/project/create-pr.md
+++ b/docs/sources/project/create-pr.md
@@ -11,7 +11,7 @@ repository into the `docker/docker` repository.
You can see the
list of active pull requests to Docker on GitHub.
-## Check Your Work
+## Check your work
Before you create a pull request, check your work.
@@ -22,7 +22,7 @@ Before you create a pull request, check your work.
2. Checkout your feature branch.
$ git checkout 11038-fix-rhel-link
- Already on '11038-fix-rhel-link'
+ Switched to branch '11038-fix-rhel-link'
3. Run the full test suite on your branch.
@@ -41,7 +41,11 @@ Before you create a pull request, check your work.
Always rebase and squash your commits before making a pull request.
-1. Fetch any of the last minute changes from `docker/docker`.
+1. Checkout your feature branch in your local `docker-fork` repository.
+
+ This is the branch associated with your request.
+
+2. Fetch any last minute changes from `docker/docker`.
$ git fetch upstream master
From github.com:docker/docker
@@ -56,28 +60,28 @@ Always rebase and squash your commits before making a pull request.
pick 1a79f55 Tweak some of the other text for grammar
pick 53e4983 Fix a link
pick 3ce07bb Add a new line about RHEL
-
- If you run into trouble, `git --rebase abort` removes any changes and gets
- you back to where you started.
-4. Squash the `pick` keyword with `squash` on all but the first commit.
+5. Replace the `pick` keyword with `squash` on all but the first commit.
pick 1a79f55 Tweak some of the other text for grammar
squash 53e4983 Fix a link
squash 3ce07bb Add a new line about RHEL
- After closing the file, `git` opens your editor again to edit the commit
- message.
+ After you save the changes and quit from the editor, git starts
+ the rebase, reporting the progress along the way. Sometimes
+ your changes can conflict with the work of others. If git
+ encounters a conflict, it stops the rebase, and prints guidance
+ for how to correct the conflict.
-5. Edit and save your commit message.
+6. Edit and save your commit message.
`git commit -s`
- Make sure your message includes
+
+3. Commit the change.
$ git commit --amend
Git opens an editor containing your last commit message.
-3. Adjust your last comment to reflect this new change.
+4. Adjust your last comment to reflect this new change.
Added a new sentence per Anaud's suggestion
@@ -72,15 +80,17 @@ To update your existing pull request:
# modified: docs/sources/installation/mac.md
# modified: docs/sources/installation/rhel.md
-4. Push to your origin.
+5. Force push the change to your origin.
- $ git push origin
+ The command syntax is:
-5. Open your browser to your pull request on GitHub.
+ git push -f origin
+
+6. Open your browser to your pull request on GitHub.
You should see your pull request now contains your newly pushed code.
-6. Add a comment to your pull request.
+7. Add a comment to your pull request.
GitHub only notifies PR participants when you comment. For example, you can
mention that you updated your PR. Your comment alerts the maintainers that
diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md
index 637eef6f5..60a59b615 100644
--- a/docs/sources/project/set-up-dev-env.md
+++ b/docs/sources/project/set-up-dev-env.md
@@ -7,7 +7,7 @@ page_keywords: development, inception, container, image Dockerfile, dependencies
In this section, you learn to develop like a member of Docker's core team.
The `docker` repository includes a `Dockerfile` at its root. This file defines
Docker's development environment. The `Dockerfile` lists the environment's
-dependencies: system libraries and binaries, go environment, go dependencies,
+dependencies: system libraries and binaries, Go environment, Go dependencies,
etc.
Docker's development environment is itself, ultimately a Docker container.
@@ -15,20 +15,19 @@ You use the `docker` repository and its `Dockerfile` to create a Docker image,
run a Docker container, and develop code in the container. Docker itself builds,
tests, and releases new Docker versions using this container.
-If you followed the procedures that
-set up the prerequisites, you should have a fork of the `docker/docker`
+If you followed the procedures that
+set up Git for contributing, you should have a fork of the `docker/docker`
repository. You also created a branch called `dry-run-test`. In this section,
you continue working with your fork on this branch.
## Clean your host of Docker artifacts
-Docker developers run the latest stable release of the Docker software; Or
-Boot2docker and Docker if their machine is Mac OS X. They clean their local
+Docker developers run the latest stable release of the Docker software (with Boot2Docker if their machine is Mac OS X). They clean their local
hosts of unnecessary Docker artifacts such as stopped containers or unused
-images. Cleaning unnecessary artifacts isn't strictly necessary but it is
+images. Cleaning unnecessary artifacts isn't strictly necessary, but it is
good practice, so it is included here.
-To remove unnecessary artifacts.
+To remove unnecessary artifacts,
1. Verify that you have no unnecessary containers running on your host.
@@ -75,9 +74,9 @@ To remove unnecessary artifacts.
$ docker rmi -f $(docker images -q -a -f dangling=true)
- This command uses `docker images` to lists all images (`-a` flag) by numeric
- IDs (`-q` flag) and filter them to find dangling images (`-f
- dangling=true`). Then, the `docker rmi` command forcibly (`-f` flag) removes
+ This command uses `docker images` to list all images (`-a` flag) by numeric
+ IDs (`-q` flag) and filter them to find dangling images (`-f dangling=true`).
+ Then, the `docker rmi` command forcibly (`-f` flag) removes
the resulting list. To remove just one image, use the `docker rmi ID`
command.
@@ -97,10 +96,17 @@ environment.
3. Change into the root of your forked repository.
$ cd ~/repos/docker-fork
+
+ If you are following along with this guide, you created a `dry-run-test`
+ branch when you set up Git for
+ contributing.
4. Ensure you are on your `dry-run-test` branch.
$ git checkout dry-run-test
+
+ If you get a message that the branch doesn't exist, add the `-b` flag (git checkout -b dry-run-test) so the
+ command both creates the branch and checks it out.
5. Compile your development environment container into an image.
@@ -194,7 +200,7 @@ build and run a `docker` binary in your container.

- Mac OSX users, make sure you run `eval "$(boot2docker shellinit)"` in any new
+ Mac OS X users, make sure you run `eval "$(boot2docker shellinit)"` in any new
terminals.
2. In a terminal, create a new container from your `dry-run-test` image.
@@ -203,9 +209,9 @@ build and run a `docker` binary in your container.
root@5f8630b873fe:/go/src/github.com/docker/docker#
The command creates a container from your `dry-run-test` image. It opens an
- interactive terminal (`-ti`) running a `/bin/bash shell`. The
+ interactive terminal (`-ti`) running a `/bin/bash` shell. The
`--privileged` flag gives the container access to kernel features and device
- access. It is this flag that allows you to run a container in a container.
+ access. This flag allows you to run a container in a container.
Finally, the `-rm` flag instructs Docker to remove the container when you
exit the `/bin/bash` shell.
@@ -232,7 +238,8 @@ build and run a `docker` binary in your container.
You will create one in the next steps.
-4. From the `/go/src/github.com/docker/docker` directory make a `docker` binary with the `make.sh` script.
+4. From the `/go/src/github.com/docker/docker` directory make a `docker` binary
+with the `make.sh` script.
root@5f8630b873fe:/go/src/github.com/docker/docker# hack/make.sh binary
@@ -244,10 +251,9 @@ build and run a `docker` binary in your container.
When the command completes successfully, you should see the following
output:
- ---> Making bundle: ubuntu (in bundles/1.5.0-dev/ubuntu)
- Created package {:path=>"lxc-docker-1.5.0-dev_1.5.0~dev~git20150223.181106.0.1ab0d23_amd64.deb"}
- Created package {:path=>"lxc-docker_1.5.0~dev~git20150223.181106.0.1ab0d23_amd64.deb"}
-
+ ---> Making bundle: binary (in bundles/1.5.0-dev/binary)
+ Created binary: /go/src/github.com/docker/docker/bundles/1.5.0-dev/binary/docker-1.5.0-dev
+
5. List all the contents of the `binary` directory.
root@5f8630b873fe:/go/src/github.com/docker/docker# ls bundles/1.5.0-dev/binary/
@@ -266,15 +272,15 @@ build and run a `docker` binary in your container.
root@5f8630b873fe:/go/src/github.com/docker/docker# docker --version
Docker version 1.5.0-dev, build 6e728fb
- Inside the container you are running a development version. This is version
- on the current branch it reflects the value of the `VERSION` file at the
+ Inside the container you are running a development version. This is the version
+ on the current branch. It reflects the value of the `VERSION` file at the
root of your `docker-fork` repository.
8. Start a `docker` daemon running inside your container.
root@5f8630b873fe:/go/src/github.com/docker/docker# docker -dD
- The `-dD` flag starts the daemon in debug mode; You'll find this useful
+ The `-dD` flag starts the daemon in debug mode. You'll find this useful
when debugging your code.
9. Bring up one of the terminals on your local host.
@@ -357,7 +363,8 @@ container.
Your location will be different because it reflects your environment.
-3. Create a container using `dry-run-test` but this time mount your repository onto the `/go` directory inside the container.
+3. Create a container using `dry-run-test`, but this time, mount your repository
+onto the `/go` directory inside the container.
$ docker run --privileged --rm -ti -v `pwd`:/go/src/github.com/docker/docker dry-run-test /bin/bash
@@ -375,7 +382,7 @@ container.
$ cd ~/repos/docker-fork/
-6. Create a fresh binary but this time use the `make` command.
+6. Create a fresh binary, but this time, use the `make` command.
$ make BINDDIR=. binary
@@ -408,4 +415,5 @@ container.
Congratulations, you have successfully achieved Docker inception. At this point,
you've set up your development environment and verified almost all the essential
processes you need to contribute. Of course, before you start contributing,
-[you'll need to learn one more piece of the development environment, the test framework](/project/test-and-docs/).
+[you'll need to learn one more piece of the development environment, the test
+framework](/project/test-and-docs/).
diff --git a/docs/sources/project/set-up-git.md b/docs/sources/project/set-up-git.md
index 2292d93b3..d67ff817c 100644
--- a/docs/sources/project/set-up-git.md
+++ b/docs/sources/project/set-up-git.md
@@ -46,9 +46,12 @@ target="_blank">docker/docker repository.
that instead. You'll need to convert what you see in the guide to what is
appropriate to your tool.
-5. Open a terminal window on your local host and change to your home directory.
+5. Open a terminal window on your local host and change to your home directory.
$ cd ~
+
+ In Windows, you'll work in your Boot2Docker window instead of Powershell or
+ a `cmd` window.
6. Create a `repos` directory.
@@ -88,7 +91,7 @@ contributions through pseudonyms.
As you change code in your fork, you'll want to keep it in sync with the changes
others make in the `docker/docker` repository. To make syncing easier, you'll
also add a _remote_ called `upstream` that points to `docker/docker`. A remote
-is just another a project version hosted on the internet or network.
+is just another project version hosted on the internet or network.
To configure your username, email, and add a remote:
@@ -134,12 +137,12 @@ To configure your username, email, and add a remote:
## Create and push a branch
-As you change code in your fork, you make your changes on a repository branch.
+As you change code in your fork, make your changes on a repository branch.
The branch name should reflect what you are working on. In this section, you
create a branch, make a change, and push it up to your fork.
This branch is just for testing your config for this guide. The changes are part
-of a dry run so the branch name is going to be dry-run-test. To create an push
+of a dry run, so the branch name will be dry-run-test. To create and push
the branch to your fork on GitHub:
1. Open a terminal and go to the root of your `docker-fork`.
@@ -171,7 +174,7 @@ the branch to your fork on GitHub:
You can use any text editor you are comfortable with.
-6. Close and save the file.
+6. Save and close the file.
7. Check the status of your branch.
diff --git a/docs/sources/project/software-req-win.md b/docs/sources/project/software-req-win.md
new file mode 100644
index 000000000..38cd73d10
--- /dev/null
+++ b/docs/sources/project/software-req-win.md
@@ -0,0 +1,258 @@
+page_title: Set up for development on Windows
+page_description: How to set up a server to test Docker Windows client
+page_keywords: development, inception, container, image Dockerfile, dependencies, Go, artifacts, windows
+
+
+# Get the required software for Windows
+
+This page explains how to get the software you need to use a a Windows Server
+2012 or Windows 8 machine for Docker development. Before you begin contributing
+you must have:
+
+- a GitHub account
+- Git for Windows (msysGit)
+- TDM-GCC, a compiler suite for Windows
+- MinGW (tar and xz)
+- Go language
+
+> **Note**: This installation procedure refers to the `C:\` drive. If you system's main drive
+is `D:\` you'll need to substitute that in where appropriate in these
+instructions.
+
+### Get a GitHub account
+
+To contribute to the Docker project, you will need a GitHub account. A free account is
+fine. All the Docker project repositories are public and visible to everyone.
+
+You should also have some experience using both the GitHub application and `git`
+on the command line.
+
+## Install Git for Windows
+
+Git for Windows includes several tools including msysGit, which is a build
+environment. The environment contains the tools you need for development such as
+Git and a Git Bash shell.
+
+1. Browse to the [Git for Windows](https://msysgit.github.io/) download page.
+
+2. Click **Download**.
+
+ Windows prompts you to save the file to your machine.
+
+3. Run the saved file.
+
+ The system displays the **Git Setup** wizard.
+
+4. Click the **Next** button to move through the wizard and accept all the defaults.
+
+5. Click **Finish** when you are done.
+
+## Installing TDM-GCC
+
+TDM-GCC is a compiler suite for Windows. You'll use this suite to compile the
+Docker Go code as you develop.
+
+1. Browse to
+ [tdm-gcc download page](http://tdm-gcc.tdragon.net/download).
+
+2. Click on the latest 64-bit version of the package.
+
+ Windows prompts you to save the file to your machine
+
+3. Set up the suite by running the downloaded file.
+
+ The system opens the **TDM-GCC Setup** wizard.
+
+4. Click **Create**.
+
+5. Click the **Next** button to move through the wizard and accept all the defaults.
+
+6. Click **Finish** when you are done.
+
+
+## Installing MinGW (tar and xz)
+
+MinGW is a minimalist port of the GNU Compiler Collection (GCC). In this
+procedure, you first download and install the MinGW installation manager. Then,
+you use the manager to install the `tar` and `xz` tools from the collection.
+
+1. Browse to MinGW
+ [SourceForge](http://sourceforge.net/projects/mingw/).
+
+2. Click **Download**.
+
+ Windows prompts you to save the file to your machine
+
+3. Run the downloaded file.
+
+ The system opens the **MinGW Installation Manager Setup Tool**
+
+4. Choose **Install** install the MinGW Installation Manager.
+
+5. Press **Continue**.
+
+ The system installs and then opens the MinGW Installation Manager.
+
+6. Press **Continue** after the install completes to open the manager.
+
+7. Select **All Packages > MSYS Base System** from the left hand menu.
+
+ The system displays the available packages.
+
+8. Click on the the **msys-tar bin** package and choose **Mark for Installation**.
+
+9. Click on the **msys-xz bin** package and choose **Mark for Installation**.
+
+10. Select **Installation > Apply Changes**, to install the selected packages.
+
+ The system displays the **Schedule of Pending Actions Dialog**.
+
+ 
+
+11. Press **Apply**
+
+ MingGW installs the packages for you.
+
+12. Close the dialog and the MinGW Installation Manager.
+
+
+## Set up your environment variables
+
+You'll need to add the compiler to your `Path` environment variable.
+
+1. Open the **Control Panel**.
+
+2. Choose **System and Security > System**.
+
+3. Click the **Advanced system settings** link in the sidebar.
+
+ The system opens the **System Properties** dialog.
+
+3. Select the **Advanced** tab.
+
+4. Click **Environment Variables**.
+
+ The system opens the **Environment Variables dialog** dialog.
+
+5. Locate the **System variables** area and scroll to the **Path**
+ variable.
+
+ 
+
+6. Click **Edit** to edit the variable (you can also double-click it).
+
+ The system opens the **Edit System Variable** dialog.
+
+7. Make sure the `Path` includes `C:\TDM-GCC64\bin`
+
+ 
+
+ If you don't see `C:\TDM-GCC64\bin`, add it.
+
+8. Press **OK** to close this dialog.
+
+9. Press **OK** twice to close out of the remaining dialogs.
+
+## Install Go and cross-compile it
+
+In this section, you install the Go language. Then, you build the source so that it can cross-compile for `linux/amd64` architectures.
+
+1. Open [Go Language download](http://golang.org/dl/) page in your browser.
+
+2. Locate and click the latest `.msi` installer.
+
+ The system prompts you to save the file.
+
+3. Run the installer.
+
+ The system opens the **Go Programming Language Setup** dialog.
+
+4. Select all the defaults to install.
+
+5. Press **Finish** to close the installation dialog.
+
+6. Start a command prompt.
+
+7. Change to the Go `src` directory.
+
+ cd c:\Go\src
+
+8. Set the following Go variables
+
+ c:\Go\src> set GOOS=linux
+ c:\Go\src> set GOARCH=amd64
+
+9. Compile the source.
+
+ c:\Go\src> make.bat
+
+ Compiling the source also adds a number of variables to your Windows environment.
+
+## Get the Docker repository
+
+In this step, you start a Git `bash` terminal and get the Docker source code from
+Github.
+
+1. Locate the **Git Bash** program and start it.
+
+ Recall that **Git Bash** came with the Git for Windows installation. **Git
+ Bash** just as it sounds allows you to run a Bash terminal on Windows.
+
+ 
+
+2. Change to the root directory.
+
+ $ cd /c/
+
+3. Make a `gopath` directory.
+
+ $ mkdir gopath
+
+4. Go get the `docker/docker` repository.
+
+ $ go.exe get github.com/docker/docker package github.com/docker/docker
+ imports github.com/docker/docker
+ imports github.com/docker/docker: no buildable Go source files in C:\gopath\src\github.com\docker\docker
+
+ In the next steps, you create environment variables for you Go paths.
+
+5. Open the **Control Panel** on your system.
+
+6. Choose **System and Security > System**.
+
+7. Click the **Advanced system settings** link in the sidebar.
+
+ The system opens the **System Properties** dialog.
+
+8. Select the **Advanced** tab.
+
+9. Click **Environment Variables**.
+
+ The system opens the **Environment Variables dialog** dialog.
+
+10. Locate the **System variables** area and scroll to the **Path**
+ variable.
+
+11. Click **New**.
+
+ Now you are going to create some new variables. These paths you'll create in the next procedure; but you can set them now.
+
+12. Enter `GOPATH` for the **Variable Name**.
+
+13. For the **Variable Value** enter the following:
+
+ C:\gopath;C:\gopath\src\github.com\docker\docker\vendor
+
+
+14. Press **OK** to close this dialog.
+
+ The system adds `GOPATH` to the list of **System Variables**.
+
+15. Press **OK** twice to close out of the remaining dialogs.
+
+
+## Where to go next
+
+In the next section, you'll [learn how to set up and configure Git for
+contributing to Docker](/project/set-up-git/).
\ No newline at end of file
diff --git a/docs/sources/project/software-required.md b/docs/sources/project/software-required.md
index 476cbbc2c..15b9a6935 100644
--- a/docs/sources/project/software-required.md
+++ b/docs/sources/project/software-required.md
@@ -2,9 +2,10 @@ page_title: Get the required software
page_description: Describes the software required to contribute to Docker
page_keywords: GitHub account, repository, Docker, Git, Go, make,
-# Get the required software
+# Get the required software for Linux or OS X
-Before you begin contributing you must have:
+This page explains how to get the software you need to use a Linux or OS X
+machine for Docker development. Before you begin contributing you must have:
* a GitHub account
* `git`
@@ -82,7 +83,7 @@ your user to the `docker` group as follows:
$ sudo usermod -aG docker ubuntu
-You must log out and back in for this modification to take effect.
+You must log out and log back in for this modification to take effect.
## Where to go next
diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md
index d586ea2c3..bcf4167ff 100644
--- a/docs/sources/project/test-and-docs.md
+++ b/docs/sources/project/test-and-docs.md
@@ -40,7 +40,7 @@ units each have unit tests and then, together, integration tests that test the
interface between the components. The `integration` and `integration-cli`
directories in the Docker repository contain integration test code.
-Testing is its own speciality. If you aren't familiar with testing techniques,
+Testing is its own specialty. If you aren't familiar with testing techniques,
there is a lot of information available to you on the Web. For now, you should
understand that, the Docker maintainers may ask you to write a new test or
change an existing one.
@@ -67,10 +67,6 @@ is simply `test`. The make file contains several targets for testing:
| test-unit |
Run just the unit tests. |
-
- | test-integration |
- Run just integration tests. |
-
| test-integration-cli |
Run the test for the integration command line interface. |
@@ -128,7 +124,7 @@ Run the entire test suite on your current repository:
If you are working inside a Docker development container, you use the
`hack/make.sh` script to run tests. The `hack/make.sh` script doesn't
have a single target that runs all the tests. Instead, you provide a single
-commmand line with multiple targets that does the same thing.
+command line with multiple targets that does the same thing.
Try this now.
@@ -143,7 +139,7 @@ Try this now.
3. Run the tests using the `hack/make.sh` script.
- root@5f8630b873fe:/go/src/github.com/docker/docker# hack/make.sh dynbinary binary cross test-unit test-integration test-integration-cli test-docker-py
+ root@5f8630b873fe:/go/src/github.com/docker/docker# hack/make.sh dynbinary binary cross test-unit test-integration-cli test-docker-py
The tests run just as they did within your local host.
@@ -159,17 +155,18 @@ Most test targets require that you build these precursor targets first:
## Running individual or multiple named tests
+We use [gocheck](https://labix.org/gocheck) for our integration-cli tests.
You can use the `TESTFLAGS` environment variable to run a single test. The
flag's value is passed as arguments to the `go test` command. For example, from
your local host you can run the `TestBuild` test with this command:
- $ TESTFLAGS='-test.run ^TestBuild$' make test
+ $ TESTFLAGS='-check.f DockerSuite.TestBuild*' make test-integration-cli
To run the same test inside your Docker development container, you do this:
- root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-run ^TestBuild$' hack/make.sh
+ root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-check.f TestBuild*' hack/make.sh binary test-integration-cli
-## If test under Boot2Docker fail do to space errors
+## If tests under Boot2Docker fail due to disk space errors
Running the tests requires about 2GB of memory. If you are running your
container on bare metal, that is you are not running with Boot2Docker, your
@@ -229,6 +226,46 @@ with new memory settings.
6. Restart your container and try your test again.
+## Testing just the Windows client
+
+This explains how to test the Windows client on a Windows server set up as a
+development environment. You'll use the **Git Bash** came with the Git for
+Windows installation. **Git Bash** just as it sounds allows you to run a Bash
+terminal on Windows.
+
+1. If you don't have one, start a Git Bash terminal.
+
+ 
+
+2. Change to the `docker` source directory.
+
+ $ cd /c/gopath/src/github.com/docker/docker
+
+3. Set `DOCKER_CLIENTONLY` as follows:
+
+ $ export DOCKER_CLIENTONLY=1
+
+ This ensures you are building only the client binary instead of both the
+ binary and the daemon.
+
+4. Set `DOCKER_TEST_HOST` to the `tcp://IP_ADDRESS:2376` value; substitute your
+machine's actual IP address, for example:
+
+ $ export DOCKER_TEST_HOST=tcp://263.124.23.200:2376
+
+5. Make the binary and the test:
+
+ $ hack/make.sh binary test-integration-cli
+
+ Many tests are skipped on Windows for various reasons. You see which tests
+ were skipped by re-running the make and passing in the
+ `TESTFLAGS='-test.v'` value.
+
+
+You can now choose to make changes to the Docker source or the tests. If you
+make any changes just run these commands again.
+
+
## Build and test the documentation
The Docker documentation source files are under `docs/sources`. The content is
@@ -249,7 +286,7 @@ can browse the docs.
1. In a terminal, change to the root of your `docker-fork` repository.
- $ cd ~/repos/dry-run-test
+ $ cd ~/repos/docker-fork
2. Make sure you are in your feature branch.
diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md
index 190cec055..329110289 100644
--- a/docs/sources/project/work-issue.md
+++ b/docs/sources/project/work-issue.md
@@ -30,8 +30,10 @@ Follow this workflow as you work:
source into a development container and iterate that way. For documentation
alone, you can work on your local host.
- Review if you forgot the details
- of working with a container.
+ Make sure you don't change files in the `vendor` directory and its
+ subdirectories; they contain third-party dependency code. Review if you forgot the details of
+ working with a container.
3. Test your changes as you work.
@@ -67,7 +69,7 @@ Follow this workflow as you work:
For example, if you edited the `docker.go` file you would format the file
like this:
- $ gofmt -s -w file.go
+ $ gofmt -s -w docker.go
Most file editors have a plugin to format for you. Check your editor's
documentation.
@@ -107,7 +109,7 @@ Follow this workflow as you work:
9. Push your change to your repository.
- $ git push origin
+ $ git push origin 11038-fix-rhel-link
Username for 'https://github.com': moxiegirl
Password for 'https://moxiegirl@github.com':
Counting objects: 60, done.
@@ -117,11 +119,7 @@ Follow this workflow as you work:
To https://github.com/moxiegirl/docker.git
* [new branch] 11038-fix-rhel-link -> 11038-fix-rhel-link
Branch 11038-fix-rhel-link set up to track remote branch 11038-fix-rhel-link from origin.
-
- The first time you push a change, you must specify the branch. Later, you can just do this:
-
- git push origin
-
+
## Review your branch on GitHub
After you push a new branch, you should verify it on GitHub:
@@ -147,54 +145,46 @@ After you push a new branch, you should verify it on GitHub:
You should pull and rebase frequently as you work.
-1. Return to the terminal on your local machine.
+1. Return to the terminal on your local machine and checkout your
+ feature branch in your local `docker-fork` repository.
-2. Make sure you are in your branch.
+2. Fetch any last minute changes from `docker/docker`.
- $ git branch 11038-fix-rhel-link
+ $ git fetch upstream master
+ From github.com:docker/docker
+ * branch master -> FETCH_HEAD
-3. Fetch all the changes from the `upstream/master` branch.
+3. Start an interactive rebase.
- $ git fetch upstream/master
+ $ git rebase -i upstream/master
- This command says get all the changes from the `master` branch belonging to
- the `upstream` remote.
+4. Rebase opens an editor with a list of commits.
-4. Rebase your local master with Docker's `upstream/master` branch.
+ pick 1a79f55 Tweak some of the other text for grammar
+ pick 53e4983 Fix a link
+ pick 3ce07bb Add a new line about RHEL
- $ git rebase -i upstream/master
-
- This command starts an interactive rebase to merge code from Docker's
- `upstream/master` branch into your local branch. If you aren't familiar or
- comfortable with rebase, you can learn more about rebasing on the web.
-
-5. Rebase opens an editor with a list of commits.
+5. Replace the `pick` keyword with `squash` on all but the first commit.
- pick 1a79f55 Tweak some of the other text for grammar
- pick 53e4983 Fix a link
- pick 3ce07bb Add a new line about RHEL
-
- If you run into trouble, `git --rebase abort` removes any changes and gets
- you back to where you started.
+ pick 1a79f55 Tweak some of the other text for grammar
+ squash 53e4983 Fix a link
+ squash 3ce07bb Add a new line about RHEL
-6. Squash the `pick` keyword with `squash` on all but the first commit.
+ After you save the changes and quit from the editor, git starts
+ the rebase, reporting the progress along the way. Sometimes
+ your changes can conflict with the work of others. If git
+ encounters a conflict, it stops the rebase, and prints guidance
+ for how to correct the conflict.
- pick 1a79f55 Tweak some of the other text for grammar
- squash 53e4983 Fix a link
- squash 3ce07bb Add a new line about RHEL
+6. Edit and save your commit message.
- After closing the file, `git` opens your editor again to edit the commit
- message.
+ `git commit -s`
-7. Edit and save your commit message.
+ Make sure your message includes /: {}" }`
@@ -207,8 +207,8 @@ Json Parameters:
volume for the container), `host_path:container_path` (to bind-mount
a host path into the container), or `host_path:container_path:ro`
(to make the bind-mount read-only inside the container).
- - **Links** - A list of links for the container. Each link entry should be of
- of the form "container_name:alias".
+ - **Links** - A list of links for the container. Each link entry should be
+ in the form of "container_name:alias".
- **LxcConf** - LXC specific configurations. These configurations will only
work when using the `lxc` execution driver.
- **PortBindings** - A map of exposed container ports and the host port they
@@ -225,8 +225,8 @@ Json Parameters:
container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
- **VolumesFrom** - A list of volumes to inherit from another container.
Specified in the form `[:]`
- - **CapAdd** - A list of kernel capabilties to add to the container.
- - **Capdrop** - A list of kernel capabilties to drop from the container.
+ - **CapAdd** - A list of kernel capabilities to add to the container.
+ - **Capdrop** - A list of kernel capabilities to drop from the container.
- **RestartPolicy** – The behavior to apply when the container exits. The
value is an object with a `Name` property of either `"always"` to
always restart or `"on-failure"` to restart only when the container
@@ -553,8 +553,8 @@ Json Parameters:
- **DnsSearch** - A list of DNS search domains
- **VolumesFrom** - A list of volumes to inherit from another container.
Specified in the form `[:]`
-- **CapAdd** - A list of kernel capabilties to add to the container.
-- **Capdrop** - A list of kernel capabilties to drop from the container.
+- **CapAdd** - A list of kernel capabilities to add to the container.
+- **Capdrop** - A list of kernel capabilities to drop from the container.
- **RestartPolicy** – The behavior to apply when the container exits. The
value is an object with a `Name` property of either `"always"` to
always restart or `"on-failure"` to restart only when the container
@@ -766,7 +766,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1
@@ -1261,7 +1261,7 @@ Query Parameters:
Request Headers:
- **Content-type** – should be set to `"application/tar"`.
-- **X-Registry-Config** – base64-encoded ConfigFile objec
+- **X-Registry-Config** – base64-encoded ConfigFile object
Status Codes:
@@ -1495,7 +1495,7 @@ Get a tarball containing all images and metadata for the repository specified
by `name`.
If `name` is a specific name and tag (e.g. ubuntu:latest), then only that image
-(and its parents) are returned. If `name` is an image ID, similarly only tha
+(and its parents) are returned. If `name` is an image ID, similarly only that
image (and its parents) are returned, but with the exclusion of the
'repositories' file in the tarball, as there were no image names referenced.
diff --git a/docs/sources/reference/api/docker_remote_api_v1.16.md b/docs/sources/reference/api/docker_remote_api_v1.16.md
index 86df97b71..df8e5be13 100644
--- a/docs/sources/reference/api/docker_remote_api_v1.16.md
+++ b/docs/sources/reference/api/docker_remote_api_v1.16.md
@@ -174,12 +174,12 @@ Json Parameters:
container.
- **Domainname** - A string value containing the desired domain name to use
for the container.
-- **User** - A string value containg the user to use inside the container.
+- **User** - A string value containing the user to use inside the container.
- **Memory** - Memory limit in bytes.
- **MemorySwap**- Total memory usage (memory + swap); set `-1` to disable swap.
- **CpuShares** - An integer value containing the CPU Shares for container
- (ie. the relative weight vs othercontainers).
- **CpuSet** - String value containg the cgroups Cpuset to use.
+ (ie. the relative weight vs other containers).
+ **CpuSet** - String value containing the cgroups Cpuset to use.
- **AttachStdin** - Boolean value, attaches to stdin.
- **AttachStdout** - Boolean value, attaches to stdout.
- **AttachStderr** - Boolean value, attaches to stderr.
@@ -195,7 +195,7 @@ Json Parameters:
container to empty objects.
- **WorkingDir** - A string value containing the working dir for commands to
run in.
-- **NetworkDisabled** - Boolean value, when true disables neworking for the
+- **NetworkDisabled** - Boolean value, when true disables networking for the
container
- **ExposedPorts** - An object mapping ports to an empty object in the form of:
`"ExposedPorts": { "/: {}" }`
@@ -207,8 +207,8 @@ Json Parameters:
volume for the container), `host_path:container_path` (to bind-mount
a host path into the container), or `host_path:container_path:ro`
(to make the bind-mount read-only inside the container).
- - **Links** - A list of links for the container. Each link entry should be of
- of the form "container_name:alias".
+ - **Links** - A list of links for the container. Each link entry should be
+ in the form of "container_name:alias".
- **LxcConf** - LXC specific configurations. These configurations will only
work when using the `lxc` execution driver.
- **PortBindings** - A map of exposed container ports and the host port they
@@ -225,8 +225,8 @@ Json Parameters:
container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
- **VolumesFrom** - A list of volumes to inherit from another container.
Specified in the form `[:]`
- - **CapAdd** - A list of kernel capabilties to add to the container.
- - **Capdrop** - A list of kernel capabilties to drop from the container.
+ - **CapAdd** - A list of kernel capabilities to add to the container.
+ - **Capdrop** - A list of kernel capabilities to drop from the container.
- **RestartPolicy** – The behavior to apply when the container exits. The
value is an object with a `Name` property of either `"always"` to
always restart or `"on-failure"` to restart only when the container
@@ -509,12 +509,66 @@ Start the container `id`
POST /containers/(id)/start HTTP/1.1
Content-Type: application/json
+ {
+ "Binds": ["/tmp:/tmp"],
+ "Links": ["redis3:redis"],
+ "LxcConf": {"lxc.utsname":"docker"},
+ "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+ "PublishAllPorts": false,
+ "Privileged": false,
+ "Dns": ["8.8.8.8"],
+ "DnsSearch": [""],
+ "VolumesFrom": ["parent", "other:ro"],
+ "CapAdd": ["NET_ADMIN"],
+ "CapDrop": ["MKNOD"],
+ "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+ "NetworkMode": "bridge",
+ "Devices": []
+ }
+
**Example response**:
HTTP/1.1 204 No Content
Json Parameters:
+- **Binds** – A list of volume bindings for this container. Each volume
+ binding is a string of the form `container_path` (to create a new
+ volume for the container), `host_path:container_path` (to bind-mount
+ a host path into the container), or `host_path:container_path:ro`
+ (to make the bind-mount read-only inside the container).
+- **Links** - A list of links for the container. Each link entry should be of
+ of the form "container_name:alias".
+- **LxcConf** - LXC specific configurations. These configurations will only
+ work when using the `lxc` execution driver.
+- **PortBindings** - A map of exposed container ports and the host port they
+ should map to. It should be specified in the form
+ `{ /: [{ "HostPort": "" }] }`
+ Take note that `port` is specified as a string and not an integer value.
+- **PublishAllPorts** - Allocates a random host port for all of a container's
+ exposed ports. Specified as a boolean value.
+- **Privileged** - Gives the container full access to the host. Specified as
+ a boolean value.
+- **Dns** - A list of dns servers for the container to use.
+- **DnsSearch** - A list of DNS search domains
+- **VolumesFrom** - A list of volumes to inherit from another container.
+ Specified in the form `[:]`
+- **CapAdd** - A list of kernel capabilities to add to the container.
+- **Capdrop** - A list of kernel capabilities to drop from the container.
+- **RestartPolicy** – The behavior to apply when the container exits. The
+ value is an object with a `Name` property of either `"always"` to
+ always restart or `"on-failure"` to restart only when the container
+ exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
+ controls the number of times to retry before giving up.
+ The default is not to restart. (optional)
+ An ever increasing delay (double the previous delay, starting at 100mS)
+ is added before each restart to prevent flooding the server.
+- **NetworkMode** - Sets the networking mode for the container. Supported
+ values are: `bridge`, `host`, and `container:`
+- **Devices** - A list of devices to add to the container specified in the
+ form
+ `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
+
Status Codes:
- **204** – no error
@@ -712,7 +766,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1
@@ -1208,7 +1262,7 @@ Query Parameters:
Request Headers:
- **Content-type** – should be set to `"application/tar"`.
-- **X-Registry-Config** – base64-encoded ConfigFile objec
+- **X-Registry-Config** – base64-encoded ConfigFile object
Status Codes:
@@ -1455,7 +1509,7 @@ Get a tarball containing all images and metadata for the repository specified
by `name`.
If `name` is a specific name and tag (e.g. ubuntu:latest), then only that image
-(and its parents) are returned. If `name` is an image ID, similarly only tha
+(and its parents) are returned. If `name` is an image ID, similarly only that
image (and its parents) are returned, but with the exclusion of the
'repositories' file in the tarball, as there were no image names referenced.
diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md
index 96887559c..d8ef81c0f 100644
--- a/docs/sources/reference/api/docker_remote_api_v1.17.md
+++ b/docs/sources/reference/api/docker_remote_api_v1.17.md
@@ -138,7 +138,6 @@ Create a container
"ExposedPorts": {
"22/tcp": {}
},
- "SecurityOpts": [""],
"HostConfig": {
"Binds": ["/tmp:/tmp"],
"Links": ["redis3:redis"],
@@ -156,6 +155,7 @@ Create a container
"RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
"NetworkMode": "bridge",
"Devices": []
+ "SecurityOpt": [""],
}
}
@@ -175,13 +175,13 @@ Json Parameters:
container.
- **Domainname** - A string value containing the desired domain name to use
for the container.
-- **User** - A string value containg the user to use inside the container.
+- **User** - A string value containing the user to use inside the container.
- **Memory** - Memory limit in bytes.
- **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap,
always use this with `memory`, and make the value larger than `memory`.
- **CpuShares** - An integer value containing the CPU Shares for container
- (ie. the relative weight vs othercontainers).
- **CpuSet** - String value containg the cgroups Cpuset to use.
+ (ie. the relative weight vs other containers).
+ **CpuSet** - String value containing the cgroups Cpuset to use.
- **AttachStdin** - Boolean value, attaches to stdin.
- **AttachStdout** - Boolean value, attaches to stdout.
- **AttachStderr** - Boolean value, attaches to stderr.
@@ -197,20 +197,18 @@ Json Parameters:
container to empty objects.
- **WorkingDir** - A string value containing the working dir for commands to
run in.
-- **NetworkDisabled** - Boolean value, when true disables neworking for the
+- **NetworkDisabled** - Boolean value, when true disables networking for the
container
- **ExposedPorts** - An object mapping ports to an empty object in the form of:
`"ExposedPorts": { "/: {}" }`
-- **SecurityOpts**: A list of string values to customize labels for MLS
- systems, such as SELinux.
- **HostConfig**
- **Binds** – A list of volume bindings for this container. Each volume
binding is a string of the form `container_path` (to create a new
volume for the container), `host_path:container_path` (to bind-mount
a host path into the container), or `host_path:container_path:ro`
(to make the bind-mount read-only inside the container).
- - **Links** - A list of links for the container. Each link entry should be of
- of the form "container_name:alias".
+ - **Links** - A list of links for the container. Each link entry should be
+ in the form of "container_name:alias".
- **LxcConf** - LXC specific configurations. These configurations will only
work when using the `lxc` execution driver.
- **PortBindings** - A map of exposed container ports and the host port they
@@ -229,8 +227,8 @@ Json Parameters:
container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
- **VolumesFrom** - A list of volumes to inherit from another container.
Specified in the form `[:]`
- - **CapAdd** - A list of kernel capabilties to add to the container.
- - **Capdrop** - A list of kernel capabilties to drop from the container.
+ - **CapAdd** - A list of kernel capabilities to add to the container.
+ - **Capdrop** - A list of kernel capabilities to drop from the container.
- **RestartPolicy** – The behavior to apply when the container exits. The
value is an object with a `Name` property of either `"always"` to
always restart or `"on-failure"` to restart only when the container
@@ -244,6 +242,8 @@ Json Parameters:
- **Devices** - A list of devices to add to the container specified in the
form
`{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
+ - **SecurityOpt**: A list of string values to customize labels for MLS
+ systems, such as SELinux.
Query Parameters:
@@ -639,12 +639,69 @@ Start the container `id`
POST /containers/(id)/start HTTP/1.1
Content-Type: application/json
+ {
+ "Binds": ["/tmp:/tmp"],
+ "Links": ["redis3:redis"],
+ "LxcConf": {"lxc.utsname":"docker"},
+ "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+ "PublishAllPorts": false,
+ "Privileged": false,
+ "ReadonlyRootfs": false,
+ "Dns": ["8.8.8.8"],
+ "DnsSearch": [""],
+ "VolumesFrom": ["parent", "other:ro"],
+ "CapAdd": ["NET_ADMIN"],
+ "CapDrop": ["MKNOD"],
+ "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+ "NetworkMode": "bridge",
+ "Devices": []
+ }
+
**Example response**:
HTTP/1.1 204 No Content
Json Parameters:
+- **Binds** – A list of volume bindings for this container. Each volume
+ binding is a string of the form `container_path` (to create a new
+ volume for the container), `host_path:container_path` (to bind-mount
+ a host path into the container), or `host_path:container_path:ro`
+ (to make the bind-mount read-only inside the container).
+- **Links** - A list of links for the container. Each link entry should be of
+ of the form "container_name:alias".
+- **LxcConf** - LXC specific configurations. These configurations will only
+ work when using the `lxc` execution driver.
+- **PortBindings** - A map of exposed container ports and the host port they
+ should map to. It should be specified in the form
+ `{ /: [{ "HostPort": "" }] }`
+ Take note that `port` is specified as a string and not an integer value.
+- **PublishAllPorts** - Allocates a random host port for all of a container's
+ exposed ports. Specified as a boolean value.
+- **Privileged** - Gives the container full access to the host. Specified as
+ a boolean value.
+- **ReadonlyRootfs** - Mount the container's root filesystem as read only.
+ Specified as a boolean value.
+- **Dns** - A list of dns servers for the container to use.
+- **DnsSearch** - A list of DNS search domains
+- **VolumesFrom** - A list of volumes to inherit from another container.
+ Specified in the form `[:]`
+- **CapAdd** - A list of kernel capabilities to add to the container.
+- **Capdrop** - A list of kernel capabilities to drop from the container.
+- **RestartPolicy** – The behavior to apply when the container exits. The
+ value is an object with a `Name` property of either `"always"` to
+ always restart or `"on-failure"` to restart only when the container
+ exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
+ controls the number of times to retry before giving up.
+ The default is not to restart. (optional)
+ An ever increasing delay (double the previous delay, starting at 100mS)
+ is added before each restart to prevent flooding the server.
+- **NetworkMode** - Sets the networking mode for the container. Supported
+ values are: `bridge`, `host`, and `container:`
+- **Devices** - A list of devices to add to the container specified in the
+ form
+ `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
+
Status Codes:
- **204** – no error
@@ -870,7 +927,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1
@@ -1083,7 +1140,7 @@ Query Parameters:
Request Headers:
- **Content-type** – should be set to `"application/tar"`.
-- **X-Registry-Config** – base64-encoded ConfigFile objec
+- **X-Registry-Config** – base64-encoded ConfigFile object
Status Codes:
@@ -1618,7 +1675,7 @@ Get a tarball containing all images and metadata for the repository specified
by `name`.
If `name` is a specific name and tag (e.g. ubuntu:latest), then only that image
-(and its parents) are returned. If `name` is an image ID, similarly only tha
+(and its parents) are returned. If `name` is an image ID, similarly only that
image (and its parents) are returned, but with the exclusion of the
'repositories' file in the tarball, as there were no image names referenced.
diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md
index ee89ccb02..2f6f2aada 100644
--- a/docs/sources/reference/api/docker_remote_api_v1.18.md
+++ b/docs/sources/reference/api/docker_remote_api_v1.18.md
@@ -91,6 +91,7 @@ Query Parameters:
- **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters:
- exited=<int> -- containers with exit code of <int>
- status=(restarting|running|paused|exited)
+ - label=`key` or `key=value` of a container label
Status Codes:
@@ -139,7 +140,6 @@ Create a container
"ExposedPorts": {
"22/tcp": {}
},
- "SecurityOpts": [""],
"HostConfig": {
"Binds": ["/tmp:/tmp"],
"Links": ["redis3:redis"],
@@ -163,6 +163,7 @@ Create a container
"Devices": [],
"Ulimits": [{}],
"LogConfig": { "Type": "json-file", Config: {} },
+ "SecurityOpt": [""],
"CgroupParent": ""
}
}
@@ -183,14 +184,14 @@ Json Parameters:
container.
- **Domainname** - A string value containing the desired domain name to use
for the container.
-- **User** - A string value containg the user to use inside the container.
+- **User** - A string value containing the user to use inside the container.
- **Memory** - Memory limit in bytes.
- **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap,
always use this with `memory`, and make the value larger than `memory`.
- **CpuShares** - An integer value containing the CPU Shares for container
- (ie. the relative weight vs othercontainers).
+ (ie. the relative weight vs other containers).
- **Cpuset** - The same as CpusetCpus, but deprecated, please don't use.
-- **CpusetCpus** - String value containg the cgroups CpusetCpus to use.
+- **CpusetCpus** - String value containing the cgroups CpusetCpus to use.
- **AttachStdin** - Boolean value, attaches to stdin.
- **AttachStdout** - Boolean value, attaches to stdout.
- **AttachStderr** - Boolean value, attaches to stderr.
@@ -207,61 +208,61 @@ Json Parameters:
container to empty objects.
- **WorkingDir** - A string value containing the working dir for commands to
run in.
-- **NetworkDisabled** - Boolean value, when true disables neworking for the
+- **NetworkDisabled** - Boolean value, when true disables networking for the
container
- **ExposedPorts** - An object mapping ports to an empty object in the form of:
`"ExposedPorts": { "/: {}" }`
-- **SecurityOpts**: A list of string values to customize labels for MLS
- systems, such as SELinux.
- **HostConfig**
- - **Binds** – A list of volume bindings for this container. Each volume
- binding is a string of the form `container_path` (to create a new
- volume for the container), `host_path:container_path` (to bind-mount
- a host path into the container), or `host_path:container_path:ro`
- (to make the bind-mount read-only inside the container).
- - **Links** - A list of links for the container. Each link entry should be of
- of the form "container_name:alias".
- - **LxcConf** - LXC specific configurations. These configurations will only
- work when using the `lxc` execution driver.
- - **PortBindings** - A map of exposed container ports and the host port they
- should map to. It should be specified in the form
- `{ /: [{ "HostPort": "" }] }`
- Take note that `port` is specified as a string and not an integer value.
- - **PublishAllPorts** - Allocates a random host port for all of a container's
- exposed ports. Specified as a boolean value.
- - **Privileged** - Gives the container full access to the host. Specified as
- a boolean value.
- - **ReadonlyRootfs** - Mount the container's root filesystem as read only.
- Specified as a boolean value.
- - **Dns** - A list of dns servers for the container to use.
- - **DnsSearch** - A list of DNS search domains
- - **ExtraHosts** - A list of hostnames/IP mappings to be added to the
- container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
- - **VolumesFrom** - A list of volumes to inherit from another container.
- Specified in the form `[:]`
- - **CapAdd** - A list of kernel capabilties to add to the container.
- - **Capdrop** - A list of kernel capabilties to drop from the container.
- - **RestartPolicy** – The behavior to apply when the container exits. The
- value is an object with a `Name` property of either `"always"` to
- always restart or `"on-failure"` to restart only when the container
- exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
- controls the number of times to retry before giving up.
- The default is not to restart. (optional)
- An ever increasing delay (double the previous delay, starting at 100mS)
- is added before each restart to prevent flooding the server.
- - **NetworkMode** - Sets the networking mode for the container. Supported
- values are: `bridge`, `host`, and `container:`
- - **Devices** - A list of devices to add to the container specified in the
- form
- `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
- - **Ulimits** - A list of ulimits to be set in the container, specified as
- `{ "Name": , "Soft": , "Hard": }`, for example:
- `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
- - **LogConfig** - Logging configuration to container, format
- `{ "Type": "", "Config": {"key1": "val1"}}
- Available types: `json-file`, `syslog`, `none`.
- `json-file` logging driver.
- - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+ - **Binds** – A list of volume bindings for this container. Each volume
+ binding is a string of the form `container_path` (to create a new
+ volume for the container), `host_path:container_path` (to bind-mount
+ a host path into the container), or `host_path:container_path:ro`
+ (to make the bind-mount read-only inside the container).
+ - **Links** - A list of links for the container. Each link entry should be
+ in the form of `container_name:alias`.
+ - **LxcConf** - LXC specific configurations. These configurations will only
+ work when using the `lxc` execution driver.
+ - **PortBindings** - A map of exposed container ports and the host port they
+ should map to. It should be specified in the form
+ `{ /: [{ "HostPort": "" }] }`
+ Take note that `port` is specified as a string and not an integer value.
+ - **PublishAllPorts** - Allocates a random host port for all of a container's
+ exposed ports. Specified as a boolean value.
+ - **Privileged** - Gives the container full access to the host. Specified as
+ a boolean value.
+ - **ReadonlyRootfs** - Mount the container's root filesystem as read only.
+ Specified as a boolean value.
+ - **Dns** - A list of dns servers for the container to use.
+ - **DnsSearch** - A list of DNS search domains
+ - **ExtraHosts** - A list of hostnames/IP mappings to be added to the
+ container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
+ - **VolumesFrom** - A list of volumes to inherit from another container.
+ Specified in the form `[:]`
+ - **CapAdd** - A list of kernel capabilities to add to the container.
+ - **Capdrop** - A list of kernel capabilities to drop from the container.
+ - **RestartPolicy** – The behavior to apply when the container exits. The
+ value is an object with a `Name` property of either `"always"` to
+ always restart or `"on-failure"` to restart only when the container
+ exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
+ controls the number of times to retry before giving up.
+ The default is not to restart. (optional)
+ An ever increasing delay (double the previous delay, starting at 100mS)
+ is added before each restart to prevent flooding the server.
+ - **NetworkMode** - Sets the networking mode for the container. Supported
+ values are: `bridge`, `host`, and `container:`
+ - **Devices** - A list of devices to add to the container specified in the
+ form
+ `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
+ - **Ulimits** - A list of ulimits to be set in the container, specified as
+ `{ "Name": , "Soft": , "Hard": }`, for example:
+ `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
+ - **SecurityOpt**: A list of string values to customize labels for MLS
+ systems, such as SELinux.
+ - **LogConfig** - Log configuration for the container, specified as
+ `{ "Type": "", "Config": {"key1": "val1"}}`.
+ Available types: `json-file`, `syslog`, `none`.
+ `json-file` logging driver.
+ - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
Query Parameters:
@@ -679,12 +680,90 @@ Start the container `id`
POST /containers/(id)/start HTTP/1.1
Content-Type: application/json
+ {
+ "Binds": ["/tmp:/tmp"],
+ "Links": ["redis3:redis"],
+ "LxcConf": {"lxc.utsname":"docker"},
+ "Memory": 0,
+ "MemorySwap": 0,
+ "CpuShares": 512,
+ "CpusetCpus": "0,1",
+ "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+ "PublishAllPorts": false,
+ "Privileged": false,
+ "ReadonlyRootfs": false,
+ "Dns": ["8.8.8.8"],
+ "DnsSearch": [""],
+ "ExtraHosts": null,
+ "VolumesFrom": ["parent", "other:ro"],
+ "CapAdd": ["NET_ADMIN"],
+ "CapDrop": ["MKNOD"],
+ "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+ "NetworkMode": "bridge",
+ "Devices": [],
+ "Ulimits": [{}],
+ "LogConfig": { "Type": "json-file", Config: {} },
+ "SecurityOpt": [""],
+ "CgroupParent": ""
+ }
+
**Example response**:
HTTP/1.1 204 No Content
Json Parameters:
+- **Binds** – A list of volume bindings for this container. Each volume
+ binding is a string of the form `container_path` (to create a new
+ volume for the container), `host_path:container_path` (to bind-mount
+ a host path into the container), or `host_path:container_path:ro`
+ (to make the bind-mount read-only inside the container).
+- **Links** - A list of links for the container. Each link entry should be of
+ of the form `container_name:alias`.
+- **LxcConf** - LXC specific configurations. These configurations will only
+ work when using the `lxc` execution driver.
+- **PortBindings** - A map of exposed container ports and the host port they
+ should map to. It should be specified in the form
+ `{ /: [{ "HostPort": "" }] }`
+ Take note that `port` is specified as a string and not an integer value.
+- **PublishAllPorts** - Allocates a random host port for all of a container's
+ exposed ports. Specified as a boolean value.
+- **Privileged** - Gives the container full access to the host. Specified as
+ a boolean value.
+- **ReadonlyRootfs** - Mount the container's root filesystem as read only.
+ Specified as a boolean value.
+- **Dns** - A list of dns servers for the container to use.
+- **DnsSearch** - A list of DNS search domains
+- **ExtraHosts** - A list of hostnames/IP mappings to be added to the
+ container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
+- **VolumesFrom** - A list of volumes to inherit from another container.
+ Specified in the form `[:]`
+- **CapAdd** - A list of kernel capabilities to add to the container.
+- **Capdrop** - A list of kernel capabilities to drop from the container.
+- **RestartPolicy** – The behavior to apply when the container exits. The
+ value is an object with a `Name` property of either `"always"` to
+ always restart or `"on-failure"` to restart only when the container
+ exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
+ controls the number of times to retry before giving up.
+ The default is not to restart. (optional)
+ An ever increasing delay (double the previous delay, starting at 100mS)
+ is added before each restart to prevent flooding the server.
+- **NetworkMode** - Sets the networking mode for the container. Supported
+ values are: `bridge`, `host`, and `container:`
+- **Devices** - A list of devices to add to the container specified in the
+ form
+ `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
+- **Ulimits** - A list of ulimits to be set in the container, specified as
+ `{ "Name": , "Soft": , "Hard": }`, for example:
+ `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
+- **SecurityOpt**: A list of string values to customize labels for MLS
+ systems, such as SELinux.
+- **LogConfig** - Log configuration for the container, specified as
+ `{ "Type": "", "Config": {"key1": "val1"}}`.
+ Available types: `json-file`, `syslog`, `none`.
+ `json-file` logging driver.
+- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+
Status Codes:
- **204** – no error
@@ -910,7 +989,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1
@@ -1113,6 +1192,7 @@ Query Parameters:
- **all** – 1/True/true or 0/False/false, default false
- **filters** – a json encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
- dangling=true
+ - label=`key` or `key=value` of an image label
### Build image from a Dockerfile
@@ -1167,12 +1247,12 @@ Query Parameters:
- **memory** - set memory limit for build
- **memswap** - Total memory (memory + swap), `-1` to disable swap
- **cpushares** - CPU shares (relative weight)
-- **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1`
+- **cpusetcpus** - CPUs in which to allow execution, e.g., `0-3`, `0,1`
Request Headers:
- **Content-type** – should be set to `"application/tar"`.
-- **X-Registry-Config** – base64-encoded ConfigFile objec
+- **X-Registry-Config** – base64-encoded ConfigFile object
Status Codes:
@@ -1514,35 +1594,50 @@ Display system-wide information
Content-Type: application/json
{
- "Containers":11,
- "Images":16,
- "Driver":"btrfs",
- "DriverStatus": [[""]],
- "ExecutionDriver":"native-0.1",
- "KernelVersion":"3.12.0-1-amd64"
- "NCPU":1,
- "MemTotal":2099236864,
- "Name":"prod-server-42",
- "ID":"7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS",
- "Debug":false,
- "NFd": 11,
- "NGoroutines":21,
- "SystemTime": "2015-03-10T11:11:23.730591467-07:00"
- "NEventsListener":0,
- "InitPath":"/usr/bin/docker",
- "InitSha1":"",
- "IndexServerAddress":["https://index.docker.io/v1/"],
- "MemoryLimit":true,
- "SwapLimit":false,
- "IPv4Forwarding":true,
- "Labels":["storage=ssd"],
- "DockerRootDir": "/var/lib/docker",
- "HttpProxy": "http://test:test@localhost:8080"
- "HttpsProxy": "https://test:test@localhost:8080"
- "NoProxy": "9.81.1.160"
- "OperatingSystem": "Boot2Docker",
+ "Containers": 11,
+ "Debug": 0,
+ "DockerRootDir": "/var/lib/docker",
+ "Driver": "btrfs",
+ "DriverStatus": [[""]],
+ "ExecutionDriver": "native-0.1",
+ "HttpProxy": "http://test:test@localhost:8080",
+ "HttpsProxy": "https://test:test@localhost:8080",
+ "ID": "7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS",
+ "IPv4Forwarding": 1,
+ "Images": 16,
+ "IndexServerAddress": "https://index.docker.io/v1/",
+ "InitPath": "/usr/bin/docker",
+ "InitSha1": "",
+ "KernelVersion": "3.12.0-1-amd64",
+ "Labels": [
+ "storage=ssd"
+ ],
+ "MemTotal": 2099236864,
+ "MemoryLimit": 1,
+ "NCPU": 1,
+ "NEventsListener": 0,
+ "NFd": 11,
+ "NGoroutines": 21,
+ "Name": "prod-server-42",
+ "NoProxy": "9.81.1.160",
+ "OperatingSystem": "Boot2Docker",
+ "RegistryConfig": {
+ "IndexConfigs": {
+ "docker.io": {
+ "Mirrors": null,
+ "Name": "docker.io",
+ "Official": true,
+ "Secure": true
+ }
+ },
+ "InsecureRegistryCIDRs": [
+ "127.0.0.0/8"
+ ]
+ },
+ "SwapLimit": 0,
+ "SystemTime": "2015-03-10T11:11:23.730591467-07:00"
}
-
+
Status Codes:
- **200** – no error
@@ -1713,7 +1808,7 @@ Get a tarball containing all images and metadata for the repository specified
by `name`.
If `name` is a specific name and tag (e.g. ubuntu:latest), then only that image
-(and its parents) are returned. If `name` is an image ID, similarly only tha
+(and its parents) are returned. If `name` is an image ID, similarly only that
image (and its parents) are returned, but with the exclusion of the
'repositories' file in the tarball, as there were no image names referenced.
@@ -1835,6 +1930,7 @@ Sets up an exec instance in a running container `id`
{
"Id": "f90e34656806"
+ "Warnings":[]
}
Json Parameters:
diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md
new file mode 100644
index 000000000..dde8ee79a
--- /dev/null
+++ b/docs/sources/reference/api/docker_remote_api_v1.19.md
@@ -0,0 +1,2187 @@
+page_title: Remote API v1.19
+page_description: API Documentation for Docker
+page_keywords: API, Docker, rcli, REST, documentation
+
+# Docker Remote API v1.19
+
+## 1. Brief introduction
+
+ - The Remote API has replaced `rcli`.
+ - The daemon listens on `unix:///var/run/docker.sock` but you can
+ [Bind Docker to another host/port or a Unix socket](
+ /articles/basics/#bind-docker-to-another-hostport-or-a-unix-socket).
+ - The API tends to be REST, but for some complex commands, like `attach`
+ or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
+ `STDIN` and `STDERR`.
+ - When the client API version is newer than the daemon's an HTTP
+ `400 Bad Request` error message is returned.
+
+# 2. Endpoints
+
+## 2.1 Containers
+
+### List containers
+
+`GET /containers/json`
+
+List containers
+
+**Example request**:
+
+ GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ [
+ {
+ "Id": "8dfafdbc3a40",
+ "Image": "ubuntu:latest",
+ "Command": "echo 1",
+ "Created": 1367854155,
+ "Status": "Exit 0",
+ "Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
+ "SizeRw": 12288,
+ "SizeRootFs": 0
+ },
+ {
+ "Id": "9cd87474be90",
+ "Image": "ubuntu:latest",
+ "Command": "echo 222222",
+ "Created": 1367854155,
+ "Status": "Exit 0",
+ "Ports": [],
+ "SizeRw": 12288,
+ "SizeRootFs": 0
+ },
+ {
+ "Id": "3176a2479c92",
+ "Image": "ubuntu:latest",
+ "Command": "echo 3333333333333333",
+ "Created": 1367854154,
+ "Status": "Exit 0",
+ "Ports":[],
+ "SizeRw":12288,
+ "SizeRootFs":0
+ },
+ {
+ "Id": "4cb07b47f9fb",
+ "Image": "ubuntu:latest",
+ "Command": "echo 444444444444444444444444444444444",
+ "Created": 1367854152,
+ "Status": "Exit 0",
+ "Ports": [],
+ "SizeRw": 12288,
+ "SizeRootFs": 0
+ }
+ ]
+
+Query Parameters:
+
+- **all** – 1/True/true or 0/False/false, Show all containers.
+ Only running containers are shown by default (i.e., this defaults to false)
+- **limit** – Show `limit` last created
+ containers, include non-running ones.
+- **since** – Show only containers created since Id, include
+ non-running ones.
+- **before** – Show only containers created before Id, include
+ non-running ones.
+- **size** – 1/True/true or 0/False/false, Show the containers
+ sizes
+- **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters:
+ - exited=<int> -- containers with exit code of <int>
+ - status=(restarting|running|paused|exited)
+ - label=`key` or `key=value` of a container label
+
+Status Codes:
+
+- **200** – no error
+- **400** – bad parameter
+- **500** – server error
+
+### Create a container
+
+`POST /containers/create`
+
+Create a container
+
+**Example request**:
+
+ POST /containers/create HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "Hostname": "",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": true,
+ "AttachStderr": true,
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": null,
+ "Cmd": [
+ "date"
+ ],
+ "Entrypoint": "",
+ "Image": "ubuntu",
+ "Labels": {
+ "com.example.vendor": "Acme",
+ "com.example.license": "GPL",
+ "com.example.version": "1.0"
+ },
+ "Volumes": {
+ "/tmp": {}
+ },
+ "WorkingDir": "",
+ "NetworkDisabled": false,
+ "MacAddress": "12:34:56:78:9a:bc",
+ "ExposedPorts": {
+ "22/tcp": {}
+ },
+ "HostConfig": {
+ "Binds": ["/tmp:/tmp"],
+ "Links": ["redis3:redis"],
+ "LxcConf": {"lxc.utsname":"docker"},
+ "Memory": 0,
+ "MemorySwap": 0,
+ "CpuShares": 512,
+ "CpuPeriod": 100000,
+ "CpusetCpus": "0,1",
+ "CpusetMems": "0,1",
+ "BlkioWeight": 300,
+ "OomKillDisable": false,
+ "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+ "PublishAllPorts": false,
+ "Privileged": false,
+ "ReadonlyRootfs": false,
+ "Dns": ["8.8.8.8"],
+ "DnsSearch": [""],
+ "ExtraHosts": null,
+ "VolumesFrom": ["parent", "other:ro"],
+ "CapAdd": ["NET_ADMIN"],
+ "CapDrop": ["MKNOD"],
+ "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+ "NetworkMode": "bridge",
+ "Devices": [],
+ "Ulimits": [{}],
+ "LogConfig": { "Type": "json-file", "Config": {} },
+ "SecurityOpt": [""],
+ "CgroupParent": ""
+ }
+ }
+
+**Example response**:
+
+ HTTP/1.1 201 Created
+ Content-Type: application/json
+
+ {
+ "Id":"e90e34656806"
+ "Warnings":[]
+ }
+
+Json Parameters:
+
+- **Hostname** - A string value containing the desired hostname to use for the
+ container.
+- **Domainname** - A string value containing the desired domain name to use
+ for the container.
+- **User** - A string value containing the user to use inside the container.
+- **Memory** - Memory limit in bytes.
+- **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap,
+ always use this with `memory`, and make the value larger than `memory`.
+- **CpuShares** - An integer value containing the CPU Shares for container
+ (ie. the relative weight vs other containers).
+- **CpuPeriod** - The length of a CPU period (in microseconds).
+- **Cpuset** - The same as CpusetCpus, but deprecated, please don't use.
+- **CpusetCpus** - String value containing the cgroups CpusetCpus to use.
+- **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
+- **BlkioWeight** - Block IO weight (relative weight) accepts a weight value between 10 and 1000.
+- **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not.
+- **AttachStdin** - Boolean value, attaches to stdin.
+- **AttachStdout** - Boolean value, attaches to stdout.
+- **AttachStderr** - Boolean value, attaches to stderr.
+- **Tty** - Boolean value, Attach standard streams to a tty, including stdin if it is not closed.
+- **OpenStdin** - Boolean value, opens stdin,
+- **StdinOnce** - Boolean value, close stdin after the 1 attached client disconnects.
+- **Env** - A list of environment variables in the form of `VAR=value`
+- **Labels** - Adds a map of labels that to a container. To specify a map: `{"key":"value"[,"key2":"value2"]}`
+- **Cmd** - Command to run specified as a string or an array of strings.
+- **Entrypoint** - Set the entrypoint for the container a a string or an array
+ of strings
+- **Image** - String value containing the image name to use for the container
+- **Volumes** – An object mapping mountpoint paths (strings) inside the
+ container to empty objects.
+- **WorkingDir** - A string value containing the working dir for commands to
+ run in.
+- **NetworkDisabled** - Boolean value, when true disables networking for the
+ container
+- **ExposedPorts** - An object mapping ports to an empty object in the form of:
+ `"ExposedPorts": { "/: {}" }`
+- **HostConfig**
+ - **Binds** – A list of volume bindings for this container. Each volume
+ binding is a string of the form `container_path` (to create a new
+ volume for the container), `host_path:container_path` (to bind-mount
+ a host path into the container), or `host_path:container_path:ro`
+ (to make the bind-mount read-only inside the container).
+ - **Links** - A list of links for the container. Each link entry should be
+ in the form of `container_name:alias`.
+ - **LxcConf** - LXC specific configurations. These configurations will only
+ work when using the `lxc` execution driver.
+ - **PortBindings** - A map of exposed container ports and the host port they
+ should map to. It should be specified in the form
+ `{ /: [{ "HostPort": "" }] }`
+ Take note that `port` is specified as a string and not an integer value.
+ - **PublishAllPorts** - Allocates a random host port for all of a container's
+ exposed ports. Specified as a boolean value.
+ - **Privileged** - Gives the container full access to the host. Specified as
+ a boolean value.
+ - **ReadonlyRootfs** - Mount the container's root filesystem as read only.
+ Specified as a boolean value.
+ - **Dns** - A list of dns servers for the container to use.
+ - **DnsSearch** - A list of DNS search domains
+ - **ExtraHosts** - A list of hostnames/IP mappings to be added to the
+ container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
+ - **VolumesFrom** - A list of volumes to inherit from another container.
+ Specified in the form `[:]`
+ - **CapAdd** - A list of kernel capabilities to add to the container.
+ - **Capdrop** - A list of kernel capabilities to drop from the container.
+ - **RestartPolicy** – The behavior to apply when the container exits. The
+ value is an object with a `Name` property of either `"always"` to
+ always restart or `"on-failure"` to restart only when the container
+ exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
+ controls the number of times to retry before giving up.
+ The default is not to restart. (optional)
+ An ever increasing delay (double the previous delay, starting at 100mS)
+ is added before each restart to prevent flooding the server.
+ - **NetworkMode** - Sets the networking mode for the container. Supported
+ values are: `bridge`, `host`, and `container:`
+ - **Devices** - A list of devices to add to the container specified in the
+ form
+ `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
+ - **Ulimits** - A list of ulimits to be set in the container, specified as
+ `{ "Name": , "Soft": , "Hard": }`, for example:
+ `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
+ - **SecurityOpt**: A list of string values to customize labels for MLS
+ systems, such as SELinux.
+ - **LogConfig** - Log configuration for the container, specified as
+ `{ "Type": "", "Config": {"key1": "val1"}}`.
+ Available types: `json-file`, `syslog`, `journald`, `none`.
+ `json-file` logging driver.
+ - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+
+Query Parameters:
+
+- **name** – Assign the specified name to the container. Must
+ match `/?[a-zA-Z0-9_-]+`.
+
+Status Codes:
+
+- **201** – no error
+- **404** – no such container
+- **406** – impossible to attach (container not running)
+- **500** – server error
+
+### Inspect a container
+
+`GET /containers/(id)/json`
+
+Return low-level information on the container `id`
+
+
+**Example request**:
+
+ GET /containers/4fa6e0f0c678/json HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "AppArmorProfile": "",
+ "Args": [
+ "-c",
+ "exit 9"
+ ],
+ "Config": {
+ "AttachStderr": true,
+ "AttachStdin": false,
+ "AttachStdout": true,
+ "Cmd": [
+ "/bin/sh",
+ "-c",
+ "exit 9"
+ ],
+ "Domainname": "",
+ "Entrypoint": null,
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ ],
+ "ExposedPorts": null,
+ "Hostname": "ba033ac44011",
+ "Image": "ubuntu",
+ "Labels": {
+ "com.example.vendor": "Acme",
+ "com.example.license": "GPL",
+ "com.example.version": "1.0"
+ },
+ "MacAddress": "",
+ "NetworkDisabled": false,
+ "OnBuild": null,
+ "OpenStdin": false,
+ "PortSpecs": null,
+ "StdinOnce": false,
+ "Tty": false,
+ "User": "",
+ "Volumes": null,
+ "WorkingDir": ""
+ },
+ "Created": "2015-01-06T15:47:31.485331387Z",
+ "Driver": "devicemapper",
+ "ExecDriver": "native-0.2",
+ "ExecIDs": null,
+ "HostConfig": {
+ "Binds": null,
+ "BlkioWeight": 0,
+ "CapAdd": null,
+ "CapDrop": null,
+ "ContainerIDFile": "",
+ "CpusetCpus": "",
+ "CpusetMems": "",
+ "CpuShares": 0,
+ "CpuPeriod": 100000,
+ "Devices": [],
+ "Dns": null,
+ "DnsSearch": null,
+ "ExtraHosts": null,
+ "IpcMode": "",
+ "Links": null,
+ "LxcConf": [],
+ "Memory": 0,
+ "MemorySwap": 0,
+ "OomKillDisable": false,
+ "NetworkMode": "bridge",
+ "PortBindings": {},
+ "Privileged": false,
+ "ReadonlyRootfs": false,
+ "PublishAllPorts": false,
+ "RestartPolicy": {
+ "MaximumRetryCount": 2,
+ "Name": "on-failure"
+ },
+ "LogConfig": {
+ "Config": null,
+ "Type": "json-file"
+ },
+ "SecurityOpt": null,
+ "VolumesFrom": null,
+ "Ulimits": [{}]
+ },
+ "HostnamePath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hostname",
+ "HostsPath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hosts",
+ "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log",
+ "Id": "ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39",
+ "Image": "04c5d3b7b0656168630d3ba35d8889bd0e9caafcaeb3004d2bfbc47e7c5d35d2",
+ "MountLabel": "",
+ "Name": "/boring_euclid",
+ "NetworkSettings": {
+ "Bridge": "",
+ "Gateway": "",
+ "IPAddress": "",
+ "IPPrefixLen": 0,
+ "MacAddress": "",
+ "PortMapping": null,
+ "Ports": null
+ },
+ "Path": "/bin/sh",
+ "ProcessLabel": "",
+ "ResolvConfPath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/resolv.conf",
+ "RestartCount": 1,
+ "State": {
+ "Error": "",
+ "ExitCode": 9,
+ "FinishedAt": "2015-01-06T15:47:32.080254511Z",
+ "OOMKilled": false,
+ "Paused": false,
+ "Pid": 0,
+ "Restarting": false,
+ "Running": false,
+ "StartedAt": "2015-01-06T15:47:32.072697474Z"
+ },
+ "Volumes": {},
+ "VolumesRW": {}
+ }
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such container
+- **500** – server error
+
+### List processes running inside a container
+
+`GET /containers/(id)/top`
+
+List processes running inside the container `id`
+
+**Example request**:
+
+ GET /containers/4fa6e0f0c678/top HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "Titles": [
+ "USER",
+ "PID",
+ "%CPU",
+ "%MEM",
+ "VSZ",
+ "RSS",
+ "TTY",
+ "STAT",
+ "START",
+ "TIME",
+ "COMMAND"
+ ],
+ "Processes": [
+ ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"],
+ ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"]
+ ]
+ }
+
+Query Parameters:
+
+- **ps_args** – ps arguments to use (e.g., aux)
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such container
+- **500** – server error
+
+### Get container logs
+
+`GET /containers/(id)/logs`
+
+Get stdout and stderr logs from the container ``id``
+
+> **Note**:
+> This endpoint works only for containers with `json-file` logging driver.
+
+**Example request**:
+
+ GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1×tamps=1&follow=1&tail=10&since=1428990821 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 101 UPGRADED
+ Content-Type: application/vnd.docker.raw-stream
+ Connection: Upgrade
+ Upgrade: tcp
+
+ {{ STREAM }}
+
+Query Parameters:
+
+- **follow** – 1/True/true or 0/False/false, return stream. Default false
+- **stdout** – 1/True/true or 0/False/false, show stdout log. Default false
+- **stderr** – 1/True/true or 0/False/false, show stderr log. Default false
+- **since** – UNIX timestamp (integer) to filter logs. Specifying a timestamp
+ will only output log-entries since that timestamp. Default: 0 (unfiltered)
+- **timestamps** – 1/True/true or 0/False/false, print timestamps for
+ every log line. Default false
+- **tail** – Output specified number of lines at the end of logs: `all` or ``. Default all
+
+Status Codes:
+
+- **101** – no error, hints proxy about hijacking
+- **200** – no error, no upgrade header found
+- **404** – no such container
+- **500** – server error
+
+### Inspect changes on a container's filesystem
+
+`GET /containers/(id)/changes`
+
+Inspect changes on container `id`'s filesystem
+
+**Example request**:
+
+ GET /containers/4fa6e0f0c678/changes HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ [
+ {
+ "Path": "/dev",
+ "Kind": 0
+ },
+ {
+ "Path": "/dev/kmsg",
+ "Kind": 1
+ },
+ {
+ "Path": "/test",
+ "Kind": 1
+ }
+ ]
+
+Values for `Kind`:
+
+- `0`: Modify
+- `1`: Add
+- `2`: Delete
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such container
+- **500** – server error
+
+### Export a container
+
+`GET /containers/(id)/export`
+
+Export the contents of container `id`
+
+**Example request**:
+
+ GET /containers/4fa6e0f0c678/export HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/octet-stream
+
+ {{ TAR STREAM }}
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such container
+- **500** – server error
+
+### Get container stats based on resource usage
+
+`GET /containers/(id)/stats`
+
+This endpoint returns a live stream of a container's resource usage statistics.
+
+> **Note**: this functionality currently only works when using the *libcontainer* exec-driver.
+
+**Example request**:
+
+ GET /containers/redis1/stats HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "read" : "2015-01-08T22:57:31.547920715Z",
+ "network" : {
+ "rx_dropped" : 0,
+ "rx_bytes" : 648,
+ "rx_errors" : 0,
+ "tx_packets" : 8,
+ "tx_dropped" : 0,
+ "rx_packets" : 8,
+ "tx_errors" : 0,
+ "tx_bytes" : 648
+ },
+ "memory_stats" : {
+ "stats" : {
+ "total_pgmajfault" : 0,
+ "cache" : 0,
+ "mapped_file" : 0,
+ "total_inactive_file" : 0,
+ "pgpgout" : 414,
+ "rss" : 6537216,
+ "total_mapped_file" : 0,
+ "writeback" : 0,
+ "unevictable" : 0,
+ "pgpgin" : 477,
+ "total_unevictable" : 0,
+ "pgmajfault" : 0,
+ "total_rss" : 6537216,
+ "total_rss_huge" : 6291456,
+ "total_writeback" : 0,
+ "total_inactive_anon" : 0,
+ "rss_huge" : 6291456,
+ "hierarchical_memory_limit" : 67108864,
+ "total_pgfault" : 964,
+ "total_active_file" : 0,
+ "active_anon" : 6537216,
+ "total_active_anon" : 6537216,
+ "total_pgpgout" : 414,
+ "total_cache" : 0,
+ "inactive_anon" : 0,
+ "active_file" : 0,
+ "pgfault" : 964,
+ "inactive_file" : 0,
+ "total_pgpgin" : 477
+ },
+ "max_usage" : 6651904,
+ "usage" : 6537216,
+ "failcnt" : 0,
+ "limit" : 67108864
+ },
+ "blkio_stats" : {},
+ "cpu_stats" : {
+ "cpu_usage" : {
+ "percpu_usage" : [
+ 16970827,
+ 1839451,
+ 7107380,
+ 10571290
+ ],
+ "usage_in_usermode" : 10000000,
+ "total_usage" : 36488948,
+ "usage_in_kernelmode" : 20000000
+ },
+ "system_cpu_usage" : 20091722000000000,
+ "throttling_data" : {}
+ }
+ }
+
+Query Parameters:
+
+- **stream** – 1/True/true or 0/False/false, pull stats once then disconnect. Default true
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such container
+- **500** – server error
+
+### Resize a container TTY
+
+`POST /containers/(id)/resize?h=&w=`
+
+Resize the TTY for container with `id`. The container must be restarted for the resize to take effect.
+
+**Example request**:
+
+ POST /containers/4fa6e0f0c678/resize?h=40&w=80 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Length: 0
+ Content-Type: text/plain; charset=utf-8
+
+Status Codes:
+
+- **200** – no error
+- **404** – No such container
+- **500** – Cannot resize container
+
+### Start a container
+
+`POST /containers/(id)/start`
+
+Start the container `id`
+
+**Example request**:
+
+ POST /containers/(id)/start HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "Binds": ["/tmp:/tmp"],
+ "Links": ["redis3:redis"],
+ "LxcConf": {"lxc.utsname":"docker"},
+ "Memory": 0,
+ "MemorySwap": 0,
+ "CpuShares": 512,
+ "CpusetCpus": "0,1",
+ "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+ "PublishAllPorts": false,
+ "Privileged": false,
+ "ReadonlyRootfs": false,
+ "Dns": ["8.8.8.8"],
+ "DnsSearch": [""],
+ "ExtraHosts": null,
+ "VolumesFrom": ["parent", "other:ro"],
+ "CapAdd": ["NET_ADMIN"],
+ "CapDrop": ["MKNOD"],
+ "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+ "NetworkMode": "bridge",
+ "Devices": [],
+ "Ulimits": [{}],
+ "LogConfig": { "Type": "json-file", "Config": {} },
+ "SecurityOpt": [""],
+ "CgroupParent": ""
+ }
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Json Parameters:
+
+- **Binds** – A list of volume bindings for this container. Each volume
+ binding is a string of the form `container_path` (to create a new
+ volume for the container), `host_path:container_path` (to bind-mount
+ a host path into the container), or `host_path:container_path:ro`
+ (to make the bind-mount read-only inside the container).
+- **Links** - A list of links for the container. Each link entry should be of
+ of the form `container_name:alias`.
+- **LxcConf** - LXC specific configurations. These configurations will only
+ work when using the `lxc` execution driver.
+- **PortBindings** - A map of exposed container ports and the host port they
+ should map to. It should be specified in the form
+ `{ /: [{ "HostPort": "" }] }`
+ Take note that `port` is specified as a string and not an integer value.
+- **PublishAllPorts** - Allocates a random host port for all of a container's
+ exposed ports. Specified as a boolean value.
+- **Privileged** - Gives the container full access to the host. Specified as
+ a boolean value.
+- **ReadonlyRootfs** - Mount the container's root filesystem as read only.
+ Specified as a boolean value.
+- **Dns** - A list of dns servers for the container to use.
+- **DnsSearch** - A list of DNS search domains
+- **ExtraHosts** - A list of hostnames/IP mappings to be added to the
+ container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
+- **VolumesFrom** - A list of volumes to inherit from another container.
+ Specified in the form `[:]`
+- **CapAdd** - A list of kernel capabilities to add to the container.
+- **Capdrop** - A list of kernel capabilities to drop from the container.
+- **RestartPolicy** – The behavior to apply when the container exits. The
+ value is an object with a `Name` property of either `"always"` to
+ always restart or `"on-failure"` to restart only when the container
+ exit code is non-zero. If `on-failure` is used, `MaximumRetryCount`
+ controls the number of times to retry before giving up.
+ The default is not to restart. (optional)
+ An ever increasing delay (double the previous delay, starting at 100mS)
+ is added before each restart to prevent flooding the server.
+- **NetworkMode** - Sets the networking mode for the container. Supported
+ values are: `bridge`, `host`, and `container:`
+- **Devices** - A list of devices to add to the container specified in the
+ form
+ `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
+- **Ulimits** - A list of ulimits to be set in the container, specified as
+ `{ "Name": , "Soft": , "Hard": }`, for example:
+ `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
+- **SecurityOpt**: A list of string values to customize labels for MLS
+ systems, such as SELinux.
+- **LogConfig** - Log configuration for the container, specified as
+ `{ "Type": "", "Config": {"key1": "val1"}}`.
+ Available types: `json-file`, `syslog`, `journald`, `none`.
+ `json-file` logging driver.
+- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+
+Status Codes:
+
+- **204** – no error
+- **304** – container already started
+- **404** – no such container
+- **500** – server error
+
+### Stop a container
+
+`POST /containers/(id)/stop`
+
+Stop the container `id`
+
+**Example request**:
+
+ POST /containers/e90e34656806/stop?t=5 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Query Parameters:
+
+- **t** – number of seconds to wait before killing the container
+
+Status Codes:
+
+- **204** – no error
+- **304** – container already stopped
+- **404** – no such container
+- **500** – server error
+
+### Restart a container
+
+`POST /containers/(id)/restart`
+
+Restart the container `id`
+
+**Example request**:
+
+ POST /containers/e90e34656806/restart?t=5 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Query Parameters:
+
+- **t** – number of seconds to wait before killing the container
+
+Status Codes:
+
+- **204** – no error
+- **404** – no such container
+- **500** – server error
+
+### Kill a container
+
+`POST /containers/(id)/kill`
+
+Kill the container `id`
+
+**Example request**:
+
+ POST /containers/e90e34656806/kill HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Query Parameters
+
+- **signal** - Signal to send to the container: integer or string like "SIGINT".
+ When not set, SIGKILL is assumed and the call will waits for the container to exit.
+
+Status Codes:
+
+- **204** – no error
+- **404** – no such container
+- **500** – server error
+
+### Rename a container
+
+`POST /containers/(id)/rename`
+
+Rename the container `id` to a `new_name`
+
+**Example request**:
+
+ POST /containers/e90e34656806/rename?name=new_name HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Query Parameters:
+
+- **name** – new name for the container
+
+Status Codes:
+
+- **204** – no error
+- **404** – no such container
+- **409** - conflict name already assigned
+- **500** – server error
+
+### Pause a container
+
+`POST /containers/(id)/pause`
+
+Pause the container `id`
+
+**Example request**:
+
+ POST /containers/e90e34656806/pause HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Status Codes:
+
+- **204** – no error
+- **404** – no such container
+- **500** – server error
+
+### Unpause a container
+
+`POST /containers/(id)/unpause`
+
+Unpause the container `id`
+
+**Example request**:
+
+ POST /containers/e90e34656806/unpause HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Status Codes:
+
+- **204** – no error
+- **404** – no such container
+- **500** – server error
+
+### Attach to a container
+
+`POST /containers/(id)/attach`
+
+Attach to the container `id`
+
+**Example request**:
+
+ POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 101 UPGRADED
+ Content-Type: application/vnd.docker.raw-stream
+ Connection: Upgrade
+ Upgrade: tcp
+
+ {{ STREAM }}
+
+Query Parameters:
+
+- **logs** – 1/True/true or 0/False/false, return logs. Default false
+- **stream** – 1/True/true or 0/False/false, return stream.
+ Default false
+- **stdin** – 1/True/true or 0/False/false, if stream=true, attach
+ to stdin. Default false
+- **stdout** – 1/True/true or 0/False/false, if logs=true, return
+ stdout log, if stream=true, attach to stdout. Default false
+- **stderr** – 1/True/true or 0/False/false, if logs=true, return
+ stderr log, if stream=true, attach to stderr. Default false
+
+Status Codes:
+
+- **101** – no error, hints proxy about hijacking
+- **200** – no error, no upgrade header found
+- **400** – bad parameter
+- **404** – no such container
+- **500** – server error
+
+ **Stream details**:
+
+ When using the TTY setting is enabled in
+ [`POST /containers/create`
+ ](/reference/api/docker_remote_api_v1.9/#create-a-container "POST /containers/create"),
+ the stream is the raw data from the process PTY and client's stdin.
+ When the TTY is disabled, then the stream is multiplexed to separate
+ stdout and stderr.
+
+ The format is a **Header** and a **Payload** (frame).
+
+ **HEADER**
+
+ The header will contain the information on which stream write the
+ stream (stdout or stderr). It also contain the size of the
+ associated frame encoded on the last 4 bytes (uint32).
+
+ It is encoded on the first 8 bytes like this:
+
+ header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
+
+ `STREAM_TYPE` can be:
+
+- 0: stdin (will be written on stdout)
+- 1: stdout
+- 2: stderr
+
+ `SIZE1, SIZE2, SIZE3, SIZE4` are the 4 bytes of
+ the uint32 size encoded as big endian.
+
+ **PAYLOAD**
+
+ The payload is the raw stream.
+
+ **IMPLEMENTATION**
+
+ The simplest way to implement the Attach protocol is the following:
+
+ 1. Read 8 bytes
+ 2. chose stdout or stderr depending on the first byte
+ 3. Extract the frame size from the last 4 bytes
+ 4. Read the extracted size and output it on the correct output
+ 5. Goto 1
+
+### Attach to a container (websocket)
+
+`GET /containers/(id)/attach/ws`
+
+Attach to the container `id` via websocket
+
+Implements websocket protocol handshake according to [RFC 6455](http://tools.ietf.org/html/rfc6455)
+
+**Example request**
+
+ GET /containers/e90e34656806/attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1
+
+**Example response**
+
+ {{ STREAM }}
+
+Query Parameters:
+
+- **logs** – 1/True/true or 0/False/false, return logs. Default false
+- **stream** – 1/True/true or 0/False/false, return stream.
+ Default false
+- **stdin** – 1/True/true or 0/False/false, if stream=true, attach
+ to stdin. Default false
+- **stdout** – 1/True/true or 0/False/false, if logs=true, return
+ stdout log, if stream=true, attach to stdout. Default false
+- **stderr** – 1/True/true or 0/False/false, if logs=true, return
+ stderr log, if stream=true, attach to stderr. Default false
+
+Status Codes:
+
+- **200** – no error
+- **400** – bad parameter
+- **404** – no such container
+- **500** – server error
+
+### Wait a container
+
+`POST /containers/(id)/wait`
+
+Block until container `id` stops, then returns the exit code
+
+**Example request**:
+
+ POST /containers/16253994b7c4/wait HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {"StatusCode": 0}
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such container
+- **500** – server error
+
+### Remove a container
+
+`DELETE /containers/(id)`
+
+Remove the container `id` from the filesystem
+
+**Example request**:
+
+ DELETE /containers/16253994b7c4?v=1 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 204 No Content
+
+Query Parameters:
+
+- **v** – 1/True/true or 0/False/false, Remove the volumes
+ associated to the container. Default false
+- **force** - 1/True/true or 0/False/false, Kill then remove the container.
+ Default false
+
+Status Codes:
+
+- **204** – no error
+- **400** – bad parameter
+- **404** – no such container
+- **500** – server error
+
+### Copy files or folders from a container
+
+`POST /containers/(id)/copy`
+
+Copy files or folders of container `id`
+
+**Example request**:
+
+ POST /containers/4fa6e0f0c678/copy HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "Resource": "test.txt"
+ }
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/x-tar
+
+ {{ TAR STREAM }}
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such container
+- **500** – server error
+
+## 2.2 Images
+
+### List Images
+
+`GET /images/json`
+
+**Example request**:
+
+ GET /images/json?all=0 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ [
+ {
+ "RepoTags": [
+ "ubuntu:12.04",
+ "ubuntu:precise",
+ "ubuntu:latest"
+ ],
+ "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+ "Created": 1365714795,
+ "Size": 131506275,
+ "VirtualSize": 131506275
+ },
+ {
+ "RepoTags": [
+ "ubuntu:12.10",
+ "ubuntu:quantal"
+ ],
+ "ParentId": "27cf784147099545",
+ "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+ "Created": 1364102658,
+ "Size": 24653,
+ "VirtualSize": 180116135
+ }
+ ]
+
+**Example request, with digest information**:
+
+ GET /images/json?digests=1 HTTP/1.1
+
+**Example response, with digest information**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ [
+ {
+ "Created": 1420064636,
+ "Id": "4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125",
+ "ParentId": "ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2",
+ "RepoDigests": [
+ "localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"
+ ],
+ "RepoTags": [
+ "localhost:5000/test/busybox:latest",
+ "playdate:latest"
+ ],
+ "Size": 0,
+ "VirtualSize": 2429728
+ }
+ ]
+
+The response shows a single image `Id` associated with two repositories
+(`RepoTags`): `localhost:5000/test/busybox`: and `playdate`. A caller can use
+either of the `RepoTags` values `localhost:5000/test/busybox:latest` or
+`playdate:latest` to reference the image.
+
+You can also use `RepoDigests` values to reference an image. In this response,
+the array has only one reference and that is to the
+`localhost:5000/test/busybox` repository; the `playdate` repository has no
+digest. You can reference this digest using the value:
+`localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d...`
+
+See the `docker run` and `docker build` commands for examples of digest and tag
+references on the command line.
+
+Query Parameters:
+
+- **all** – 1/True/true or 0/False/false, default false
+- **filters** – a json encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
+ - dangling=true
+ - label=`key` or `key=value` of an image label
+
+### Build image from a Dockerfile
+
+`POST /build`
+
+Build an image from a Dockerfile
+
+**Example request**:
+
+ POST /build HTTP/1.1
+
+ {{ TAR STREAM }}
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {"stream": "Step 1..."}
+ {"stream": "..."}
+ {"error": "Error...", "errorDetail": {"code": 123, "message": "Error..."}}
+
+The input stream must be a tar archive compressed with one of the
+following algorithms: identity (no compression), gzip, bzip2, xz.
+
+The archive must include a build instructions file, typically called
+`Dockerfile` at the root of the archive. The `dockerfile` parameter may be
+used to specify a different build instructions file by having its value be
+the path to the alternate build instructions file to use.
+
+The archive may include any number of other files,
+which will be accessible in the build context (See the [*ADD build
+command*](/reference/builder/#dockerbuilder)).
+
+The build will also be canceled if the client drops the connection by quitting
+or being killed.
+
+Query Parameters:
+
+- **dockerfile** - path within the build context to the Dockerfile. This is
+ ignored if `remote` is specified and points to an individual filename.
+- **t** – repository name (and optionally a tag) to be applied to
+ the resulting image in case of success
+- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the
+ URI specifies a filename, the file's contents are placed into a file
+ called `Dockerfile`.
+- **q** – suppress verbose build output
+- **nocache** – do not use the cache when building the image
+- **pull** - attempt to pull the image even if an older image exists locally
+- **rm** - remove intermediate containers after a successful build (default behavior)
+- **forcerm** - always remove intermediate containers (includes rm)
+- **memory** - set memory limit for build
+- **memswap** - Total memory (memory + swap), `-1` to disable swap
+- **cpushares** - CPU shares (relative weight)
+- **cpusetcpus** - CPUs in which to allow execution, e.g., `0-3`, `0,1`
+
+ Request Headers:
+
+- **Content-type** – should be set to `"application/tar"`.
+- **X-Registry-Config** – base64-encoded ConfigFile object
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Create an image
+
+`POST /images/create`
+
+Create an image, either by pulling it from the registry or by importing it
+
+**Example request**:
+
+ POST /images/create?fromImage=ubuntu HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {"status": "Pulling..."}
+ {"status": "Pulling", "progress": "1 B/ 100 B", "progressDetail": {"current": 1, "total": 100}}
+ {"error": "Invalid..."}
+ ...
+
+ When using this endpoint to pull an image from the registry, the
+ `X-Registry-Auth` header can be used to include
+ a base64-encoded AuthConfig object.
+
+Query Parameters:
+
+- **fromImage** – name of the image to pull
+- **fromSrc** – source to import. The value may be a URL from which the image
+ can be retrieved or `-` to read the image from the request body.
+- **repo** – repository
+- **tag** – tag
+- **registry** – the registry to pull from
+
+ Request Headers:
+
+- **X-Registry-Auth** – base64-encoded AuthConfig object
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+
+
+### Inspect an image
+
+`GET /images/(name)/json`
+
+Return low-level information on the image `name`
+
+**Example request**:
+
+ GET /images/ubuntu/json HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "Created": "2013-03-23T22:24:18.818426-07:00",
+ "Container": "3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
+ "ContainerConfig":
+ {
+ "Hostname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": false,
+ "AttachStderr": false,
+ "PortSpecs": null,
+ "Tty": true,
+ "OpenStdin": true,
+ "StdinOnce": false,
+ "Env": null,
+ "Cmd": ["/bin/bash"],
+ "Dns": null,
+ "Image": "ubuntu",
+ "Labels": {
+ "com.example.vendor": "Acme",
+ "com.example.license": "GPL",
+ "com.example.version": "1.0"
+ },
+ "Volumes": null,
+ "VolumesFrom": "",
+ "WorkingDir": ""
+ },
+ "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+ "Parent": "27cf784147099545",
+ "Size": 6824592
+ }
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such image
+- **500** – server error
+
+### Get the history of an image
+
+`GET /images/(name)/history`
+
+Return the history of the image `name`
+
+**Example request**:
+
+ GET /images/ubuntu/history HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ [
+ {
+ "Id": "b750fe79269d",
+ "Created": 1364102658,
+ "CreatedBy": "/bin/bash"
+ },
+ {
+ "Id": "27cf78414709",
+ "Created": 1364068391,
+ "CreatedBy": ""
+ }
+ ]
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such image
+- **500** – server error
+
+### Push an image on the registry
+
+`POST /images/(name)/push`
+
+Push the image `name` on the registry
+
+**Example request**:
+
+ POST /images/test/push HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {"status": "Pushing..."}
+ {"status": "Pushing", "progress": "1/? (n/a)", "progressDetail": {"current": 1}}}
+ {"error": "Invalid..."}
+ ...
+
+ If you wish to push an image on to a private registry, that image must already have been tagged
+ into a repository which references that registry host name and port. This repository name should
+ then be used in the URL. This mirrors the flow of the CLI.
+
+**Example request**:
+
+ POST /images/registry.acme.com:5000/test/push HTTP/1.1
+
+
+Query Parameters:
+
+- **tag** – the tag to associate with the image on the registry, optional
+
+Request Headers:
+
+- **X-Registry-Auth** – include a base64-encoded AuthConfig
+ object.
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such image
+- **500** – server error
+
+### Tag an image into a repository
+
+`POST /images/(name)/tag`
+
+Tag the image `name` into a repository
+
+**Example request**:
+
+ POST /images/test/tag?repo=myrepo&force=0&tag=v42 HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 201 OK
+
+Query Parameters:
+
+- **repo** – The repository to tag in
+- **force** – 1/True/true or 0/False/false, default false
+- **tag** - The new tag name
+
+Status Codes:
+
+- **201** – no error
+- **400** – bad parameter
+- **404** – no such image
+- **409** – conflict
+- **500** – server error
+
+### Remove an image
+
+`DELETE /images/(name)`
+
+Remove the image `name` from the filesystem
+
+**Example request**:
+
+ DELETE /images/test HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-type: application/json
+
+ [
+ {"Untagged": "3e2f21a89f"},
+ {"Deleted": "3e2f21a89f"},
+ {"Deleted": "53b4f83ac9"}
+ ]
+
+Query Parameters:
+
+- **force** – 1/True/true or 0/False/false, default false
+- **noprune** – 1/True/true or 0/False/false, default false
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such image
+- **409** – conflict
+- **500** – server error
+
+### Search images
+
+`GET /images/search`
+
+Search for an image on [Docker Hub](https://hub.docker.com).
+
+> **Note**:
+> The response keys have changed from API v1.6 to reflect the JSON
+> sent by the registry server to the docker daemon's request.
+
+**Example request**:
+
+ GET /images/search?term=sshd HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ [
+ {
+ "description": "",
+ "is_official": false,
+ "is_automated": false,
+ "name": "wma55/u1210sshd",
+ "star_count": 0
+ },
+ {
+ "description": "",
+ "is_official": false,
+ "is_automated": false,
+ "name": "jdswinbank/sshd",
+ "star_count": 0
+ },
+ {
+ "description": "",
+ "is_official": false,
+ "is_automated": false,
+ "name": "vgauthier/sshd",
+ "star_count": 0
+ }
+ ...
+ ]
+
+Query Parameters:
+
+- **term** – term to search
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+## 2.3 Misc
+
+### Check auth configuration
+
+`POST /auth`
+
+Get the default username and email
+
+**Example request**:
+
+ POST /auth HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "username":" hannibal",
+ "password: "xxxx",
+ "email": "hannibal@a-team.com",
+ "serveraddress": "https://index.docker.io/v1/"
+ }
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+
+Status Codes:
+
+- **200** – no error
+- **204** – no error
+- **500** – server error
+
+### Display system-wide information
+
+`GET /info`
+
+Display system-wide information
+
+**Example request**:
+
+ GET /info HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "Containers": 11,
+ "CpuCfsPeriod": true,
+ "CpuCfsQuota": true,
+ "Debug": false,
+ "DockerRootDir": "/var/lib/docker",
+ "Driver": "btrfs",
+ "DriverStatus": [[""]],
+ "ExecutionDriver": "native-0.1",
+ "ExperimentalBuild": false,
+ "HttpProxy": "http://test:test@localhost:8080",
+ "HttpsProxy": "https://test:test@localhost:8080",
+ "ID": "7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS",
+ "IPv4Forwarding": true,
+ "Images": 16,
+ "IndexServerAddress": "https://index.docker.io/v1/",
+ "InitPath": "/usr/bin/docker",
+ "InitSha1": "",
+ "KernelVersion": "3.12.0-1-amd64",
+ "Labels": [
+ "storage=ssd"
+ ],
+ "MemTotal": 2099236864,
+ "MemoryLimit": true,
+ "NCPU": 1,
+ "NEventsListener": 0,
+ "NFd": 11,
+ "NGoroutines": 21,
+ "Name": "prod-server-42",
+ "NoProxy": "9.81.1.160",
+ "OomKillDisable": true,
+ "OperatingSystem": "Boot2Docker",
+ "RegistryConfig": {
+ "IndexConfigs": {
+ "docker.io": {
+ "Mirrors": null,
+ "Name": "docker.io",
+ "Official": true,
+ "Secure": true
+ }
+ },
+ "InsecureRegistryCIDRs": [
+ "127.0.0.0/8"
+ ]
+ },
+ "SwapLimit": false,
+ "SystemTime": "2015-03-10T11:11:23.730591467-07:00"
+ }
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Show the docker version information
+
+`GET /version`
+
+Show the docker version information
+
+**Example request**:
+
+ GET /version HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "Version": "1.5.0",
+ "Os": "linux",
+ "KernelVersion": "3.18.5-tinycore64",
+ "GoVersion": "go1.4.1",
+ "GitCommit": "a8a31ef",
+ "Arch": "amd64",
+ "ApiVersion": "1.19"
+ }
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Ping the docker server
+
+`GET /_ping`
+
+Ping the docker server
+
+**Example request**:
+
+ GET /_ping HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: text/plain
+
+ OK
+
+Status Codes:
+
+- **200** - no error
+- **500** - server error
+
+### Create a new image from a container's changes
+
+`POST /commit`
+
+Create a new image from a container's changes
+
+**Example request**:
+
+ POST /commit?container=44c004db4b17&comment=message&repo=myrepo HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "Hostname": "",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": true,
+ "AttachStderr": true,
+ "PortSpecs": null,
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": null,
+ "Cmd": [
+ "date"
+ ],
+ "Volumes": {
+ "/tmp": {}
+ },
+ "WorkingDir": "",
+ "NetworkDisabled": false,
+ "ExposedPorts": {
+ "22/tcp": {}
+ }
+ }
+
+**Example response**:
+
+ HTTP/1.1 201 Created
+ Content-Type: application/vnd.docker.raw-stream
+
+ {"Id": "596069db4bf5"}
+
+Json Parameters:
+
+- **config** - the container's configuration
+
+Query Parameters:
+
+- **container** – source container
+- **repo** – repository
+- **tag** – tag
+- **comment** – commit message
+- **author** – author (e.g., "John Hannibal Smith
+ <[hannibal@a-team.com](mailto:hannibal%40a-team.com)>")
+
+Status Codes:
+
+- **201** – no error
+- **404** – no such container
+- **500** – server error
+
+### Monitor Docker's events
+
+`GET /events`
+
+Get container events from docker, either in real time via streaming, or via
+polling (using since).
+
+Docker containers will report the following events:
+
+ create, destroy, die, exec_create, exec_start, export, kill, oom, pause, restart, start, stop, unpause
+
+and Docker images will report:
+
+ untag, delete
+
+**Example request**:
+
+ GET /events?since=1374067924
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {"status": "create", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067924}
+ {"status": "start", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067924}
+ {"status": "stop", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067966}
+ {"status": "destroy", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067970}
+
+Query Parameters:
+
+- **since** – timestamp used for polling
+- **until** – timestamp used for polling
+- **filters** – a json encoded value of the filters (a map[string][]string) to process on the event list. Available filters:
+ - event=<string> -- event to filter
+ - image=<string> -- image to filter
+ - container=<string> -- container to filter
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Get a tarball containing all images in a repository
+
+`GET /images/(name)/get`
+
+Get a tarball containing all images and metadata for the repository specified
+by `name`.
+
+If `name` is a specific name and tag (e.g. ubuntu:latest), then only that image
+(and its parents) are returned. If `name` is an image ID, similarly only that
+image (and its parents) are returned, but with the exclusion of the
+'repositories' file in the tarball, as there were no image names referenced.
+
+See the [image tarball format](#image-tarball-format) for more details.
+
+**Example request**
+
+ GET /images/ubuntu/get
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/x-tar
+
+ Binary data stream
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Get a tarball containing all images.
+
+`GET /images/get`
+
+Get a tarball containing all images and metadata for one or more repositories.
+
+For each value of the `names` parameter: if it is a specific name and tag (e.g.
+ubuntu:latest), then only that image (and its parents) are returned; if it is
+an image ID, similarly only that image (and its parents) are returned and there
+would be no names referenced in the 'repositories' file for this image ID.
+
+See the [image tarball format](#image-tarball-format) for more details.
+
+**Example request**
+
+ GET /images/get?names=myname%2Fmyapp%3Alatest&names=busybox
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/x-tar
+
+ Binary data stream
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Load a tarball with a set of images and tags into docker
+
+`POST /images/load`
+
+Load a set of images and tags into the docker repository.
+See the [image tarball format](#image-tarball-format) for more details.
+
+**Example request**
+
+ POST /images/load
+
+ Tarball in body
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+
+Status Codes:
+
+- **200** – no error
+- **500** – server error
+
+### Image tarball format
+
+An image tarball contains one directory per image layer (named using its long ID),
+each containing three files:
+
+1. `VERSION`: currently `1.0` - the file format version
+2. `json`: detailed layer information, similar to `docker inspect layer_id`
+3. `layer.tar`: A tarfile containing the filesystem changes in this layer
+
+The `layer.tar` file will contain `aufs` style `.wh..wh.aufs` files and directories
+for storing attribute changes and deletions.
+
+If the tarball defines a repository, there will also be a `repositories` file at
+the root that contains a list of repository and tag names mapped to layer IDs.
+
+```
+{"hello-world":
+ {"latest": "565a9d68a73f6706862bfe8409a7f659776d4d60a8d096eb4a3cbce6999cc2a1"}
+}
+```
+
+### Exec Create
+
+`POST /containers/(id)/exec`
+
+Sets up an exec instance in a running container `id`
+
+**Example request**:
+
+ POST /containers/e90e34656806/exec HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "AttachStdin": false,
+ "AttachStdout": true,
+ "AttachStderr": true,
+ "Tty": false,
+ "Cmd": [
+ "date"
+ ],
+ }
+
+**Example response**:
+
+ HTTP/1.1 201 OK
+ Content-Type: application/json
+
+ {
+ "Id": "f90e34656806"
+ "Warnings":[]
+ }
+
+Json Parameters:
+
+- **AttachStdin** - Boolean value, attaches to stdin of the exec command.
+- **AttachStdout** - Boolean value, attaches to stdout of the exec command.
+- **AttachStderr** - Boolean value, attaches to stderr of the exec command.
+- **Tty** - Boolean value to allocate a pseudo-TTY
+- **Cmd** - Command to run specified as a string or an array of strings.
+
+
+Status Codes:
+
+- **201** – no error
+- **404** – no such container
+
+### Exec Start
+
+`POST /exec/(id)/start`
+
+Starts a previously set up exec instance `id`. If `detach` is true, this API
+returns after starting the `exec` command. Otherwise, this API sets up an
+interactive session with the `exec` command.
+
+**Example request**:
+
+ POST /exec/e90e34656806/start HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "Detach": false,
+ "Tty": false,
+ }
+
+**Example response**:
+
+ HTTP/1.1 201 OK
+ Content-Type: application/json
+
+ {{ STREAM }}
+
+Json Parameters:
+
+- **Detach** - Detach from the exec command
+- **Tty** - Boolean value to allocate a pseudo-TTY
+
+Status Codes:
+
+- **201** – no error
+- **404** – no such exec instance
+
+ **Stream details**:
+ Similar to the stream behavior of `POST /container/(id)/attach` API
+
+### Exec Resize
+
+`POST /exec/(id)/resize`
+
+Resizes the tty session used by the exec command `id`.
+This API is valid only if `tty` was specified as part of creating and starting the exec command.
+
+**Example request**:
+
+ POST /exec/e90e34656806/resize HTTP/1.1
+ Content-Type: text/plain
+
+**Example response**:
+
+ HTTP/1.1 201 OK
+ Content-Type: text/plain
+
+Query Parameters:
+
+- **h** – height of tty session
+- **w** – width
+
+Status Codes:
+
+- **201** – no error
+- **404** – no such exec instance
+
+### Exec Inspect
+
+`GET /exec/(id)/json`
+
+Return low-level information about the exec command `id`.
+
+**Example request**:
+
+ GET /exec/11fb006128e8ceb3942e7c58d77750f24210e35f879dd204ac975c184b820b39/json HTTP/1.1
+
+**Example response**:
+
+ HTTP/1.1 200 OK
+ Content-Type: plain/text
+
+ {
+ "ID" : "11fb006128e8ceb3942e7c58d77750f24210e35f879dd204ac975c184b820b39",
+ "Running" : false,
+ "ExitCode" : 2,
+ "ProcessConfig" : {
+ "privileged" : false,
+ "user" : "",
+ "tty" : false,
+ "entrypoint" : "sh",
+ "arguments" : [
+ "-c",
+ "exit 2"
+ ]
+ },
+ "OpenStdin" : false,
+ "OpenStderr" : false,
+ "OpenStdout" : false,
+ "Container" : {
+ "State" : {
+ "Running" : true,
+ "Paused" : false,
+ "Restarting" : false,
+ "OOMKilled" : false,
+ "Pid" : 3650,
+ "ExitCode" : 0,
+ "Error" : "",
+ "StartedAt" : "2014-11-17T22:26:03.717657531Z",
+ "FinishedAt" : "0001-01-01T00:00:00Z"
+ },
+ "ID" : "8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c",
+ "Created" : "2014-11-17T22:26:03.626304998Z",
+ "Path" : "date",
+ "Args" : [],
+ "Config" : {
+ "Hostname" : "8f177a186b97",
+ "Domainname" : "",
+ "User" : "",
+ "AttachStdin" : false,
+ "AttachStdout" : false,
+ "AttachStderr" : false,
+ "PortSpecs" : null,
+ "ExposedPorts" : null,
+ "Tty" : false,
+ "OpenStdin" : false,
+ "StdinOnce" : false,
+ "Env" : [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ],
+ "Cmd" : [
+ "date"
+ ],
+ "Image" : "ubuntu",
+ "Volumes" : null,
+ "WorkingDir" : "",
+ "Entrypoint" : null,
+ "NetworkDisabled" : false,
+ "MacAddress" : "",
+ "OnBuild" : null,
+ "SecurityOpt" : null
+ },
+ "Image" : "5506de2b643be1e6febbf3b8a240760c6843244c41e12aa2f60ccbb7153d17f5",
+ "NetworkSettings" : {
+ "IPAddress" : "172.17.0.2",
+ "IPPrefixLen" : 16,
+ "MacAddress" : "02:42:ac:11:00:02",
+ "Gateway" : "172.17.42.1",
+ "Bridge" : "docker0",
+ "PortMapping" : null,
+ "Ports" : {}
+ },
+ "ResolvConfPath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/resolv.conf",
+ "HostnamePath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/hostname",
+ "HostsPath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/hosts",
+ "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log",
+ "Name" : "/test",
+ "Driver" : "aufs",
+ "ExecDriver" : "native-0.2",
+ "MountLabel" : "",
+ "ProcessLabel" : "",
+ "AppArmorProfile" : "",
+ "RestartCount" : 0,
+ "Volumes" : {},
+ "VolumesRW" : {}
+ }
+ }
+
+Status Codes:
+
+- **200** – no error
+- **404** – no such exec instance
+- **500** - server error
+
+# 3. Going further
+
+## 3.1 Inside `docker run`
+
+As an example, the `docker run` command line makes the following API calls:
+
+- Create the container
+
+- If the status code is 404, it means the image doesn't exist:
+ - Try to pull it
+ - Then retry to create the container
+
+- Start the container
+
+- If you are not in detached mode:
+- Attach to the container, using logs=1 (to have stdout and
+ stderr from the container's start) and stream=1
+
+- If in detached mode or only stdin is attached:
+- Display the container's id
+
+## 3.2 Hijacking
+
+In this version of the API, /attach, uses hijacking to transport stdin,
+stdout and stderr on the same socket.
+
+To hint potential proxies about connection hijacking, Docker client sends
+connection upgrade headers similarly to websocket.
+
+ Upgrade: tcp
+ Connection: Upgrade
+
+When Docker daemon detects the `Upgrade` header, it will switch its status code
+from **200 OK** to **101 UPGRADED** and resend the same headers.
+
+This might change in the future.
+
+## 3.3 CORS Requests
+
+To set cross origin requests to the remote api please give values to
+"--api-cors-header" when running docker in daemon mode. Set * will allow all,
+default or blank means CORS disabled
+
+ $ docker -d -H="192.168.1.9:2375" --api-cors-header="http://foo.bar"
diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.md b/docs/sources/reference/api/docker_remote_api_v1.6.md
index d0f9661e5..cd8a73088 100644
--- a/docs/sources/reference/api/docker_remote_api_v1.6.md
+++ b/docs/sources/reference/api/docker_remote_api_v1.6.md
@@ -560,7 +560,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1)
diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.md b/docs/sources/reference/api/docker_remote_api_v1.7.md
index 6cdd60374..dade45fbc 100644
--- a/docs/sources/reference/api/docker_remote_api_v1.7.md
+++ b/docs/sources/reference/api/docker_remote_api_v1.7.md
@@ -505,7 +505,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1)
diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.md b/docs/sources/reference/api/docker_remote_api_v1.8.md
index 409e63a16..56260db86 100644
--- a/docs/sources/reference/api/docker_remote_api_v1.8.md
+++ b/docs/sources/reference/api/docker_remote_api_v1.8.md
@@ -553,7 +553,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1)
diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md
index 7ea3fc9ab..26c6b453e 100644
--- a/docs/sources/reference/api/docker_remote_api_v1.9.md
+++ b/docs/sources/reference/api/docker_remote_api_v1.9.md
@@ -557,7 +557,7 @@ Status Codes:
1. Read 8 bytes
2. chose stdout or stderr depending on the first byte
- 3. Extract the frame size from the last 4 byets
+ 3. Extract the frame size from the last 4 bytes
4. Read the extracted size and output it on the correct output
5. Goto 1)
@@ -675,7 +675,7 @@ Status Codes:
## 2.2 Images
-### List Images
+### List images
`GET /images/json`
@@ -1052,7 +1052,7 @@ Query Parameters:
Request Headers:
- **Content-type** – should be set to `"application/tar"`.
-- **X-Registry-Config** – base64-encoded ConfigFile objec
+- **X-Registry-Config** – base64-encoded ConfigFile object
Status Codes:
@@ -1119,7 +1119,7 @@ Status Codes:
- **200** – no error
- **500** – server error
-### Show the docker version information
+### Show the Docker version information
`GET /version`
@@ -1343,7 +1343,7 @@ Here are the steps of `docker run` :
In this version of the API, /attach, uses hijacking to transport stdin,
stdout and stderr on the same socket. This might change in the future.
-## 3.3 CORS Requests
+## 3.3 CORS requests
To enable cross origin requests to the remote api add the flag
"--api-enable-cors" when running docker in daemon mode.
diff --git a/docs/sources/reference/api/hub_registry_spec.md b/docs/sources/reference/api/hub_registry_spec.md
index 0e39784b8..b1481e3a0 100644
--- a/docs/sources/reference/api/hub_registry_spec.md
+++ b/docs/sources/reference/api/hub_registry_spec.md
@@ -1,4 +1,4 @@
-page_title: Registry Documentation
+page_title: Registry documentation
page_description: Documentation for docker Registry and Registry API
page_keywords: docker, registry, api, hub
@@ -115,7 +115,7 @@ supports:
It's possible to run:
- $ sudo docker pull https:///repositories/samalba/busybox
+ $ docker pull https:///repositories/samalba/busybox
In this case, Docker bypasses the Docker Hub. However the security is not
guaranteed (in case Registry A is corrupted) because there won't be any
@@ -679,7 +679,7 @@ On every request, a special header can be returned:
On the next request, the client will always pick a server from this
list.
-## Authentication & Authorization
+## Authentication and authorization
### On the Docker Hub
@@ -747,7 +747,7 @@ Next request:
GET /(...)
Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="
-## Document Version
+## Document version
- 1.0 : May 6th 2013 : initial release
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new
diff --git a/docs/sources/reference/api/images/event_state.gliffy b/docs/sources/reference/api/images/event_state.gliffy
new file mode 100644
index 000000000..2eb0f3a5d
--- /dev/null
+++ b/docs/sources/reference/api/images/event_state.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":1109,"height":539,"nodeIndex":335,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":26.46762966848334,"y":100},"max":{"x":1109,"y":538.0017856687341}},"objects":[{"x":83.0,"y":251.0,"rotation":0.0,"id":328,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":328,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":188,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-52.03237033151666,-0.9999999999999716],[87.0,-1.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":332,"width":67.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5233416311379174,"linePerpValue":null,"cardinalityType":null,"html":"docker run
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":74.0,"y":318.0,"rotation":0.0,"id":327,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":327,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":228,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-42.0,1.0],[96.0,1.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":333,"width":83.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5689443767164591,"linePerpValue":null,"cardinalityType":null,"html":"docker create
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":191.0,"y":409.0,"rotation":0.0,"id":325,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":325,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":193,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":215,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-21.0,1.0],[-61.0,1.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":331.0,"y":346.0,"rotation":0.0,"id":320,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":320,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":193,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-21.0,1.0],[-53.5,1.0],[-53.5,64.0],[-86.0,64.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":324,"width":63.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"docker rm
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":265.0,"y":245.0,"rotation":0.0,"id":319,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":319,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":188,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":191,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-20.0,5.0],[312.5,5.0],[312.5,55.0]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":872.0,"y":503.0,"rotation":0.0,"id":310,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":310,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":205,"py":0.0,"px":0.2928932188134524}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-60.03300858899104,-53.0],[-148.0,-151.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":735.0,"y":341.0,"rotation":0.0,"id":307,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":307,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":203,"py":0.2928932188134525,"px":1.1102230246251563E-16}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[0.0,0.0],[137.5,60.7157287525381]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":309,"width":83.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.37922003257116654,"linePerpValue":null,"cardinalityType":null,"html":"docker pause
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":1023.0,"y":446.0,"rotation":0.0,"id":298,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":298,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":213,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":205,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[39.5,-1.0],[39.5,24.0],[-158.0,24.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":313,"width":101.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.37286693198126664,"linePerpValue":null,"cardinalityType":null,"html":" docker unpause
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":904.0,"y":434.0,"rotation":0.0,"id":295,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":295,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":203,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":213,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[43.5,-24.0],[123.5,-24.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":411.0,"y":419.0,"rotation":0.0,"id":291,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":291,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":217,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":209,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.2659812842322253,1.0],[3.2659812842322253,-32.0],[-81.46803743153555,-32.0],[-81.46803743153555,-65.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":292,"width":21.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.4870188236535277,"linePerpValue":null,"cardinalityType":null,"html":"No
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":415.0,"y":419.0,"rotation":0.0,"id":289,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":289,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":217,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":191,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-0.7340187157677747,1.0],[-0.7340187157677747,-32.5],[162.5,-32.5],[162.5,-79.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":290,"width":26.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.46753493572435184,"linePerpValue":null,"cardinalityType":null,"html":"Yes
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":521.0,"y":209.0,"rotation":0.0,"id":287,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":287,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":195,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-11.0,-19.0],[-87.0,-19.0],[-87.0,84.0],[-163.0,84.0]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":988.0,"y":232.0,"rotation":0.0,"id":282,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":282,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":201,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[39.5,18.0],[-150.0,18.0],[-150.0,68.0],[-250.0,68.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[]},{"x":664.0,"y":493.0,"rotation":0.0,"id":276,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":276,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":207,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":236,"py":0.7071067811865475,"px":0.9999999999999998}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[6.0,-33.0],[-36.5,-33.0],[-36.5,-44.7157287525381],[-79.0,-44.7157287525381]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":678.0,"y":344.0,"rotation":0.0,"id":273,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":273,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":236,"py":0.29289321881345237,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.0,-4.0],[-45.5,-4.0],[-45.5,87.7157287525381],[-93.0,87.7157287525381]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":275,"width":57.0,"height":40.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.5,"linePerpValue":0.0,"cardinalityType":null,"html":"container
process
exited
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":566.0,"y":431.0,"rotation":0.0,"id":272,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":272,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":236,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":217,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-56.0,9.0],[-78.86700935788389,9.0],[-78.86700935788389,39.0],[-101.73401871576777,39.0]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":785.0,"y":119.0,"rotation":0.0,"id":270,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":270,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":199,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":209,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[5.0,1.0],[-455.46803743153555,1.0],[-455.46803743153555,165.0]],"lockSegments":{},"ortho":true}},"linkMap":[]},{"x":829.0,"y":172.0,"rotation":0.0,"id":269,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":269,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":248,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":199,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.5,-2.0],[-1.5,-32.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":661.0,"y":189.0,"rotation":0.0,"id":267,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":267,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":195,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[7.0,2.284271247461902],[-76.0,1.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":946.0,"y":319.0,"rotation":0.0,"id":263,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":263,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":197,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":233,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.5,1.0],[81.5,1.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":708.0,"y":286.0,"rotation":0.0,"id":256,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":256,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":211,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":254,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.5,-2.0],[-0.5,-76.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":258,"width":64.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3108108108108108,"linePerpValue":null,"cardinalityType":null,"html":"docker kill
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":710.0,"y":359.0,"rotation":0.0,"id":245,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":46,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":211,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":207,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-2.5,-5.0],[-2.5,81.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":247,"width":83.0,"height":27.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":" killed by
out-of-memory
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":761.0,"y":318.0,"rotation":0.0,"id":238,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":42,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":211,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":197,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-18.5,1.0],[111.5,2.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":240,"width":85.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.4363456059259962,"linePerpValue":null,"cardinalityType":null,"html":"docker restart
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":608.0,"y":319.0,"rotation":0.0,"id":232,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":37,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":191,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":211,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[7.0,1.0],[64.5,0.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":454.0,"y":325.0,"rotation":0.0,"id":231,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":36,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":209,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":191,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-89.46803743153555,-6.0],[86.0,-5.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":288,"width":77.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":0.3163731357954714,"linePerpValue":null,"cardinalityType":null,"html":"docker start
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":299.0,"y":321.0,"rotation":0.0,"id":230,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":35,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":228,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":209,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-54.0,-2.0],[-4.468037431535549,-2.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":294.53196256846445,"y":284.0,"rotation":0.0,"id":209,"width":70.0,"height":70.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.connector","order":20,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#e6b8af","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5555555555555554,"y":0.0,"rotation":0.0,"id":210,"width":66.88888888888889,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"stopped
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":540.0,"y":300.0,"rotation":0.0,"id":191,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":192,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"start
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":510.0,"y":170.0,"rotation":0.0,"id":195,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":6,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":196,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"kill
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":872.5,"y":300.0,"rotation":0.0,"id":197,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":8,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":198,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"die
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":790.0,"y":100.0,"rotation":0.0,"id":199,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":200,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"stop
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":790.0,"y":450.0,"rotation":0.0,"id":205,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":16,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":206,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"unpause
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":670.0,"y":440.0,"rotation":0.0,"id":207,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":18,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":208,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"OOM
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":672.5,"y":284.0,"rotation":0.0,"id":211,"width":70.0,"height":70.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.connector","order":22,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#b6d7a8","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5555555555555556,"y":0.0,"rotation":0.0,"id":212,"width":66.88888888888889,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"running
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":60.0,"y":375.0,"rotation":0.0,"id":215,"width":70.0,"height":70.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.connector","order":26,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#b7b7b7","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5555555555555556,"y":0.0,"rotation":0.0,"id":216,"width":66.88888888888889,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"deleted
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":349.53196256846445,"y":420.0,"rotation":0.0,"id":227,"width":130.46803743153555,"height":116.23401871576777,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":32,"lockAspectRatio":false,"lockShape":false,"children":[{"x":-6.765981284232225,"y":76.0,"rotation":45.0,"id":223,"width":80.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"Restart
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[]},{"x":57.234018715767775,"y":75.0,"rotation":315.0,"id":219,"width":80.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":30,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"Policy
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[]},{"x":14.734018715767775,"y":0.0,"rotation":0.0,"id":217,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.decision","order":28,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.diamond.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":218,"width":96.0,"height":27.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"Should restart?
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]}]},{"x":1027.5,"y":375.0,"rotation":0.0,"id":213,"width":70.0,"height":70.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.connector","order":24,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#fce5cd","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5555555555555556,"y":0.0,"rotation":0.0,"id":214,"width":66.88888888888889,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"paused
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":872.5,"y":390.0,"rotation":0.0,"id":203,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":14,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":204,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"pause
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":510.0,"y":420.0,"rotation":0.0,"id":236,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":237,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"die
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":790.0,"y":170.0,"rotation":0.0,"id":248,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":249,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"die
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":670.0,"y":170.0,"rotation":0.0,"id":254,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":53,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":255,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"die
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":740.0,"y":323.0,"rotation":0.0,"id":250,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":50,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":248,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-10.0,-33.0],[87.5,-113.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[{"x":0.0,"y":0.0,"rotation":0.0,"id":253,"width":73.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"docker stop
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":1027.5,"y":300.0,"rotation":0.0,"id":233,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":234,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"start
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":1027.5,"y":230.0,"rotation":0.0,"id":201,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":202,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"restart
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":1066.5,"y":298.0,"rotation":0.0,"id":264,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":264,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":233,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":201,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.5,2.0],[-1.5,-28.0]],"lockSegments":{},"ortho":false}},"linkMap":[]},{"x":170.0,"y":299.0,"rotation":0.0,"id":228,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":33,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":229,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"create
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":170.0,"y":230.0,"rotation":0.0,"id":188,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":190,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"create
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]},{"x":170.0,"y":390.0,"rotation":0.0,"id":193,"width":75.0,"height":40.0,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.process","order":4,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":1.5,"y":0.0,"rotation":0.0,"id":194,"width":72.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"destroy
","tid":null,"valign":"middle","vposition":"none","hposition":"none"}}}]}],"shapeStyles":{"com.gliffy.shape.uml.uml_v2.state_machine":{"fill":"#e2e2e2","stroke":"#000000","strokeWidth":2},"com.gliffy.shape.flowchart.flowchart_v1.default":{"fill":"#b7b7b7","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"endArrow":1}},"textStyles":{"global":{"bold":true,"color":"#000000"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.images"]},"embeddedResources":{"index":0,"resources":[]}}
\ No newline at end of file
diff --git a/docs/sources/reference/api/images/event_state.png b/docs/sources/reference/api/images/event_state.png
new file mode 100644
index 000000000..aeeaca347
Binary files /dev/null and b/docs/sources/reference/api/images/event_state.png differ
diff --git a/docs/sources/reference/api/registry_api_client_libraries.md b/docs/sources/reference/api/registry_api_client_libraries.md
index 811ac859e..965ba4603 100644
--- a/docs/sources/reference/api/registry_api_client_libraries.md
+++ b/docs/sources/reference/api/registry_api_client_libraries.md
@@ -1,8 +1,8 @@
-page_title: Registry API Client Libraries
+page_title: Registry API client libraries
page_description: Various client libraries available to use with the Docker registry API
page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala
-# Docker Registry 1.0 API Client Libraries
+# Docker Registry 1.0 API client libraries
These libraries have not been tested by the Docker maintainers for
compatibility. Please file issues with the library owners. If you find
diff --git a/docs/sources/reference/api/remote_api_client_libraries.md b/docs/sources/reference/api/remote_api_client_libraries.md
index d79bbd89a..7b237c267 100644
--- a/docs/sources/reference/api/remote_api_client_libraries.md
+++ b/docs/sources/reference/api/remote_api_client_libraries.md
@@ -1,8 +1,8 @@
-page_title: Remote API Client Libraries
+page_title: Remote API client libraries
page_description: Various client libraries available to use with the Docker remote API
page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala
-# Docker Remote API Client Libraries
+# Docker Remote API client libraries
These libraries have not been tested by the Docker maintainers for
compatibility. Please file issues with the library owners. If you find
@@ -43,23 +43,35 @@ will add the libraries here.
Active |
+ | Dart |
+ bwu_docker |
+ https://github.com/bwu-dart/bwu_docker |
+ Active |
+
+
| Go |
go-dockerclient |
https://github.com/fsouza/go-dockerclient |
Active |
-
+
| Go |
dockerclient |
https://github.com/samalba/dockerclient |
Active |
-
+
| Groovy |
docker-client |
https://github.com/gesellix-docker/docker-client |
Active |
+
+ | Haskell |
+ docker-hs |
+ https://github.com/denibertovic/docker-hs |
+ Active |
+
| Java |
docker-java |
diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md
index 025b253b0..fb4d5ce1d 100644
--- a/docs/sources/reference/builder.md
+++ b/docs/sources/reference/builder.md
@@ -1,8 +1,8 @@
-page_title: Dockerfile Reference
+page_title: Dockerfile reference
page_description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image.
page_keywords: builder, docker, Dockerfile, automation, image creation
-# Dockerfile Reference
+# Dockerfile reference
**Docker can build images automatically** by reading the instructions
from a `Dockerfile`. A `Dockerfile` is a text document that contains all
@@ -26,7 +26,7 @@ This file will describe the steps to assemble the image.
Then call `docker build` with the path of your source repository as the argument
(for example, `.`):
- $ sudo docker build .
+ $ docker build .
The path to the source repository defines where to find the *context* of
the build. The build is run by the Docker daemon, not by the CLI, so the
@@ -41,15 +41,16 @@ whole context must be transferred to the daemon. The Docker CLI reports
> repository, the entire contents of your hard drive will get sent to the daemon (and
> thus to the machine running the daemon). You probably don't want that.
-In most cases, it's best to put each Dockerfile in an empty directory, and then add only
-the files needed for building that Dockerfile to that directory. To further speed up the
-build, you can exclude files and directories by adding a `.dockerignore` file to the same
-directory.
+In most cases, it's best to put each Dockerfile in an empty directory. Then,
+only add the files needed for building the Dockerfile to the directory. To
+increase the build's performance, you can exclude files and directories by
+adding a `.dockerignore` file to the directory. For information about how to
+[create a `.dockerignore` file](#the-dockerignore-file) on this page.
You can specify a repository and tag at which to save the new image if
the build succeeds:
- $ sudo docker build -t shykes/myapp .
+ $ docker build -t shykes/myapp .
The Docker daemon will run your steps one-by-one, committing the result
to a new image if necessary, before finally outputting the ID of your
@@ -65,7 +66,7 @@ accelerating `docker build` significantly (indicated by `Using cache` -
see the [`Dockerfile` Best Practices
guide](/articles/dockerfile_best-practices/#build-cache) for more information):
- $ sudo docker build -t SvenDowideit/ambassador .
+ $ docker build -t SvenDowideit/ambassador .
Uploading context 10.24 kB
Uploading context
Step 1 : FROM docker-ut
@@ -105,7 +106,7 @@ be treated as an argument. This allows statements like:
Here is the set of instructions you can use in a `Dockerfile` for building
images.
-### Environment Replacement
+### Environment replacement
> **Note**: prior to 1.3, `Dockerfile` environment variables were handled
> similarly, in that they would be replaced as described below. However, there
@@ -113,18 +114,30 @@ images.
> replacement at the time. After 1.3 this behavior will be preserved and
> canonical.
-Environment variables (declared with [the `ENV` statement](#env)) can also be used in
-certain instructions as variables to be interpreted by the `Dockerfile`. Escapes
-are also handled for including variable-like syntax into a statement literally.
+Environment variables (declared with [the `ENV` statement](#env)) can also be
+used in certain instructions as variables to be interpreted by the
+`Dockerfile`. Escapes are also handled for including variable-like syntax
+into a statement literally.
Environment variables are notated in the `Dockerfile` either with
`$variable_name` or `${variable_name}`. They are treated equivalently and the
brace syntax is typically used to address issues with variable names with no
whitespace, like `${foo}_bar`.
+The `${variable_name}` syntax also supports a few of the standard `bash`
+modifiers as specified below:
+
+* `${variable:-word}` indicates that if `variable` is set then the result
+ will be that value. If `variable` is not set then `word` will be the result.
+* `${variable:+word}` indicates that if `variable` is set then `word` will be
+ the result, otherwise the result is the empty string.
+
+In all cases, `word` can be any string, including additional environment
+variables.
+
Escaping is possible by adding a `\` before the variable: `\$foo` or `\${foo}`,
for example, will translate to `$foo` and `${foo}` literals respectively.
-
+
Example (parsed representation is displayed after the `#`):
FROM busybox
@@ -146,7 +159,7 @@ The instructions that handle environment variables in the `Dockerfile` are:
`ONBUILD` instructions are **NOT** supported for environment replacement, even
the instructions above.
-Environment variable subtitution will use the same value for each variable
+Environment variable substitution will use the same value for each variable
throughout the entire command. In other words, in this example:
ENV abc=hello
@@ -157,43 +170,67 @@ will result in `def` having a value of `hello`, not `bye`. However,
`ghi` will have a value of `bye` because it is not part of the same command
that set `abc` to `bye`.
-## The `.dockerignore` file
+### .dockerignore file
-If a file named `.dockerignore` exists in the source repository, then it
-is interpreted as a newline-separated list of exclusion patterns.
-Exclusion patterns match files or directories relative to the source repository
-that will be excluded from the context. Globbing is done using Go's
+If a file named `.dockerignore` exists in the root of `PATH`, then Docker
+interprets it as a newline-separated list of exclusion patterns. Docker excludes
+files or directories relative to `PATH` that match these exclusion patterns. If
+there are any `.dockerignore` files in `PATH` subdirectories, Docker treats
+them as normal files.
+
+Filepaths in `.dockerignore` are absolute with the current directory as the
+root. Wildcards are allowed but the search is not recursive. Globbing (file name
+expansion) is done using Go's
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.
-> **Note**:
-> The `.dockerignore` file can even be used to ignore the `Dockerfile` and
-> `.dockerignore` files. This might be useful if you are copying files from
-> the root of the build context into your new containter but do not want to
-> include the `Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`).
+You can specify exceptions to exclusion rules. To do this, simply prefix a
+pattern with an `!` (exclamation mark) in the same way you would in a
+`.gitignore` file. Currently there is no support for regular expressions.
+Formats like `[^temp*]` are ignored.
-The following example shows the use of the `.dockerignore` file to exclude the
-`.git` directory from the context. Its effect can be seen in the changed size of
-the uploaded context.
+The following is an example `.dockerignore` file:
+
+```
+ */temp*
+ */*/temp*
+ temp?
+ *.md
+ !LICENCSE.md
+```
+
+This file causes the following build behavior:
+
+| Rule | Behavior |
+|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `*/temp*` | Exclude all files with names starting with`temp` in any subdirectory below the root directory. For example, a file named`/somedir/temporary.txt` is ignored. |
+| `*/*/temp*` | Exclude files starting with name `temp` from any subdirectory that is two levels below the root directory. For example, the file `/somedir/subdir/temporary.txt` is ignored. |
+| `temp?` | Exclude the files that match the pattern in the root directory. For example, the files `tempa`, `tempb` in the root directory are ignored. |
+| `*.md ` | Exclude all markdown files. |
+| `!LICENSE.md` | Exception to the exclude all Markdown files is this file, `LICENSE.md`, include this file in the build. |
+
+The placement of `!` exception rules influences the matching algorithm; the
+last line of the `.dockerignore` that matches a particular file determines
+whether it is included or excluded. In the above example, the `LICENSE.md` file
+matches both the `*.md` and `!LICENSE.md` rule. If you reverse the lines in the
+example:
+
+```
+ */temp*
+ */*/temp*
+ temp?
+ !LICENCSE.md
+ *.md
+```
+
+The build would exclude `LICENSE.md` because the last `*.md` rule adds all
+Markdown files back onto the ignore list. The `!LICENSE.md` rule has no effect
+because the subsequent `*.md` rule overrides it.
+
+You can even use the `.dockerignore` file to ignore the `Dockerfile` and
+`.dockerignore` files. This is useful if you are copying files from the root of
+the build context into your new container but do not want to include the
+`Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`).
- $ sudo docker build .
- Uploading context 18.829 MB
- Uploading context
- Step 0 : FROM busybox
- ---> 769b9341d937
- Step 1 : CMD echo Hello World
- ---> Using cache
- ---> 99cc1ad10469
- Successfully built 99cc1ad10469
- $ echo ".git" > .dockerignore
- $ sudo docker build .
- Uploading context 6.76 MB
- Uploading context
- Step 0 : FROM busybox
- ---> 769b9341d937
- Step 1 : CMD echo Hello World
- ---> Using cache
- ---> 99cc1ad10469
- Successfully built 99cc1ad10469
## FROM
@@ -276,7 +313,7 @@ guide](/articles/dockerfile_best-practices/#build-cache) for more information.
The cache for `RUN` instructions can be invalidated by `ADD` instructions. See
[below](#add) for details.
-### Known Issues (RUN)
+### Known issues (RUN)
- [Issue 783](https://github.com/docker/docker/issues/783) is about file
permissions problems that can occur when using the AUFS file system. You
@@ -287,7 +324,7 @@ The cache for `RUN` instructions can be invalidated by `ADD` instructions. See
the layers with `dirperm1` option. More details on `dirperm1` option can be
found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
- If your system doesnt have support for `dirperm1`, the issue describes a workaround.
+ If your system doesn't have support for `dirperm1`, the issue describes a workaround.
## CMD
@@ -356,14 +393,13 @@ default specified in `CMD`.
The `LABEL` instruction adds metadata to an image. A `LABEL` is a
key-value pair. To include spaces within a `LABEL` value, use quotes and
-blackslashes as you would in command-line parsing.
+backslashes as you would in command-line parsing.
LABEL "com.example.vendor"="ACME Incorporated"
An image can have more than one label. To specify multiple labels, separate each
-key-value pair by an EOL.
+key-value pair with whitespace.
- LABEL com.example.label-without-value
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
@@ -373,6 +409,8 @@ Docker recommends combining labels in a single `LABEL` instruction where
possible. Each `LABEL` instruction produces a new layer which can result in an
inefficient image if you use many labels. This example results in four image
layers.
+
+ LABEL multi.label1="value1" multi.label2="value2" other="value3"
Labels are additive including `LABEL`s in `FROM` images. As the system
encounters and then applies a new label, new `key`s override any previous labels
@@ -380,6 +418,16 @@ with identical keys.
To view an image's labels, use the `docker inspect` command.
+ "Labels": {
+ "com.example.vendor": "ACME Incorporated"
+ "com.example.label-with-value": "foo",
+ "version": "1.0",
+ "description": "This text illustrates that label-values can span multiple lines.",
+ "multi.label1": "value1",
+ "multi.label2": "value2",
+ "other": "value3"
+ },
+
## EXPOSE
EXPOSE [...]
@@ -844,11 +892,11 @@ consider the following Dockerfile snippet:
FROM ubuntu
RUN mkdir /myvol
- RUN echo "hello world" > /myvol/greating
+ RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
This Dockerfile results in an image that causes `docker run`, to
-create a new mount point at `/myvol` and copy the `greating` file
+create a new mount point at `/myvol` and copy the `greeting` file
into the newly created volume.
> **Note**:
@@ -950,7 +998,7 @@ For example you might add something like this:
> **Warning**: The `ONBUILD` instruction may not trigger `FROM` or `MAINTAINER` instructions.
-## Dockerfile Examples
+## Dockerfile examples
# Nginx
#
diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md
index 6c3eb1c8d..c5e276e3a 100644
--- a/docs/sources/reference/commandline/cli.md
+++ b/docs/sources/reference/commandline/cli.md
@@ -9,18 +9,78 @@ page_keywords: Docker, Docker documentation, CLI, command line
To list available commands, either run `docker` with no parameters
or execute `docker help`:
- $ sudo docker
+ $ docker
Usage: docker [OPTIONS] COMMAND [arg...]
-H, --host=[]: The socket(s) to bind to in daemon mode, specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.
A self-sufficient runtime for Linux containers.
...
+Depending on your Docker system configuration, you may be required
+to preface each `docker` command with `sudo`. To avoid having to use `sudo` with
+the `docker` command, your system administrator can create a Unix group called
+`docker` and add users to it.
+
+For more information about installing Docker or `sudo` configuration, refer to
+the [installation](/installation) instructions for your operating system.
+
+## Environment variables
+
+For easy reference, the following list of environment variables are supported
+by the `docker` command line:
+
+* `DOCKER_CERT_PATH` The location of your authentication keys.
+* `DOCKER_DRIVER` The graph driver to use.
+* `DOCKER_HOST` Daemon socket to connect to.
+* `DOCKER_NOWARN_KERNEL_VERSION` Prevent warnings that your Linux kernel is unsuitable for Docker.
+* `DOCKER_RAMDISK` If set this will disable 'pivot_root'.
+* `DOCKER_TLS_VERIFY` When set Docker uses TLS and verifies the remote.
+* `DOCKER_TMPDIR` Location for temporary Docker files.
+
+Because Docker is developed using 'Go', you can also use any environment
+variables used by the 'Go' runtime. In particular, you may find these useful:
+
+* `HTTP_PROXY`
+* `HTTPS_PROXY`
+* `NO_PROXY`
+
+These Go environment variables are case-insensitive. See the
+[Go specification](http://golang.org/pkg/net/http/) for details on these
+variables.
+
+## Configuration files
+
+The Docker command line stores its configuration files in a directory called
+`.docker` within your `HOME` directory. Docker manages most of the files in
+`.docker` and you should not modify them. However, you *can modify* the
+`.docker/config.json` file to control certain aspects of how the `docker`
+command behaves.
+
+Currently, you can modify the `docker` command behavior using environment
+variables or command-line options. You can also use options within
+`config.json` to modify some of the same behavior. When using these
+mechanisms, you must keep in mind the order of precedence among them. Command
+line options override environment variables and environment variables override
+properties you specify in a `config.json` file.
+
+The `config.json` file stores a JSON encoding of a single `HttpHeaders`
+property. The property specifies a set of headers to include in all
+messages sent from the Docker client to the daemon. Docker does not try to
+interpret or understand these header; it simply puts them into the messages.
+Docker does not allow these headers to change any headers it sets for itself.
+
+Following is a sample `config.json` file:
+
+ {
+ "HttpHeaders: {
+ "MyHeader": "MyValue"
+ }
+ }
## Help
To list the help on any command just execute the command, followed by the `--help` option.
- $ sudo docker run --help
+ $ docker run --help
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
@@ -53,13 +113,19 @@ be set to the non-default value by explicitly setting them to `false`:
### Multi
-Options like `-a=[]` indicate they can be specified multiple times:
+You can specify options like `-a=[]` multiple times in a single command line,
+for example in these commands:
- $ sudo docker run -a stdin -a stdout -a stderr -i -t ubuntu /bin/bash
+ $ docker run -a stdin -a stdout -i -t ubuntu /bin/bash
+ $ docker run -a stdin -a stdout -a stderr ubuntu /bin/ls
-Sometimes this can use a more complex value string, as for `-v`:
+Sometimes, multiple options can call for a more complex value string as for `-v`:
- $ sudo docker run -v /host:/container example/mysql
+ $ docker run -v /host:/container example/mysql
+
+> **Note**:
+> Do not use the `-t` and `-a stderr` options together due to limitations
+> in the `pty` implementation. All `stderr` in `pty` mode simply goes to `stdout`.
### Strings and Integers
@@ -79,9 +145,14 @@ expect an integer, and they can only be specified once.
--bip="" Specify network bridge IP
-D, --debug=false Enable debug mode
-d, --daemon=false Enable daemon mode
+ --default-gateway="" Container default gateway IPv4 address
+ --default-gateway-v6="" Container default gateway IPv6 address
--dns=[] DNS server to use
--dns-search=[] DNS search domains to use
+ --default-ulimit=[] Set default ulimit settings for containers
-e, --exec-driver="native" Exec driver to use
+ --exec-opt=[] Set exec driver options
+ --exec-root="/var/run/docker" Root of the Docker execdriver
--fixed-cidr="" IPv4 subnet for fixed IPs
--fixed-cidr-v6="" IPv6 subnet for fixed IPs
-G, --group="docker" Group for the unix socket
@@ -97,7 +168,7 @@ expect an integer, and they can only be specified once.
--ipv6=false Enable IPv6 networking
-l, --log-level="info" Set the logging level
--label=[] Set key=value labels to the daemon
- --log-driver="json-file" Container's logging driver (json-file/none)
+ --log-driver="json-file" Default driver for container logs
--mtu=0 Set the containers network MTU
-p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file
--registry-mirror=[] Preferred Docker registry mirror
@@ -109,8 +180,8 @@ expect an integer, and they can only be specified once.
--tlscert="~/.docker/cert.pem" Path to TLS certificate file
--tlskey="~/.docker/key.pem" Path to TLS key file
--tlsverify=false Use TLS and verify the remote
+ --userland-proxy=true Use userland proxy for loopback traffic
-v, --version=false Print version information and quit
- --default-ulimit=[] Set default ulimit settings for containers.
Options with [] may be specified multiple times.
@@ -160,19 +231,19 @@ time using multiple `-H` options:
The Docker client will honor the `DOCKER_HOST` environment variable to set
the `-H` flag for the client.
- $ sudo docker -H tcp://0.0.0.0:2375 ps
+ $ docker -H tcp://0.0.0.0:2375 ps
# or
$ export DOCKER_HOST="tcp://0.0.0.0:2375"
- $ sudo docker ps
+ $ docker ps
# both are equal
Setting the `DOCKER_TLS_VERIFY` environment variable to any value other than the empty
string is equivalent to setting the `--tlsverify` flag. The following are equivalent:
- $ sudo docker --tlsverify ps
+ $ docker --tlsverify ps
# or
$ export DOCKER_TLS_VERIFY=1
- $ sudo docker ps
+ $ docker ps
The Docker client will honor the `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`
environment variables (or the lowercase versions thereof). `HTTPS_PROXY` takes
@@ -181,7 +252,7 @@ precedence over `HTTP_PROXY`.
### Daemon storage-driver option
The Docker daemon has support for several different image layer storage drivers: `aufs`,
-`devicemapper`, `btrfs` and `overlay`.
+`devicemapper`, `btrfs`, `zfs` and `overlay`.
The `aufs` driver is the oldest, but is based on a Linux kernel patch-set that
is unlikely to be merged into the main kernel. These are also known to cause some
@@ -203,6 +274,11 @@ explains how to tune your existing setup without the use of options.
The `btrfs` driver is very fast for `docker build` - but like `devicemapper` does not
share executable memory between devices. Use `docker -d -s btrfs -g /mnt/btrfs_partition`.
+The `zfs` driver is probably not fast as `btrfs` but has a longer track record
+on stability. Thanks to `Single Copy ARC` shared blocks between clones will be
+cached only once. Use `docker -d -s zfs`. To select a different zfs filesystem
+set `zfs.fsname` option as described in [Storage driver options](#storage-driver-options):
+
The `overlay` is a very fast union filesystem. It is now merged in the main
Linux kernel as of [3.18.0](https://lkml.org/lkml/2014/10/26/137).
Call `docker -d -s overlay` to use it.
@@ -213,10 +289,10 @@ Call `docker -d -s overlay` to use it.
#### Storage driver options
Particular storage-driver can be configured with options specified with
-`--storage-opt` flags. The only driver accepting options is `devicemapper` as
-of now. All its options are prefixed with `dm`.
+`--storage-opt` flags. Options for `devicemapper` are prefixed with `dm` and
+options for `zfs` start with `zfs`.
-Currently supported options are:
+Currently supported options of `devicemapper`:
* `dm.basesize`
@@ -236,7 +312,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt dm.basesize=20G
+ $ docker -d --storage-opt dm.basesize=20G
* `dm.loopdatasize`
@@ -246,7 +322,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt dm.loopdatasize=200G
+ $ docker -d --storage-opt dm.loopdatasize=200G
* `dm.loopmetadatasize`
@@ -257,7 +333,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt dm.loopmetadatasize=4G
+ $ docker -d --storage-opt dm.loopmetadatasize=4G
* `dm.fs`
@@ -266,7 +342,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt dm.fs=xfs
+ $ docker -d --storage-opt dm.fs=xfs
* `dm.mkfsarg`
@@ -274,7 +350,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt "dm.mkfsarg=-O ^has_journal"
+ $ docker -d --storage-opt "dm.mkfsarg=-O ^has_journal"
* `dm.mountopt`
@@ -282,7 +358,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt dm.mountopt=nodiscard
+ $ docker -d --storage-opt dm.mountopt=nodiscard
* `dm.datadev`
@@ -294,7 +370,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d \
+ $ docker -d \
--storage-opt dm.datadev=/dev/sdb1 \
--storage-opt dm.metadatadev=/dev/sdc1
@@ -312,7 +388,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d \
+ $ docker -d \
--storage-opt dm.datadev=/dev/sdb1 \
--storage-opt dm.metadatadev=/dev/sdc1
@@ -323,7 +399,7 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt dm.blocksize=512K
+ $ docker -d --storage-opt dm.blocksize=512K
* `dm.blkdiscard`
@@ -337,9 +413,55 @@ Currently supported options are:
Example use:
- $ sudo docker -d --storage-opt dm.blkdiscard=false
+ $ docker -d --storage-opt dm.blkdiscard=false
-### Docker exec-driver option
+ * `dm.override_udev_sync_check`
+
+ Overrides the `udev` synchronization checks between `devicemapper` and `udev`.
+ `udev` is the device manager for the Linux kernel.
+
+ To view the `udev` sync support of a Docker daemon that is using the
+ `devicemapper` driver, run:
+
+ $ docker info
+ [...]
+ Udev Sync Supported: true
+ [...]
+
+ When `udev` sync support is `true`, then `devicemapper` and udev can
+ coordinate the activation and deactivation of devices for containers.
+
+ When `udev` sync support is `false`, a race condition occurs between
+ the`devicemapper` and `udev` during create and cleanup. The race condition
+ results in errors and failures. (For information on these failures, see
+ [docker#4036](https://github.com/docker/docker/issues/4036))
+
+ To allow the `docker` daemon to start, regardless of `udev` sync not being
+ supported, set `dm.override_udev_sync_check` to true:
+
+ $ docker -d --storage-opt dm.override_udev_sync_check=true
+
+ When this value is `true`, the `devicemapper` continues and simply warns
+ you the errors are happening.
+
+ > **Note**: The ideal is to pursue a `docker` daemon and environment that
+ > does support synchronizing with `udev`. For further discussion on this
+ > topic, see [docker#4036](https://github.com/docker/docker/issues/4036).
+ > Otherwise, set this flag for migrating existing Docker daemons to a
+ > daemon with a supported environment.
+
+### Docker execdriver option
+Currently supported options of `zfs`:
+
+ * `zfs.fsname`
+
+ Set zfs filesystem under which docker will create its own datasets.
+ By default docker will pick up the zfs filesystem where docker graph
+ (`/var/lib/docker`) is located.
+
+ Example use:
+
+ $ docker -d -s zfs --storage-opt zfs.fsname=zroot/docker
The Docker daemon uses a specifically built `libcontainer` execution driver as its
interface to the Linux kernel `namespaces`, `cgroups`, and `SELinux`.
@@ -349,6 +471,21 @@ https://linuxcontainers.org/) via the `lxc` execution driver, however, this is
not where the primary development of new functionality is taking place.
Add `-e lxc` to the daemon flags to use the `lxc` execution driver.
+#### Options for the native execdriver
+
+You can configure the `native` (libcontainer) execdriver using options specified
+with the `--exec-opt` flag. All the flag's options have the `native` prefix. A
+single `native.cgroupdriver` option is available.
+
+The `native.cgroupdriver` option specifies the management of the container's
+cgroups. You can specify `cgroupfs` or `systemd`. If you specify `systemd` and
+it is not available, the system uses `cgroupfs`. By default, if no option is
+specified, the execdriver first tries `systemd` and falls back to `cgroupfs`.
+This example sets the execdriver to `cgroupfs`:
+
+ $ sudo docker -d --exec-opt native.cgroupdriver=cgroupfs
+
+Setting this option applies to all containers the daemon launches.
### Daemon DNS options
@@ -410,7 +547,7 @@ the proxy
`--default-ulimit` allows you to set the default `ulimit` options to use for all
containers. It takes the same options as `--ulimit` for `docker run`. If these
-defaults are not set, `ulimit` settings will be inheritted, if not set on
+defaults are not set, `ulimit` settings will be inherited, if not set on
`docker run`, from the Docker daemon. Any `--ulimit` options passed to
`docker run` will overwrite these defaults.
@@ -444,18 +581,24 @@ interactively. You can attach to the same contained process multiple times
simultaneously, screen sharing style, or quickly view the progress of your
daemonized process.
-You can detach from the container (and leave it running) with `CTRL-p CTRL-q`
-(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container.
-When you are attached to a container, and exit its main process, the process's
-exit code will be returned to the client.
+You can detach from the container and leave it running with `CTRL-p
+CTRL-q` (for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false.
+
+If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT`
+to the container.
+
+>**Note**: A process running as PID 1 inside a container is treated
+>specially by Linux: it ignores any signal with the default action.
+>So, the process will not terminate on `SIGINT` or `SIGTERM` unless it is
+>coded to do so.
It is forbidden to redirect the standard input of a `docker attach` command while
attaching to a tty-enabled container (i.e.: launched with `-t`).
#### Examples
- $ sudo docker run -d --name topdemo ubuntu /usr/bin/top -b)
- $ sudo docker attach topdemo
+ $ docker run -d --name topdemo ubuntu /usr/bin/top -b
+ $ docker attach topdemo
top - 02:05:52 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.1%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
@@ -492,14 +635,14 @@ attaching to a tty-enabled container (i.e.: launched with `-t`).
And in this second example, you can see the exit code returned by the `bash` process
is returned by the `docker attach` command to its caller too:
- $ sudo docker run --name test -d -it debian
+ $ docker run --name test -d -it debian
275c44472aebd77c926d4527885bb09f2f6db21d878c75f0a1c212c03d3bcfab
- $ sudo docker attach test
+ $ docker attach test
$$ exit 13
exit
$ echo $?
13
- $ sudo docker ps -a | grep test
+ $ docker ps -a | grep test
275c44472aeb debian:7 "/bin/bash" 26 seconds ago Exited (13) 17 seconds ago test
## build
@@ -518,7 +661,9 @@ is returned by the `docker attach` command to its caller too:
-m, --memory="" Memory limit for all build containers
--memory-swap="" Total memory (memory + swap), `-1` to disable swap
-c, --cpu-shares CPU Shares (relative weight)
+ --cpuset-mems="" MEMs in which to allow execution, e.g. `0-3`, `0,1`
--cpuset-cpus="" CPUs in which to allow exection, e.g. `0-3`, `0,1`
+ --cgroup-parent="" Optional parent cgroup for the container
Builds Docker images from a Dockerfile and a "context". A build's context is
the files located in the specified `PATH` or `URL`. The build process can
@@ -526,12 +671,36 @@ refer to any of the files in the context. For example, your build can use
an [*ADD*](/reference/builder/#add) instruction to reference a file in the
context.
-The `URL` parameter can specify the location of a Git repository; in this
-case, the repository is the context. The Git repository is recursively
-cloned with its submodules. The system does a fresh `git clone -recursive`
-in a temporary directory on your local host. Then, this clone is sent to
-the Docker daemon as the context. Local clones give you the ability to
-access private repositories using local user credentials, VPN's, and so forth.
+The `URL` parameter can specify the location of a Git repository;
+the repository acts as the build context. The system recursively clones the repository
+and its submodules using a `git clone --depth 1 --recursive` command.
+This command runs in a temporary directory on your local host.
+After the command succeeds, the directory is sent to the Docker daemon as the context.
+Local clones give you the ability to access private repositories using
+local user credentials, VPN's, and so forth.
+
+Git URLs accept context configuration in their fragment section, separated by a colon `:`.
+The first part represents the reference that Git will check out, this can be either
+a branch, a tag, or a commit SHA. The second part represents a subdirectory
+inside the repository that will be used as a build context.
+
+For example, run this command to use a directory called `docker` in the branch `container`:
+
+ $ docker build https://github.com/docker/rootfs.git#container:docker
+
+The following table represents all the valid suffixes with their build contexts:
+
+Build Syntax Suffix | Commit Used | Build Context Used
+--------------------|-------------|-------------------
+`myrepo.git` | `refs/heads/master` | `/`
+`myrepo.git#mytag` | `refs/tags/mytag` | `/`
+`myrepo.git#mybranch` | `refs/heads/mybranch` | `/`
+`myrepo.git#abcdef` | `sha1 = abcdef` | `/`
+`myrepo.git#:myfolder` | `refs/heads/master` | `/myfolder`
+`myrepo.git#master:myfolder` | `refs/heads/master` | `/myfolder`
+`myrepo.git#mytag:myfolder` | `refs/tags/mytag` | `/myfolder`
+`myrepo.git#mybranch:myfolder` | `refs/heads/mybranch` | `/myfolder`
+`myrepo.git#abcdef:myfolder` | `sha1 = abcdef` | `/myfolder`
Instead of specifying a context, you can pass a single Dockerfile in the
`URL` or pipe the file in via `STDIN`. To pipe a Dockerfile from `STDIN`:
@@ -539,9 +708,29 @@ Instead of specifying a context, you can pass a single Dockerfile in the
docker build - < Dockerfile
If you use STDIN or specify a `URL`, the system places the contents into a
-file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this
+file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this
scenario, there is no context.
+By default the `docker build` command will look for a `Dockerfile` at the
+root of the build context. The `-f`, `--file`, option lets you specify
+the path to an alternative file to use instead. This is useful
+in cases where the same set of files are used for multiple builds. The path
+must be to a file within the build context. If a relative path is specified
+then it must to be relative to the current directory.
+
+In most cases, it's best to put each Dockerfile in an empty directory. Then, add
+to that directory only the files needed for building the Dockerfile. To increase
+the build's performance, you can exclude files and directories by adding a
+`.dockerignore` file to that directory as well. For information on creating one,
+see the [.dockerignore file](../../reference/builder/#dockerignore-file).
+
+If the Docker client loses connection to the daemon, the build is canceled.
+This happens if you interrupt the Docker client with `ctrl-c` or if the Docker
+client is killed for any reason.
+
+> **Note:** Currently only the "run" phase of the build can be canceled until
+> pull cancelation is implemented).
+
### Return code
On a successful build, a return code of success `0` will be returned.
@@ -562,57 +751,13 @@ INFO[0000] The command [/bin/sh -c exit 13] returned a non-zero code: 13
$ echo $?
1
```
-
-### .dockerignore file
-
-If a file named `.dockerignore` exists in the root of `PATH` then it
-is interpreted as a newline-separated list of exclusion patterns.
-Exclusion patterns match files or directories relative to `PATH` that
-will be excluded from the context. Globbing is done using Go's
-[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.
-
-Please note that `.dockerignore` files in other subdirectories are
-considered as normal files. Filepaths in `.dockerignore` are absolute with
-the current directory as the root. Wildcards are allowed but the search
-is not recursive.
-
-#### Example .dockerignore file
- */temp*
- */*/temp*
- temp?
-
-The first line above `*/temp*`, would ignore all files with names starting with
-`temp` from any subdirectory below the root directory. For example, a file named
-`/somedir/temporary.txt` would be ignored. The second line `*/*/temp*`, will
-ignore files starting with name `temp` from any subdirectory that is two levels
-below the root directory. For example, the file `/somedir/subdir/temporary.txt`
-would get ignored in this case. The last line in the above example `temp?`
-will ignore the files that match the pattern from the root directory.
-For example, the files `tempa`, `tempb` are ignored from the root directory.
-Currently there is no support for regular expressions. Formats
-like `[^temp*]` are ignored.
-
-By default the `docker build` command will look for a `Dockerfile` at the
-root of the build context. The `-f`, `--file`, option lets you specify
-the path to an alternative file to use instead. This is useful
-in cases where the same set of files are used for multiple builds. The path
-must be to a file within the build context. If a relative path is specified
-then it must to be relative to the current directory.
-
-If the Docker client loses connection to the daemon, the build is canceled.
-This happens if you interrupt the Docker client with `ctrl-c` or if the Docker
-client is killed for any reason.
-
-> **Note:** Currently only the "run" phase of the build can be canceled until
-> pull cancelation is implemented).
-
See also:
[*Dockerfile Reference*](/reference/builder).
-#### Examples
+### Examples
- $ sudo docker build .
+ $ docker build .
Uploading context 10240 bytes
Step 1 : FROM busybox
Pulling repository busybox
@@ -657,7 +802,7 @@ If you wish to keep the intermediate containers after the build is
complete, you must use `--rm=false`. This does not
affect the build cache.
- $ sudo docker build .
+ $ docker build .
Uploading context 18.829 MB
Uploading context
Step 0 : FROM busybox
@@ -667,7 +812,7 @@ affect the build cache.
---> 99cc1ad10469
Successfully built 99cc1ad10469
$ echo ".git" > .dockerignore
- $ sudo docker build .
+ $ docker build .
Uploading context 6.76 MB
Uploading context
Step 0 : FROM busybox
@@ -679,27 +824,28 @@ affect the build cache.
This example shows the use of the `.dockerignore` file to exclude the `.git`
directory from the context. Its effect can be seen in the changed size of the
-uploaded context.
+uploaded context. The builder reference contains detailed information on
+[creating a .dockerignore file](../../builder/#dockerignore-file)
- $ sudo docker build -t vieux/apache:2.0 .
+ $ docker build -t vieux/apache:2.0 .
This will build like the previous example, but it will then tag the
resulting image. The repository name will be `vieux/apache`
and the tag will be `2.0`
- $ sudo docker build - < Dockerfile
+ $ docker build - < Dockerfile
This will read a Dockerfile from `STDIN` without context. Due to the
lack of a context, no contents of any local directory will be sent to
the Docker daemon. Since there is no context, a Dockerfile `ADD` only
works if it refers to a remote URL.
- $ sudo docker build - < context.tar.gz
+ $ docker build - < context.tar.gz
This will build an image for a compressed context read from `STDIN`.
Supported formats are: bzip2, gzip and xz.
- $ sudo docker build github.com/creack/docker-firefox
+ $ docker build github.com/creack/docker-firefox
This will clone the GitHub repository and use the cloned repository as
context. The Dockerfile at the root of the
@@ -707,21 +853,21 @@ repository is used as Dockerfile. Note that you
can specify an arbitrary Git repository by using the `git://` or `git@`
schema.
- $ sudo docker build -f Dockerfile.debug .
+ $ docker build -f Dockerfile.debug .
This will use a file called `Dockerfile.debug` for the build
instructions instead of `Dockerfile`.
- $ sudo docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .
- $ sudo docker build -f dockerfiles/Dockerfile.prod -t myapp_prod .
+ $ docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .
+ $ docker build -f dockerfiles/Dockerfile.prod -t myapp_prod .
The above commands will build the current build context (as specified by
the `.`) twice, once using a debug version of a `Dockerfile` and once using
a production version.
$ cd /home/me/myapp/some/dir/really/deep
- $ sudo docker build -f /home/me/myapp/dockerfiles/debug /home/me/myapp
- $ sudo docker build -f ../../../../dockerfiles/debug /home/me/myapp
+ $ docker build -f /home/me/myapp/dockerfiles/debug /home/me/myapp
+ $ docker build -f ../../../../dockerfiles/debug /home/me/myapp
These two `docker build` commands do the exact same thing. They both
use the contents of the `debug` file instead of looking for a `Dockerfile`
@@ -736,6 +882,11 @@ you refer to it on the command line.
> children) for security reasons, and to ensure repeatable builds on remote
> Docker hosts. This is also the reason why `ADD ../file` will not work.
+When `docker build` is run with the `--cgroup-parent` option the containers used
+in the build will be run with the [corresponding `docker run`
+flag](/reference/run/#specifying-custom-cgroups).
+
+
## commit
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
@@ -760,31 +911,32 @@ If this behavior is undesired, set the 'p' option to false.
The `--change` option will apply `Dockerfile` instructions to the image
that is created.
-Supported `Dockerfile` instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY`
+Supported `Dockerfile` instructions:
+`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR`
#### Commit a container
- $ sudo docker ps
+ $ docker ps
ID IMAGE COMMAND CREATED STATUS PORTS
c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
- $ sudo docker commit c3f279d17e0a SvenDowideit/testimage:version3
+ $ docker commit c3f279d17e0a SvenDowideit/testimage:version3
f5283438590d
- $ sudo docker images | head
+ $ docker images
REPOSITORY TAG ID CREATED VIRTUAL SIZE
SvenDowideit/testimage version3 f5283438590d 16 seconds ago 335.7 MB
#### Commit a container with new configurations
- $ sudo docker ps
+ $ docker ps
ID IMAGE COMMAND CREATED STATUS PORTS
c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
- $ sudo docker inspect -f "{{ .Config.Env }}" c3f279d17e0a
+ $ docker inspect -f "{{ .Config.Env }}" c3f279d17e0a
[HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin]
- $ sudo docker commit --change "ENV DEBUG true" c3f279d17e0a SvenDowideit/testimage:version3
+ $ docker commit --change "ENV DEBUG true" c3f279d17e0a SvenDowideit/testimage:version3
f5283438590d
- $ sudo docker inspect -f "{{ .Config.Env }}" f5283438590d
+ $ docker inspect -f "{{ .Config.Env }}" f5283438590d
[HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin DEBUG=true]
## cp
@@ -795,7 +947,7 @@ relative to the root of the container's filesystem.
Usage: docker cp CONTAINER:PATH HOSTDIR|-
- Copy files/folders from the PATH to the HOSTDIR.
+ Copy files/folders from the PATH to the HOSTDIR.
## create
@@ -808,12 +960,16 @@ Creates a new container.
-a, --attach=[] Attach to STDIN, STDOUT or STDERR
--add-host=[] Add a custom host-to-IP mapping (host:ip)
+ --blkio-weight=0 Block IO weight (relative weight)
-c, --cpu-shares=0 CPU shares (relative weight)
--cap-add=[] Add Linux capabilities
--cap-drop=[] Drop Linux capabilities
--cgroup-parent="" Optional parent cgroup for the container
--cidfile="" Write the container ID to the file
--cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1)
+ --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
+ --cpu-period=0 Limit the CPU CFS (Completely Fair Scheduler) period
+ --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota
--device=[] Add a host device to the container
--dns=[] Set custom DNS servers
--dns-search=[] Set custom DNS search domains
@@ -833,8 +989,11 @@ Creates a new container.
--mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33)
--name="" Assign a name to the container
--net="bridge" Set the Network mode for the container
+ --oom-kill-disable=false Whether to disable OOM Killer for the container or not
-P, --publish-all=false Publish all exposed ports to random ports
-p, --publish=[] Publish a container's port(s) to the host
+ --pid="" PID namespace to use
+ --uts="" UTS namespace to use
--privileged=false Give extended privileges to this container
--read-only=false Mount the container's root filesystem as read only
--restart="no" Restart policy (no, on-failure[:max-retry], always)
@@ -860,9 +1019,9 @@ Please see the [run command](#run) section and the [Docker run reference](
#### Examples
- $ sudo docker create -t -i fedora bash
+ $ docker create -t -i fedora bash
6d8af538ec541dd581ebc2a24153a28329acb5268abe5ef868c1f1a261221752
- $ sudo docker start -a -i 6d8af538ec5
+ $ docker start -a -i 6d8af538ec5
bash-4.2#
As of v1.4.0 container volumes are initialized during the `docker create`
@@ -910,7 +1069,7 @@ There are 3 events that are listed in the `diff`:
For example:
- $ sudo docker diff 7bb0e258aefe
+ $ docker diff 7bb0e258aefe
C /dev
A /dev/kmsg
@@ -955,7 +1114,7 @@ Using multiple filters will be handled as a *AND*; for example
`--filter container=588a23dac085 --filter event=start` will display events for container
container 588a23dac085 *AND* the event type is *start*
-Current filters:
+The currently supported filters are:
* container
* event
@@ -967,13 +1126,13 @@ You'll need two shells for this example.
**Shell 1: Listening for events:**
- $ sudo docker events
+ $ docker events
**Shell 2: Start and Stop containers:**
- $ sudo docker start 4386fb97867d
- $ sudo docker stop 4386fb97867d
- $ sudo docker stop 7805c1d35632
+ $ docker start 4386fb97867d
+ $ docker stop 4386fb97867d
+ $ docker stop 7805c1d35632
**Shell 1: (Again .. now showing events):**
@@ -985,20 +1144,20 @@ You'll need two shells for this example.
**Show events in the past from a specified time:**
- $ sudo docker events --since 1378216169
+ $ docker events --since 1378216169
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-03-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
- $ sudo docker events --since '2013-09-03'
+ $ docker events --since '2013-09-03'
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
- $ sudo docker events --since '2013-09-03T15:49:29'
+ $ docker events --since '2013-09-03T15:49:29'
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
@@ -1006,29 +1165,29 @@ You'll need two shells for this example.
**Filter events:**
- $ sudo docker events --filter 'event=stop'
+ $ docker events --filter 'event=stop'
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
- $ sudo docker events --filter 'image=ubuntu-1:14.04'
+ $ docker events --filter 'image=ubuntu-1:14.04'
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
- $ sudo docker events --filter 'container=7805c1d35632'
+ $ docker events --filter 'container=7805c1d35632'
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
- $ sudo docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d'
+ $ docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d'
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
- $ sudo docker events --filter 'container=7805c1d35632' --filter 'event=stop'
+ $ docker events --filter 'container=7805c1d35632' --filter 'event=stop'
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
- $ sudo docker events --filter 'container=container_1' --filter 'container=container_2'
+ $ docker events --filter 'container=container_1' --filter 'container=container_2'
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
@@ -1043,6 +1202,7 @@ You'll need two shells for this example.
-d, --detach=false Detached mode: run command in the background
-i, --interactive=false Keep STDIN open even if not attached
-t, --tty=false Allocate a pseudo-TTY
+ -u, --user= Username or UID (format: [:])
The `docker exec` command runs a new command in a running container.
@@ -1063,16 +1223,16 @@ If the container is paused, then the `docker exec` command will fail with an err
#### Examples
- $ sudo docker run --name ubuntu_bash --rm -i -t ubuntu bash
+ $ docker run --name ubuntu_bash --rm -i -t ubuntu bash
This will create a container named `ubuntu_bash` and start a Bash session.
- $ sudo docker exec -d ubuntu_bash touch /tmp/execWorks
+ $ docker exec -d ubuntu_bash touch /tmp/execWorks
This will create a new file `/tmp/execWorks` inside the running container
`ubuntu_bash`, in the background.
- $ sudo docker exec -it ubuntu_bash bash
+ $ docker exec -it ubuntu_bash bash
This will create a new Bash session in the container `ubuntu_bash`.
@@ -1088,11 +1248,11 @@ This will create a new Bash session in the container `ubuntu_bash`.
For example:
- $ sudo docker export red_panda > latest.tar
+ $ docker export red_panda > latest.tar
Or
- $ sudo docker export --output="latest.tar" red_panda
+ $ docker export --output="latest.tar" red_panda
> **Note:**
> `docker export` does not export the contents of volumes associated with the
@@ -1109,19 +1269,30 @@ This will create a new Bash session in the container `ubuntu_bash`.
Show the history of an image
+ -H, --human=true Print sizes and dates in human readable format
--no-trunc=false Don't truncate output
-q, --quiet=false Only show numeric IDs
To see how the `docker:latest` image was built:
- $ sudo docker history docker
- IMAGE CREATED CREATED BY SIZE
- 3e23a5875458790b7a806f95f7ec0d0b2a5c1659bfc899c89f939f6d5b8f7094 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B
- 8578938dd17054dce7993d21de79e96a037400e8d28e15e7290fea4f65128a36 8 days ago /bin/sh -c dpkg-reconfigure locales && locale-gen C.UTF-8 && /usr/sbin/update-locale LANG=C.UTF-8 1.245 MB
- be51b77efb42f67a5e96437b3e102f81e0a1399038f77bf28cea0ed23a65cf60 8 days ago /bin/sh -c apt-get update && apt-get install -y git libxml2-dev python build-essential make gcc python-dev locales python-pip 338.3 MB
- 4b137612be55ca69776c7f30c2d2dd0aa2e7d72059820abf3e25b629f887a084 6 weeks ago /bin/sh -c #(nop) ADD jessie.tar.xz in / 121 MB
- 750d58736b4b6cc0f9a9abe8f258cef269e3e9dceced1146503522be9f985ada 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi - mkimage-debootstrap.sh -t jessie.tar.xz jessie http://http.debian.net/debian 0 B
- 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158 9 months ago 0 B
+ $ docker history docker
+ IMAGE CREATED CREATED BY SIZE COMMENT
+ 3e23a5875458 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B
+ 8578938dd170 8 days ago /bin/sh -c dpkg-reconfigure locales && loc 1.245 MB
+ be51b77efb42 8 days ago /bin/sh -c apt-get update && apt-get install 338.3 MB
+ 4b137612be55 6 weeks ago /bin/sh -c #(nop) ADD jessie.tar.xz in / 121 MB
+ 750d58736b4b 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi 77af4d6b9913 19 hours ago 1.089 GB
committ latest b6fa739cedf5 19 hours ago 1.089 GB
@@ -1169,7 +1340,7 @@ uses up the `VIRTUAL SIZE` listed only once.
#### Listing the full length image IDs
- $ sudo docker images --no-trunc | head
+ $ docker images --no-trunc
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 1.089 GB
committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 1.089 GB
@@ -1188,7 +1359,7 @@ called a `digest`. As long as the input used to generate the image is
unchanged, the digest value is predictable. To list image digest values, use
the `--digests` flag:
- $ sudo docker images --digests | head
+ $ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE
localhost:5000/test/busybox sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 4986bf8c1536 9 weeks ago 2.43 MB
@@ -1202,13 +1373,14 @@ also reference by digest in `create`, `run`, and `rmi` commands, as well as the
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
-Current filters:
- * dangling (boolean - true or false)
- * label (`label=` or `label==`)
+The currently supported filters are:
+
+* dangling (boolean - true or false)
+* label (`label=` or `label==`)
##### Untagged images
- $ sudo docker images --filter "dangling=true"
+ $ docker images --filter "dangling=true"
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
8abc22fbb042 4 weeks ago 0 B
@@ -1226,7 +1398,7 @@ By having this flag it allows for batch cleanup.
Ready for use by `docker rmi ...`, like:
- $ sudo docker rmi $(sudo docker images -f "dangling=true" -q)
+ $ docker rmi $(docker images -f "dangling=true" -q)
8abc22fbb042
48e5f45168b9
@@ -1254,8 +1426,8 @@ the `-` parameter to take the data from `STDIN`.
The `--change` option will apply `Dockerfile` instructions to the image
that is created.
-Supported `Dockerfile` instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`,
-`ONBUILD`, `USER`, `VOLUME`, `WORKDIR`
+Supported `Dockerfile` instructions:
+`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR`
#### Examples
@@ -1263,21 +1435,21 @@ Supported `Dockerfile` instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`,
This will create a new untagged image.
- $ sudo docker import http://example.com/exampleimage.tgz
+ $ docker import http://example.com/exampleimage.tgz
**Import from a local file:**
Import to docker via pipe and `STDIN`.
- $ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new
+ $ cat exampleimage.tgz | docker import - exampleimagelocal:new
**Import from a local directory:**
- $ sudo tar -c . | sudo docker import - exampleimagedir
+ $ sudo tar -c . | docker import - exampleimagedir
**Import from a local directory with new configurations:**
- $ sudo tar -c . | sudo docker import --change "ENV DEBUG true" - exampleimagedir
+ $ sudo tar -c . | docker import --change "ENV DEBUG true" - exampleimagedir
Note the `sudo` in this example – you must preserve
the ownership of the files (especially root ownership) during the
@@ -1293,7 +1465,7 @@ tar, then the ownerships might not get preserved.
For example:
- $ sudo docker -D info
+ $ docker -D info
Containers: 14
Images: 52
Storage Driver: aufs
@@ -1301,6 +1473,7 @@ For example:
Backing Filesystem: extfs
Dirs: 545
Execution Driver: native-0.2
+ Logging Driver: json-file
Kernel Version: 3.13.0-24-generic
Operating System: Ubuntu 14.04 LTS
CPUs: 1
@@ -1309,7 +1482,7 @@ For example:
Total Memory: 2 GiB
Debug mode (server): false
Debug mode (client): true
- Fds: 10
+ File Descriptors: 10
Goroutines: 9
System Time: Tue Mar 10 18:38:57 UTC 2015
EventsListeners: 0
@@ -1349,25 +1522,25 @@ describes all the details of the format.
For the most part, you can pick out any field from the JSON in a fairly
straightforward manner.
- $ sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID
+ $ docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID
**Get an instance's MAC Address:**
For the most part, you can pick out any field from the JSON in a fairly
straightforward manner.
- $ sudo docker inspect --format='{{.NetworkSettings.MacAddress}}' $INSTANCE_ID
+ $ docker inspect --format='{{.NetworkSettings.MacAddress}}' $INSTANCE_ID
**Get an instance's log path:**
- $ sudo docker inspect --format='{{.LogPath}}' $INSTANCE_ID
+ $ docker inspect --format='{{.LogPath}}' $INSTANCE_ID
**List All Port Bindings:**
One can loop over arrays and maps in the results to produce simple text
output:
- $ sudo docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID
+ $ docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID
**Find a Specific Port Mapping:**
@@ -1379,7 +1552,7 @@ numeric public port, you use `index` to find the specific port map, and
then `index` 0 contains the first object inside of that. Then we ask for
the `HostPort` field to get the public address.
- $ sudo docker inspect --format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID
+ $ docker inspect --format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID
**Get config:**
@@ -1388,7 +1561,7 @@ the template language's custom `json` function does. The `.config`
section contains complex JSON object, so to grab it as JSON, you use
`json` to convert the configuration object into JSON.
- $ sudo docker inspect --format='{{json .config}}' $INSTANCE_ID
+ $ docker inspect --format='{{json .config}}' $INSTANCE_ID
## kill
@@ -1412,14 +1585,14 @@ signal specified with option `--signal`.
Loads a tarred repository from a file or the standard input stream.
Restores both images and tags.
- $ sudo docker images
+ $ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
- $ sudo docker load < busybox.tar
- $ sudo docker images
+ $ docker load < busybox.tar
+ $ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
busybox latest 769b9341d937 7 weeks ago 2.489 MB
- $ sudo docker load --input fedora.tar
- $ sudo docker images
+ $ docker load --input fedora.tar
+ $ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
busybox latest 769b9341d937 7 weeks ago 2.489 MB
fedora rawhide 0d20aec6529d 7 weeks ago 387 MB
@@ -1442,7 +1615,7 @@ If you want to login to a self-hosted registry you can specify this by
adding the server name.
example:
- $ sudo docker login localhost:8080
+ $ docker login localhost:8080
## logout
@@ -1453,7 +1626,7 @@ adding the server name.
For example:
- $ sudo docker logout localhost:8080
+ $ docker logout localhost:8080
## logs
@@ -1462,6 +1635,7 @@ For example:
Fetch the logs of a container
-f, --follow=false Follow log output
+ --since="" Show logs since timestamp
-t, --timestamps=false Show timestamps
--tail="all" Number of lines to show from the end of the logs
@@ -1481,6 +1655,10 @@ timestamp, for example `2014-09-16T06:17:46.000000000Z`, to each
log entry. To ensure that the timestamps for are aligned the
nano-second part of the timestamp will be padded with zero when necessary.
+The `--since` option shows logs of a container generated only after
+the given date, specified as RFC 3339 or UNIX timestamp. The `--since` option
+can be combined with the `--follow` and `--tail` options.
+
## pause
Usage: docker pause CONTAINER [CONTAINER...]
@@ -1507,17 +1685,17 @@ for further details.
You can find out all the ports mapped by not specifying a `PRIVATE_PORT`, or
just a specific mapping:
- $ sudo docker ps test
+ $ docker ps test
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b650456536c7 busybox:latest top 54 minutes ago Up 54 minutes 0.0.0.0:1234->9876/tcp, 0.0.0.0:4321->7890/tcp test
- $ sudo docker port test
+ $ docker port test
7890/tcp -> 0.0.0.0:4321
9876/tcp -> 0.0.0.0:1234
- $ sudo docker port test 7890/tcp
+ $ docker port test 7890/tcp
0.0.0.0:4321
- $ sudo docker port test 7890/udp
+ $ docker port test 7890/udp
2014/06/24 11:53:36 Error: No public port '7890/udp' published for test
- $ sudo docker port test 7890
+ $ docker port test 7890
0.0.0.0:4321
## ps
@@ -1530,7 +1708,7 @@ just a specific mapping:
--before="" Show only container created before Id or Name
-f, --filter=[] Filter output based on conditions provided
-l, --latest=false Show the latest created container, include non-running
- -n=-1 Show n last created containers, include non-running
+ -n=-1 Show n last created containers, include non-running
--no-trunc=false Don't truncate output
-q, --quiet=false Only display numeric IDs
-s, --size=false Display total file sizes
@@ -1538,7 +1716,7 @@ just a specific mapping:
Running `docker ps --no-trunc` showing 2 linked containers.
- $ sudo docker ps
+ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds 3300-3310/tcp webapp
d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db
@@ -1553,13 +1731,17 @@ Running `docker ps --no-trunc` showing 2 linked containers.
The filtering flag (`-f` or `--filter)` format is a `key=value` pair. If there is more
than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`)
-Current filters:
- * exited (int - the code of exited containers. Only useful with '--all')
- * status (restarting|running|paused|exited)
+The currently supported filters are:
+
+* id (container's id)
+* label (`label=` or `label==`)
+* name (container's name)
+* exited (int - the code of exited containers. Only useful with `--all`)
+* status (restarting|running|paused|exited)
##### Successfully exited containers
- $ sudo docker ps -a --filter 'exited=0'
+ $ docker ps -a --filter 'exited=0'
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ea09c3c82f6e registry:latest /srv/run.sh 2 weeks ago Exited (0) 2 weeks ago 127.0.0.1:5000->5000/tcp desperate_leakey
106ea823fe4e fedora:latest /bin/sh -c 'bash -l' 2 weeks ago Exited (0) 2 weeks ago determined_albattani
@@ -1569,7 +1751,7 @@ This shows all the containers that have exited with status of '0'
## pull
- Usage: docker pull [OPTIONS] NAME[:TAG]
+ Usage: docker pull [OPTIONS] NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]
Pull an image or a repository from the registry
@@ -1589,22 +1771,23 @@ a protocol specifier (`https://`, for example).
To download a particular image, or set of images (i.e., a repository),
use `docker pull`:
- $ sudo docker pull debian
+ $ docker pull debian
# will pull the debian:latest image and its intermediate layers
- $ sudo docker pull debian:testing
+ $ docker pull debian:testing
# will pull the image named debian:testing and any intermediate
# layers it is based on.
- $ sudo docker pull debian@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
+ $ docker pull debian@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
# will pull the image from the debian repository with the digest
# sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
# and any intermediate layers it is based on.
# (Typically the empty `scratch` image, a MAINTAINER layer,
# and the un-tarred base).
- $ sudo docker pull --all-tags centos
+ $ docker pull --all-tags centos
# will pull all the images from the centos repository
- $ sudo docker pull registry.hub.docker.com/debian
+ $ docker pull registry.hub.docker.com/debian
# manually specifies the path to the default Docker registry. This could
# be replaced with the path to a local registry to pull from another source.
+ # sudo docker pull myhub.com:8080/test-image
## push
@@ -1643,24 +1826,26 @@ The `docker rename` command allows the container to be renamed to a different na
#### Examples
- $ sudo docker rm /redis
+ $ docker rm /redis
/redis
This will remove the container referenced under the link
`/redis`.
- $ sudo docker rm --link /webapp/redis
+ $ docker rm --link /webapp/redis
/webapp/redis
This will remove the underlying link between `/webapp` and the `/redis`
containers removing all network communication.
- $ sudo docker rm --force redis
+ $ docker rm --force redis
redis
The main process inside the container referenced under the link `/redis` will receive
`SIGKILL`, then the container will be removed.
+ $ docker rm $(docker ps -a -q)
+
This command will delete all stopped containers. The command `docker ps
-a -q` will return all existing container IDs and pass them to the `rm`
command which will delete them. Any running containers will not be
@@ -1681,37 +1866,52 @@ You can remove an image using its short or long ID, its tag, or its digest. If
an image has one or more tag or digest reference, you must remove all of them
before the image is removed.
- $ sudo docker images
+ $ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
test2 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
- $ sudo docker rmi fd484f19954f
+ $ docker rmi fd484f19954f
Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories, use -f to force
2013/12/11 05:47:16 Error: failed to remove one or more images
- $ sudo docker rmi test1
+ $ docker rmi test1
Untagged: test1:latest
- $ sudo docker rmi test2
+ $ docker rmi test2
Untagged: test2:latest
- $ sudo docker images
+ $ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
- $ sudo docker rmi test
+ $ docker rmi test
Untagged: test:latest
Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8
+If you use the `-f` flag and specify the image's short or long ID, then this
+command untags and removes all images that match the specified ID.
+
+ $ docker images
+ REPOSITORY TAG IMAGE ID CREATED SIZE
+ test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
+ test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
+ test2 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
+
+ $ docker rmi -f fd484f19954f
+ Untagged: test1:latest
+ Untagged: test:latest
+ Untagged: test2:latest
+ Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8
+
An image pulled by digest has no tag associated with it:
- $ sudo docker images --digests
+ $ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE
localhost:5000/test/busybox sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 4986bf8c1536 9 weeks ago 2.43 MB
To remove an image using its digest:
- $ sudo docker rmi localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
+ $ docker rmi localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
Untagged: localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
Deleted: 4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125
Deleted: ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2
@@ -1725,11 +1925,15 @@ To remove an image using its digest:
-a, --attach=[] Attach to STDIN, STDOUT or STDERR
--add-host=[] Add a custom host-to-IP mapping (host:ip)
+ --blkio-weight=0 Block IO weight (relative weight)
-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="" CPUs in which to allow execution (0-3, 0,1)
+ --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
+ --cpu-period=0 Limit the CPU CFS (Completely Fair Scheduler) period
+ --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota
-d, --detach=false Run container in background and print container ID
--device=[] Add a host device to the container
--dns=[] Set custom DNS servers
@@ -1752,9 +1956,11 @@ To remove an image using its digest:
--memory-swap="" Total memory (memory + swap), '-1' to disable swap
--name="" Assign a name to the container
--net="bridge" Set the Network mode for the container
+ --oom-kill-disable=false Whether to disable OOM Killer for the container or not
-P, --publish-all=false Publish all exposed ports to random ports
-p, --publish=[] Publish a container's port(s) to the host
--pid="" PID namespace to use
+ --uts="" UTS namespace to use
--privileged=false Give extended privileges to this container
--read-only=false Mount the container's root filesystem as read only
--restart="no" Restart policy (no, on-failure[:max-retry], always)
@@ -1786,12 +1992,12 @@ and linking containers.
#### Examples
- $ sudo docker run --name test -it debian
+ $ docker run --name test -it debian
$$ exit 13
exit
$ echo $?
13
- $ sudo docker ps -a | grep test
+ $ docker ps -a | grep test
275c44472aeb debian:7 "/bin/bash" 26 seconds ago Exited (13) 17 seconds ago test
In this example, we are running `bash` interactively in the `debian:latest` image, and giving
@@ -1799,14 +2005,14 @@ the container the name `test`. We then quit `bash` by running `exit 13`, which m
will have an exit code of `13`. This is then passed on to the caller of `docker run`, and
is recorded in the `test` container metadata.
- $ sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test"
+ $ docker run --cidfile /tmp/docker_test.cid ubuntu echo "test"
This will create a container and print `test` to the console. The `cidfile`
flag makes Docker attempt to create a new file and write the container ID to it.
If the file exists already, Docker will return an error. Docker will close this
file when `docker run` exits.
- $ sudo docker run -t -i --rm ubuntu bash
+ $ docker run -t -i --rm ubuntu bash
root@bc338942ef20:/# mount -t tmpfs none /mnt
mount: permission denied
@@ -1814,7 +2020,7 @@ This will *not* work, because by default, most potentially dangerous kernel
capabilities are dropped; including `cap_sys_admin` (which is required to mount
filesystems). However, the `--privileged` flag will allow it to run:
- $ sudo docker run --privileged ubuntu bash
+ $ docker run --privileged ubuntu bash
root@50e3f57e16e6:/# mount -t tmpfs none /mnt
root@50e3f57e16e6:/# df -h
Filesystem Size Used Avail Use% Mounted on
@@ -1825,12 +2031,12 @@ lifts all the limitations enforced by the `device` cgroup controller. In other
words, the container can then do almost everything that the host can do. This
flag exists to allow special use-cases, like running Docker within Docker.
- $ sudo docker run -w /path/to/dir/ -i -t ubuntu pwd
+ $ docker run -w /path/to/dir/ -i -t ubuntu pwd
The `-w` lets the command being executed inside directory given, here
`/path/to/dir/`. If the path does not exists it is created inside the container.
- $ sudo docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
+ $ docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
The `-v` flag mounts the current working directory into the container. The `-w`
lets the command being executed inside the current working directory, by
@@ -1838,41 +2044,41 @@ changing into the directory to the value returned by `pwd`. So this
combination executes the command using the container, but inside the
current working directory.
- $ sudo docker run -v /doesnt/exist:/foo -w /foo -i -t ubuntu bash
+ $ docker run -v /doesnt/exist:/foo -w /foo -i -t ubuntu bash
When the host directory of a bind-mounted volume doesn't exist, Docker
will automatically create this directory on the host for you. In the
example above, Docker will create the `/doesnt/exist`
folder before starting your container.
- $ sudo docker run --read-only -v /icanwrite busybox touch /icanwrite here
+ $ docker run --read-only -v /icanwrite busybox touch /icanwrite here
Volumes can be used in combination with `--read-only` to control where
a container writes files. The `--read-only` flag mounts the container's root
filesystem as read only prohibiting writes to locations other than the
specified volumes for the container.
- $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh
+ $ docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh
By bind-mounting the docker unix socket and statically linked docker
binary (such as that provided by [https://get.docker.com](
https://get.docker.com)), you give the container the full access to create and
manipulate the host's Docker daemon.
- $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash
+ $ docker run -p 127.0.0.1:80:8080 ubuntu bash
This binds port `8080` of the container to port `80` on `127.0.0.1` of
the host machine. The [Docker User Guide](/userguide/dockerlinks/)
explains in detail how to manipulate ports in Docker.
- $ sudo docker run --expose 80 ubuntu bash
+ $ docker run --expose 80 ubuntu bash
This exposes port `80` of the container for use within a link without
publishing the port to the host system's interfaces. The [Docker User
Guide](/userguide/dockerlinks) explains in detail how to manipulate
ports in Docker.
- $ sudo docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash
+ $ docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash
This sets environmental variables in the container. For illustration all three
flags are shown here. Where `-e`, `--env` take an environment variable and
@@ -1889,7 +2095,7 @@ override variables as needed.
$ cat ./env.list
TEST_FOO=BAR
- $ sudo docker run --env TEST_FOO="This is a test" --env-file ./env.list busybox env | grep TEST_FOO
+ $ docker run --env TEST_FOO="This is a test" --env-file ./env.list busybox env | grep TEST_FOO
TEST_FOO=This is a test
The `--env-file` flag takes a filename as an argument and expects each line
@@ -1916,11 +2122,11 @@ An example of a file passed with `--env-file`
TEST_APP_DEST_PORT=8888
TEST_PASSTHROUGH=howdy
- $ sudo docker run --name console -t -i ubuntu bash
+ $ docker run --name console -t -i ubuntu bash
A label is a a `key=value` pair that applies metadata to a container. To label a container with two labels:
- $ sudo docker run -l my-label --label com.example.foo=bar ubuntu bash
+ $ docker run -l my-label --label com.example.foo=bar ubuntu bash
The `my-label` key doesn't specify a value so the label defaults to an empty
string(`""`). To add multiple labels, repeat the label flag (`-l` or `--label`).
@@ -1933,10 +2139,10 @@ Use the `--label-file` flag to load multiple labels from a file. Delimit each
label in the file with an EOL mark. The example below loads labels from a
labels file in the current directory:
- $ sudo docker run --label-file ./labels ubuntu bash
+ $ docker run --label-file ./labels ubuntu bash
The label-file format is similar to the format for loading environment
-variables. (Unlike environment variables, labels are not visislbe to processes
+variables. (Unlike environment variables, labels are not visible to processes
running inside a container.) The following example illustrates a label-file
format:
@@ -1946,21 +2152,27 @@ format:
com.example.label2=another\ label
com.example.label3
-You can load multiple label-files by supplying multiple `--label-file` flags.
+You can load multiple label-files by supplying multiple `--label-file` flags.
For additional information on working with labels, see [*Labels - custom
metadata in Docker*](/userguide/labels-custom-metadata/) in the Docker User
Guide.
- $ sudo docker run --link /redis:redis --name console ubuntu bash
+ $ docker run --link /redis:redis --name console ubuntu bash
The `--link` flag will link the container named `/redis` into the newly
created container with the alias `redis`. The new container can access the
network and environment of the `redis` container via environment variables.
+The `--link` flag will also just accept the form `` in which case
+the alias will match the name. For instance, you could have written the previous
+example as:
+
+ $ docker run --link redis --name console ubuntu bash
+
The `--name` flag will assign the name `console` to the newly created
container.
- $ sudo docker run --volumes-from 777f7dc92da7 --volumes-from ba8c0c54f0f2:ro -i -t ubuntu pwd
+ $ docker run --volumes-from 777f7dc92da7 --volumes-from ba8c0c54f0f2:ro -i -t ubuntu pwd
The `--volumes-from` flag mounts all the defined volumes from the referenced
containers. Containers can be specified by repetitions of the `--volumes-from`
@@ -1969,21 +2181,34 @@ mount the volumes in read-only or read-write mode, respectively. By default,
the volumes are mounted in the same mode (read write or read only) as
the reference container.
+Labeling systems like SELinux require proper labels be placed on volume content
+mounted into a container, otherwise the security system might prevent the
+processes running inside the container from using the content. By default,
+volumes are not relabeled.
+
+Two suffixes :z or :Z can be added to the volume mount. These suffixes tell
+Docker to relabel file objects on the shared volumes. The 'z' option tells
+Docker that the volume content will be shared between containers. Docker will
+label the content with a shared content label. Shared volumes labels allow all
+containers to read/write content. The 'Z' option tells Docker to label the
+content with a private unshared label. Private volumes can only be used by the
+current container.
+
The `-a` flag tells `docker run` to bind to the container's `STDIN`, `STDOUT` or
`STDERR`. This makes it possible to manipulate the output and input as needed.
- $ echo "test" | sudo docker run -i -a stdin ubuntu cat -
+ $ echo "test" | docker run -i -a stdin ubuntu cat -
This pipes data into a container and prints the container's ID by attaching
only to the container's `STDIN`.
- $ sudo docker run -a stderr ubuntu echo test
+ $ docker run -a stderr ubuntu echo test
This isn't going to print anything unless there's an error because we've
only attached to the `STDERR` of the container. The container's logs
still store what's been written to `STDERR` and `STDOUT`.
- $ cat somefile | sudo docker run -i -a stdin mybuilder dobuild
+ $ cat somefile | docker run -i -a stdin mybuilder dobuild
This is how piping a file into a container could be done for a build.
The container's ID will be printed after the build is done and the build
@@ -1991,10 +2216,10 @@ logs could be retrieved using `docker logs`. This is
useful if you need to pipe a file or something else into a container and
retrieve the container's ID once the container has finished running.
- $ sudo docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo}
- brw-rw---- 1 root disk 8, 2 Feb 9 16:05 /dev/xvdc
- brw-rw---- 1 root disk 8, 3 Feb 9 16:05 /dev/sdd
- crw-rw-rw- 1 root root 1, 5 Feb 9 16:05 /dev/nulo
+ $ docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo}
+ brw-rw---- 1 root disk 8, 2 Feb 9 16:05 /dev/xvdc
+ brw-rw---- 1 root disk 8, 3 Feb 9 16:05 /dev/sdd
+ crw-rw-rw- 1 root root 1, 5 Feb 9 16:05 /dev/nulo
It is often necessary to directly expose devices to a container. The `--device`
option enables that. For example, a specific block storage device or loop
@@ -2007,19 +2232,19 @@ flag:
```
- $ sudo docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc
Command (m for help): q
- $ sudo docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc:ro --rm -it ubuntu fdisk /dev/xvdc
You will not be able to write the partition table.
Command (m for help): q
- $ sudo docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc
Command (m for help): q
- $ sudo docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc
fdisk: unable to open /dev/xvdc: Operation not permitted
```
@@ -2029,11 +2254,11 @@ flag:
**A complete example:**
- $ sudo docker run -d --name static static-web-files sh
- $ sudo docker run -d --expose=8098 --name riak riakserver
- $ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver
- $ sudo docker run -d -p 1443:443 --dns=10.0.0.1 --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver
- $ sudo docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log
+ $ docker run -d --name static static-web-files sh
+ $ docker run -d --expose=8098 --name riak riakserver
+ $ docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver
+ $ docker run -d -p 1443:443 --dns=10.0.0.1 --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver
+ $ docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log
This example shows five containers that might be set up to test a web
application change:
@@ -2058,9 +2283,9 @@ application change:
`--rm` option means that when the container exits, the container's layer is
removed.
-#### Restart Policies
+#### Restart policies
-Use Docker's `--restart` to specify a container's *restart policy*. A restart
+Use Docker's `--restart` to specify a container's *restart policy*. A restart
policy controls whether the Docker daemon restarts a container after exit.
Docker supports the following restart policies:
@@ -2075,7 +2300,7 @@ Docker supports the following restart policies:
| no |
- Do not automatically restart the container when it exits. This is the
+ Do not automatically restart the container when it exits. This is the
default.
|
@@ -2087,7 +2312,7 @@ Docker supports the following restart policies:
Restart only if the container exits with a non-zero exit status.
- Optionally, limit the number of restart retries the Docker
+ Optionally, limit the number of restart retries the Docker
daemon attempts.
|
@@ -2102,12 +2327,12 @@ Docker supports the following restart policies:
- $ sudo docker run --restart=always redis
+ $ docker run --restart=always redis
This will run the `redis` container with a restart policy of **always**
so that if the container exits, Docker will restart it.
-More detailed information on restart policies can be found in the
+More detailed information on restart policies can be found in the
[Restart Policies (--restart)](/reference/run/#restart-policies-restart) section
of the Docker run reference page.
@@ -2159,6 +2384,8 @@ available in the default container, you can set these using the `--ulimit` flag.
> If you do not provide a `hard limit`, the `soft limit` will be used for both
values. If no `ulimits` are set, they will be inherited from the default `ulimits`
set on the daemon.
+> `as` option is disabled now. In other words, the following script is not supported:
+> `$ docker run -it --ulimit as=1024 fedora /bin/bash`
## save
@@ -2174,18 +2401,18 @@ each argument provided.
It is used to create a backup that can then be used with `docker load`
- $ sudo docker save busybox > busybox.tar
+ $ docker save busybox > busybox.tar
$ ls -sh busybox.tar
2.7M busybox.tar
- $ sudo docker save --output busybox.tar busybox
+ $ docker save --output busybox.tar busybox
$ ls -sh busybox.tar
2.7M busybox.tar
- $ sudo docker save -o fedora-all.tar fedora
- $ sudo docker save -o fedora-latest.tar fedora:latest
+ $ docker save -o fedora-all.tar fedora
+ $ docker save -o fedora-latest.tar fedora:latest
It is even useful to cherry-pick particular tags of an image repository
- $ sudo docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy
+ $ docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy
## search
@@ -2222,13 +2449,14 @@ more details on finding shared images from the command line.
Display a live stream of one or more containers' resource usage statistics
--help=false Print usage
+ --no-stream=false Disable streaming stats and only pull the first result
Running `docker stats` on multiple containers
- $ sudo docker stats redis1 redis2
+ $ docker stats redis1 redis2
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
- redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B
- redis2 0.07% 2.746 MiB/64 MiB 4.29% 1.266 KiB/648 B
+ redis1 0.07% 796 KB/64 MB 1.21% 788 B/648 B
+ redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B
The `docker stats` command will only return a live stream of data for running
@@ -2289,7 +2517,7 @@ for further details.
Show the Docker version, API version, Git commit, Go version and OS/architecture
of both Docker client and daemon. Example use:
- $ sudo docker version
+ $ docker version
Client version: 1.5.0
Client API version: 1.17
Go version (client): go1.4.1
diff --git a/docs/sources/reference/glossary.md b/docs/sources/reference/glossary.md
new file mode 100644
index 000000000..d33d01569
--- /dev/null
+++ b/docs/sources/reference/glossary.md
@@ -0,0 +1,201 @@
+page_title: Docker Glossary
+page_description: Glossary of terms used around Docker
+page_keywords: glossary, docker, terms, definitions
+
+# Glossary
+
+A list of terms used around the Docker project.
+
+## aufs
+
+aufs (advanced multi layered unification filesystem) is a Linux [filesystem](#filesystem) that
+Docker supports as a storage backend. It implements the
+[union mount](http://en.wikipedia.org/wiki/Union_mount) for Linux file systems.
+
+## boot2docker
+
+[boot2docker](http://boot2docker.io/) is a lightweight Linux distribution made
+specifically to run Docker containers. It is a common choice for a [VM](#virtual-machine)
+to run Docker on Windows and Mac OS X.
+
+boot2docker can also refer to the boot2docker management tool on Windows and
+Mac OS X which manages the boot2docker VM.
+
+## btrfs
+
+btrfs (B-tree file system) is a Linux [filesystem](#filesystem) that Docker
+supports as a storage backend. It is a [copy-on-write](http://en.wikipedia.org/wiki/Copy-on-write)
+filesystem.
+
+## build
+
+build is the process of building Docker images using a [Dockerfile](#dockerfile).
+The build uses a Dockerfile and a "context". The context is the set of files in the
+directory in which the image is built.
+
+## cgroups
+
+cgroups is a Linux kernel feature that limits, accounts for, and isolates
+the resource usage (CPU, memory, disk I/O, network, etc.) of a collection
+of processes. Docker relies on cgroups to control and isolate resource limits.
+
+*Also known as : control groups*
+
+## Compose
+
+[Compose](https://github.com/docker/compose) is a tool for defining and
+running complex applications with Docker. With compose, you define a
+multi-container application in a single file, then spin your
+application up in a single command which does everything that needs to
+be done to get it running.
+
+*Also known as : docker-compose, fig*
+
+## container
+
+A container is a runtime instance of a [docker image](#image).
+
+A Docker container consists of
+
+- A Docker image
+- Execution environment
+- A standard set of instructions
+
+The concept is borrowed from Shipping Containers, which define a standard to ship
+goods globally. Docker defines a standard to ship software.
+
+## data volume
+
+A data volume is a specially-designated directory within one or more containers
+that bypasses the Union File System. Data volumes are designed to persist data,
+independent of the container's life cycle. Docker therefore never automatically
+delete volumes when you remove a container, nor will it "garbage collect"
+volumes that are no longer referenced by a container.
+
+
+## Docker
+
+The term Docker can refer to
+
+- The Docker project as a whole, which is a platform for developers and sysadmins to
+develop, ship, and run applications
+- The docker daemon process running on the host which manages images and containers
+
+
+## Docker Hub
+
+The [Docker Hub](https://hub.docker.com/) is a centralized resource for working with
+Docker and its components. It provides the following services:
+
+- Docker image hosting
+- User authentication
+- Automated image builds and work-flow tools such as build triggers and web hooks
+- Integration with GitHub and BitBucket
+
+
+## Dockerfile
+
+A Dockerfile is a text document that contains all the commands you would
+normally execute manually in order to build a Docker image. Docker can
+build images automatically by reading the instructions from a Dockerfile.
+
+## filesystem
+
+A file system is the method an operating system uses to name files
+and assign them locations for efficient storage and retrieval.
+
+Examples :
+
+- Linux : ext4, aufs, btrfs, zfs
+- Windows : NTFS
+- OS X : HFS+
+
+## image
+
+Docker images are the basis of [containers](#container). An Image is an
+ordered collection of root filesystem changes and the corresponding
+execution parameters for use within a container runtime. An image typically
+contains a union of layered filesystems stacked on top of each other. An image
+does not have state and it never changes.
+
+## libcontainer
+
+libcontainer provides a native Go implementation for creating containers with
+namespaces, cgroups, capabilities, and filesystem access controls. It allows
+you to manage the lifecycle of the container performing additional operations
+after the container is created.
+
+## link
+
+links provide an interface to connect Docker containers running on the same host
+to each other without exposing the hosts' network ports. When you set up a link,
+you create a conduit between a source container and a recipient container.
+The recipient can then access select data about the source. To create a link,
+you can use the `--link` flag.
+
+## Machine
+
+[Machine](https://github.com/docker/machine) is a Docker tool which
+makes it really easy to create Docker hosts on your computer, on
+cloud providers and inside your own data center. It creates servers,
+installs Docker on them, then configures the Docker client to talk to them.
+
+*Also known as : docker-machine*
+
+## overlay
+
+OverlayFS is a [filesystem](#filesystem) service for Linux which implements a
+[union mount](http://en.wikipedia.org/wiki/Union_mount) for other file systems.
+It is supported by the Docker daemon as a storage driver.
+
+## registry
+
+A Registry is a hosted service containing [repositories](#repository) of [images](#image)
+which responds to the Registry API.
+
+The default registry can be accessed using a browser at [Docker Hub](#docker-hub)
+or using the `docker search` command.
+
+## repository
+
+A repository is a set of Docker images. A repository can be shared by pushing it
+to a [registry](#registry) server. The different images in the repository can be
+labeled using [tags](#tag).
+
+Here is an example of the shared [nginx repository](https://registry.hub.docker.com/_/nginx/)
+and its [tags](https://registry.hub.docker.com/_/nginx/tags/manage/)
+
+## Swarm
+
+[Swarm](https://github.com/docker/swarm) is a native clustering tool for Docker.
+Swarm pools together several Docker hosts and exposes them as a single virtual
+Docker host. It serves the standard Docker API, so any tool that already works
+with Docker can now transparently scale up to multiple hosts.
+
+*Also known as : docker-swarm*
+
+## tag
+
+A tag is a label applied to a Docker image in a [repository](#repository).
+tags are how various images in a repository are distinguished from each other.
+
+*Note : This label is not related to the key=value labels set for docker daemon*
+
+## Union file system
+
+Union file systems, or UnionFS, are file systems that operate by creating layers, making them
+very lightweight and fast. Docker uses union file systems to provide the building
+blocks for containers.
+
+
+## Virtual Machine
+
+A Virtual Machine is a program that emulates a complete computer and imitates dedicated hardware.
+It shares physical hardware resources with other users but isolates the operating system. The
+end user has the same experience on a Virtual Machine as they would have on dedicated hardware.
+
+Compared to to containers, a Virtual Machine is heavier to run, provides more isolation,
+gets its own set of resources and does minimal sharing.
+
+*Also known as : VM*
+
diff --git a/docs/sources/reference/logging/journald.md b/docs/sources/reference/logging/journald.md
new file mode 100644
index 000000000..9c025bfe6
--- /dev/null
+++ b/docs/sources/reference/logging/journald.md
@@ -0,0 +1,66 @@
+# Journald logging driver
+
+The `journald` logging driver sends container logs to the [systemd
+journal](http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html). Log entries can be retrieved using the `journalctl`
+command or through use of the journal API.
+
+In addition to the text of the log message itself, the `journald` log
+driver stores the following metadata in the journal with each message:
+
+| Field | Description |
+----------------------|-------------|
+| `CONTAINER_ID` | The container ID truncated to 12 characters. |
+| `CONTAINER_ID_FULL` | The full 64-character container ID. |
+| `CONTAINER_NAME` | The container name at the time it was started. If you use `docker rename` to rename a container, the new name is not reflected in the journal entries. |
+
+## Usage
+
+You can configure the default logging driver by passing the
+`--log-driver` option to the Docker daemon:
+
+ docker --log-driver=journald
+
+You can set the logging driver for a specific container by using the
+`--log-driver` option to `docker run`:
+
+ docker run --log-driver=journald ...
+
+## Note regarding container names
+
+The value logged in the `CONTAINER_NAME` field is the container name
+that was set at startup. If you use `docker rename` to rename a
+container, the new name will not be reflected in the journal entries.
+Journal entries will continue to use the original name.
+
+## Retrieving log messages with journalctl
+
+You can use the `journalctl` command to retrieve log messages. You
+can apply filter expressions to limit the retrieved messages to a
+specific container. For example, to retrieve all log messages from a
+container referenced by name:
+
+ # journalctl CONTAINER_NAME=webserver
+
+You can make use of additional filters to further limit the messages
+retrieved. For example, to see just those messages generated since
+the system last booted:
+
+ # journalctl -b CONTAINER_NAME=webserver
+
+Or to retrieve log messages in JSON format with complete metadata:
+
+ # journalctl -o json CONTAINER_NAME=webserver
+
+## Retrieving log messages with the journal API
+
+This example uses the `systemd` Python module to retrieve container
+logs:
+
+ import systemd.journal
+
+ reader = systemd.journal.Reader()
+ reader.add_match('CONTAINER_NAME=web')
+
+ for msg in reader:
+ print '{CONTAINER_ID_FULL}: {MESSAGE}'.format(**msg)
+
diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md
index 5a466461e..7c5113f6d 100644
--- a/docs/sources/reference/run.md
+++ b/docs/sources/reference/run.md
@@ -24,26 +24,30 @@ other `docker` command.
The basic `docker run` command takes this form:
- $ sudo docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
+ $ docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
To learn how to interpret the types of `[OPTIONS]`,
see [*Option types*](/reference/commandline/cli/#option-types).
-The list of `[OPTIONS]` breaks down into two groups:
+The `run` options control the image's runtime behavior in a container. These
+settings affect:
-1. Settings exclusive to operators, including:
- * Detached or Foreground running,
- * Container Identification,
- * Network settings, and
- * Runtime Constraints on CPU and Memory
- * Privileges and LXC Configuration
-2. Settings shared between operators and developers, where operators can
- override defaults developers set in images at build time.
+ * detached or foreground running
+ * container identification
+ * network settings
+ * runtime constraints on CPU and memory
+ * privileges and LXC configuration
+
+An image developer may set defaults for these same settings when they create the
+image using the `docker build` command. Operators, however, can override all
+defaults set by the developer using the `run` options. And, operators can also
+override nearly all the defaults set by the Docker runtime itself.
-Together, the `docker run [OPTIONS]` give the operator complete control over runtime
-behavior, allowing them to override all defaults set by
-the developer during `docker build` and nearly all the defaults set by
-the Docker runtime itself.
+Finally, depending on your Docker system configuration, you may be required to
+preface each `docker` command with `sudo`. To avoid having to use `sudo` with
+the `docker` command, your system administrator can create a Unix group called
+`docker` and add users to it. For more information about this configuration,
+refer to the Docker installation documentation for your operating system.
## Operator exclusive options
@@ -99,13 +103,18 @@ streams]( https://github.com/docker/docker/blob/
specify to which of the three standard streams (`STDIN`, `STDOUT`,
`STDERR`) you'd like to connect instead, as in:
- $ sudo docker run -a stdin -a stdout -i -t ubuntu /bin/bash
+ $ docker run -a stdin -a stdout -i -t ubuntu /bin/bash
For interactive processes (like a shell), you must use `-i -t` together in
order to allocate a tty for the container process. `-i -t` is often written `-it`
as you'll see in later examples. Specifying `-t` is forbidden when the client
standard output is redirected or piped, such as in:
-`echo test | sudo docker run -i busybox cat`.
+`echo test | docker run -i busybox cat`.
+
+>**Note**: A process running as PID 1 inside a container is treated
+>specially by Linux: it ignores any signal with the default action.
+>So, the process will not terminate on `SIGINT` or `SIGTERM` unless it is
+>coded to do so.
## Container identification
@@ -147,7 +156,8 @@ Images using the v2 or later image format have a content-addressable identifier
called a digest. As long as the input used to generate the image is unchanged,
the digest value is predictable and referenceable.
-## PID Settings (--pid)
+## PID settings (--pid)
+
--pid="" : Set the PID (Process) Namespace mode for the container,
'host': use the host's PID namespace inside the container
@@ -163,12 +173,29 @@ on the system. For example, you could build a container with debugging tools
like `strace` or `gdb`, but want to use these tools when debugging processes
within the container.
- $ sudo docker run --pid=host rhel7 strace -p 1234
+ $ docker run --pid=host rhel7 strace -p 1234
This command would allow you to use `strace` inside the container on pid 1234 on
the host.
-## IPC Settings (--ipc)
+## UTS settings (--uts)
+
+ --uts="" : Set the UTS namespace mode for the container,
+ 'host': use the host's UTS namespace inside the container
+
+The UTS namespace is for setting the hostname and the domain that is visible
+to running processes in that namespace. By default, all containers, including
+those with `--net=host`, have their own UTS namespace. The `host` setting will
+result in the container using the same UTS namespace as the host.
+
+You may wish to share the UTS namespace with the host if you would like the
+hostname of the container to change as the hostname of the host changes. A
+more advanced use case would be changing the host's hostname from a container.
+
+> **Note**: `--uts="host"` gives the container full access to change the
+> hostname of the host and is therefore considered insecure.
+
+## IPC settings (--ipc)
--ipc="" : Set the IPC mode for the container,
'container:': reuses another container's IPC namespace
@@ -207,9 +234,9 @@ networking. In cases like this, you would perform I/O through files or
Your container will use the same DNS servers as the host by default, but
you can override this with `--dns`.
-By default a random MAC is generated. You can set the container's MAC address
-explicitly by providing a MAC via the `--mac-address` parameter (format:
-`12:34:56:78:9a:bc`).
+By default, the MAC address is generated using the IP address allocated to the
+container. You can set the container's MAC address explicitly by providing a
+MAC address via the `--mac-address` parameter (format:`12:34:56:78:9a:bc`).
Supported networking modes are:
@@ -273,7 +300,15 @@ With the networking mode set to `host` a container will share the host's
network stack and all interfaces from the host will be available to the
container. The container's hostname will match the hostname on the host
system. Publishing ports and linking to other containers will not work
-when sharing the host's network stack.
+when sharing the host's network stack. Note that `--add-host` `--hostname`
+`--dns` `--dns-search` and `--mac-address` is invalid in `host` netmode.
+
+Compared to the default `bridge` mode, the `host` mode gives *significantly*
+better networking performance since it uses the host's native networking stack
+whereas the bridge has to go through one level of virtualization through the
+docker daemon. It is recommended to run containers in this mode when their
+networking performance is critical, for example, a production Load Balancer
+or a High Performance Web Server.
> **Note**: `--net="host"` gives the container full access to local system
> services such as D-bus and is therefore considered insecure.
@@ -282,15 +317,17 @@ when sharing the host's network stack.
With the networking mode set to `container` a container will share the
network stack of another container. The other container's name must be
-provided in the format of `--net container:`.
+provided in the format of `--net container:`. Note that `--add-host`
+`--hostname` `--dns` `--dns-search` and `--mac-address` is invalid
+in `container` netmode.
Example running a Redis container with Redis binding to `localhost` then
running the `redis-cli` command and connecting to the Redis server over the
`localhost` interface.
- $ sudo docker run -d --name redis example/redis --bind 127.0.0.1
+ $ docker run -d --name redis example/redis --bind 127.0.0.1
$ # use the redis container's network stack to access localhost
- $ sudo docker run --rm -it --net container:redis example/redis-cli -h 127.0.0.1
+ $ docker run --rm -it --net container:redis example/redis-cli -h 127.0.0.1
### Managing /etc/hosts
@@ -298,7 +335,7 @@ Your container will have lines in `/etc/hosts` which define the hostname of the
container itself as well as `localhost` and a few other common things. The
`--add-host` flag can be used to add additional lines to `/etc/hosts`.
- $ sudo docker run -it --add-host db-static:86.75.30.9 ubuntu cat /etc/hosts
+ $ docker run -it --add-host db-static:86.75.30.9 ubuntu cat /etc/hosts
172.17.0.22 09d03f76bf2c
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
@@ -364,7 +401,7 @@ This means the daemon will wait for 100 ms, then 200 ms, 400, 800, 1600,
and so on until either the `on-failure` limit is hit, or when you `docker stop`
or `docker rm -f` the container.
-If a container is succesfully restarted (the container is started and runs
+If a container is successfully restarted (the container is started and runs
for at least 10 seconds), the delay is reset to its default value of 100 ms.
You can specify the maximum amount of times Docker will try to restart the
@@ -374,7 +411,7 @@ for a container can be obtained via [`docker inspect`](
/reference/commandline/cli/#inspect). For example, to get the number of restarts
for container "my-container";
- $ sudo docker inspect -f "{{ .RestartCount }}" my-container
+ $ docker inspect -f "{{ .RestartCount }}" my-container
# 2
Or, to get the last time the container was (re)started;
@@ -388,12 +425,12 @@ results in an error.
###Examples
- $ sudo docker run --restart=always redis
+ $ docker run --restart=always redis
This will run the `redis` container with a restart policy of **always**
so that if the container exits, Docker will restart it.
- $ sudo docker run --restart=on-failure:10 redis
+ $ docker run --restart=on-failure:10 redis
This will run the `redis` container with a restart policy of **on-failure**
and a maximum restart count of 10. If the `redis` container exits with a
@@ -427,36 +464,49 @@ the `--security-opt` flag. For example, you can specify the MCS/MLS level, a
requirement for MLS systems. Specifying the level in the following command
allows you to share the same content between containers.
- # docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash
+ $ docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash
An MLS example might be:
- # docker run --security-opt label:level:TopSecret -i -t rhel7 bash
+ $ docker run --security-opt label:level:TopSecret -i -t rhel7 bash
To disable the security labeling for this container versus running with the
`--permissive` flag, use the following command:
- # docker run --security-opt label:disable -i -t fedora bash
+ $ docker run --security-opt label:disable -i -t fedora bash
If you want a tighter security policy on the processes within a container,
you can specify an alternate type for the container. You could run a container
that is only allowed to listen on Apache ports by executing the following
command:
- # docker run --security-opt label:type:svirt_apache_t -i -t centos bash
+ $ docker run --security-opt label:type:svirt_apache_t -i -t centos bash
Note:
You would have to write policy defining a `svirt_apache_t` type.
-## Runtime constraints on CPU and memory
+## Specifying custom cgroups
+
+Using the `--cgroup-parent` flag, you can pass a specific cgroup to run a
+container in. This allows you to create and manage cgroups on their own. You can
+define custom resources for those cgroups and put containers under a common
+parent group.
+
+## Runtime constraints on resources
The operator can also adjust the performance parameters of the
container:
- -m="": Memory limit (format: , where unit = b, k, m or g)
+ -m, --memory="": Memory limit (format: , where unit = b, k, m or g)
-memory-swap="": Total memory limit (memory + swap, format: , where unit = b, k, m or g)
- -c, --cpu-shares=0 CPU shares (relative weight)
+ -c, --cpu-shares=0: CPU shares (relative weight)
+ --cpu-period=0: Limit the CPU CFS (Completely Fair Scheduler) period
+ --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1)
+ --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
+ --cpu-quota=0: Limit the CPU CFS (Completely Fair Scheduler) quota
+ --blkio-weight=0: Block IO weight (relative weight) accepts a weight value between 10 and 1000.
+ --oom-kill-disable=true|false: Whether to disable OOM Killer for the container or not.
### Memory constraints
@@ -508,6 +558,52 @@ We have four ways to set memory usage:
+Examples:
+
+ $ docker run -ti ubuntu:14.04 /bin/bash
+
+We set nothing about memory, this means the processes in the container can use
+as much memory and swap memory as they need.
+
+ $ docker run -ti -m 300M --memory-swap -1 ubuntu:14.04 /bin/bash
+
+We set memory limit and disabled swap memory limit, this means the processes in
+the container can use 300M memory and as much swap memory as they need (if the
+host supports swap memory).
+
+ $ docker run -ti -m 300M ubuntu:14.04 /bin/bash
+
+We set memory limit only, this means the processes in the container can use
+300M memory and 300M swap memory, by default, the total virtual memory size
+(--memory-swap) will be set as double of memory, in this case, memory + swap
+would be 2*300M, so processes can use 300M swap memory as well.
+
+ $ docker run -ti -m 300M --memory-swap 1G ubuntu:14.04 /bin/bash
+
+We set both memory and swap memory, so the processes in the container can use
+300M memory and 700M swap memory.
+
+By default, Docker kills processes in a container if an out-of-memory (OOM)
+error occurs. To change this behaviour, use the `--oom-kill-disable` option.
+Only disable the OOM killer on containers where you have also set the
+`-m/--memory` option. If the `-m` flag is not set, this can result in the host
+running out of memory and require killing the host's system processes to free
+memory.
+
+Examples:
+
+The following example limits the memory to 100M and disables the OOM killer for
+this container:
+
+ $ docker run -ti -m 100M --oom-kill-disable ubuntu:14.04 /bin/bash
+
+The following example, illustrates a dangerous way to use the flag:
+
+ $ docker run -ti --oom-kill-disable ubuntu:14.04 /bin/bash
+
+The container has unlimited memory which can cause the host to run out memory
+and require killing system processes to free memory.
+
### CPU share constraint
By default, all containers get the same proportion of CPU cycles. This proportion
@@ -525,7 +621,7 @@ the number of containers running on the system.
For example, consider three containers, one has a cpu-share of 1024 and
two others have a cpu-share setting of 512. When processes in all three
containers attempt to use 100% of CPU, the first container would receive
-50% of the total CPU time. If you add a fouth container with a cpu-share
+50% of the total CPU time. If you add a fourth container with a cpu-share
of 1024, the first container only gets 33% of the CPU. The remaining containers
receive 16.5%, 16.5% and 33% of the CPU.
@@ -543,6 +639,82 @@ division of CPU shares:
101 {C1} 1 100% of CPU1
102 {C1} 2 100% of CPU2
+### CPU period constraint
+
+The default CPU CFS (Completely Fair Scheduler) period is 100ms. We can use
+`--cpu-period` to set the period of CPUs to limit the container's CPU usage.
+And usually `--cpu-period` should work with `--cpu-quota`.
+
+Examples:
+
+ $ docker run -ti --cpu-period=50000 --cpu-quota=25000 ubuntu:14.04 /bin/bash
+
+If there is 1 CPU, this means the container can get 50% CPU worth of run-time every 50ms.
+
+For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt).
+
+### Cpuset constraint
+
+We can set cpus in which to allow execution for containers.
+
+Examples:
+
+ $ docker run -ti --cpuset-cpus="1,3" ubuntu:14.04 /bin/bash
+
+This means processes in container can be executed on cpu 1 and cpu 3.
+
+ $ docker run -ti --cpuset-cpus="0-2" ubuntu:14.04 /bin/bash
+
+This means processes in container can be executed on cpu 0, cpu 1 and cpu 2.
+
+We can set mems in which to allow execution for containers. Only effective
+on NUMA systems.
+
+Examples:
+
+ $ docker run -ti --cpuset-mems="1,3" ubuntu:14.04 /bin/bash
+
+This example restricts the processes in the container to only use memory from
+memory nodes 1 and 3.
+
+ $ docker run -ti --cpuset-mems="0-2" ubuntu:14.04 /bin/bash
+
+This example restricts the processes in the container to only use memory from
+memory nodes 0, 1 and 2.
+
+### CPU quota constraint
+
+The `--cpu-quota` flag limits the container's CPU usage. The default 0 value
+allows the container to take 100% of a CPU resource (1 CPU). The CFS (Completely Fair
+Scheduler) handles resource allocation for executing processes and is default
+Linux Scheduler used by the kernel. Set this value to 50000 to limit the container
+to 50% of a CPU resource. For multiple CPUs, adjust the `--cpu-quota` as necessary.
+For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt).
+
+### Block IO bandwidth (Blkio) constraint
+
+By default, all containers get the same proportion of block IO bandwidth
+(blkio). This proportion is 500. To modify this proportion, change the
+container's blkio weight relative to the weighting of all other running
+containers using the `--blkio-weight` flag.
+
+The `--blkio-weight` flag can set the weighting to a value between 10 to 1000.
+For example, the commands below create two containers with different blkio
+weight:
+
+ $ docker run -ti --name c1 --blkio-weight 300 ubuntu:14.04 /bin/bash
+ $ docker run -ti --name c2 --blkio-weight 600 ubuntu:14.04 /bin/bash
+
+If you do block IO in the two containers at the same time, by, for example:
+
+ $ time dd if=/mnt/zerofile of=test.out bs=1M count=1024 oflag=direct
+
+You'll find that the proportion of time is the same as the proportion of blkio
+weights of the two containers.
+
+> **Note:** The blkio weight setting is only available for direct IO. Buffered IO
+> is not currently supported.
+
## Runtime privilege, Linux capabilities, and LXC configuration
--cap-add: Add Linux capabilities
@@ -570,31 +742,75 @@ If you want to limit access to a specific device or devices you can use
the `--device` flag. It allows you to specify one or more devices that
will be accessible within the container.
- $ sudo docker run --device=/dev/snd:/dev/snd ...
+ $ docker run --device=/dev/snd:/dev/snd ...
By default, the container will be able to `read`, `write`, and `mknod` these devices.
This can be overridden using a third `:rwm` set of options to each `--device` flag:
- $ sudo docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc
Command (m for help): q
- $ sudo docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc
You will not be able to write the partition table.
Command (m for help): q
- $ sudo docker run --device=/dev/sda:/dev/xvdc:w --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc:w --rm -it ubuntu fdisk /dev/xvdc
crash....
- $ sudo docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc
+ $ docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc
fdisk: unable to open /dev/xvdc: Operation not permitted
In addition to `--privileged`, the operator can have fine grain control over the
capabilities using `--cap-add` and `--cap-drop`. By default, Docker has a default
-list of capabilities that are kept. Both flags support the value `all`, so if the
+list of capabilities that are kept. The following table lists the Linux capability options which can be added or dropped.
+
+| Capability Key | Capability Description |
+| :----------------- | :---------------| :-------------------- |
+| SETPCAP | Modify process capabilities. |
+| SYS_MODULE| Load and unload kernel modules. |
+| SYS_RAWIO | Perform I/O port operations (iopl(2) and ioperm(2)). |
+| SYS_PACCT | Use acct(2), switch process accounting on or off. |
+| SYS_ADMIN | Perform a range of system administration operations. |
+| SYS_NICE | Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes. |
+| SYS_RESOURCE | Override resource Limits. |
+| SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. |
+| SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. |
+| MKNOD | Create special files using mknod(2). |
+| AUDIT_WRITE | Write records to kernel auditing log. |
+| AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules. |
+| MAC_OVERRIDE | Allow MAC configuration or state changes. Implemented for the Smack LSM. |
+| MAC_ADMIN | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). |
+| NET_ADMIN | Perform various network-related operations. |
+| SYSLOG | Perform privileged syslog(2) operations. |
+| CHOWN | Make arbitrary changes to file UIDs and GIDs (see chown(2)). |
+| NET_RAW | Use RAW and PACKET sockets. |
+| DAC_OVERRIDE | Bypass file read, write, and execute permission checks. |
+| FOWNER | Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file. |
+| DAC_READ_SEARCH | Bypass file read permission checks and directory read and execute permission checks. |
+| FSETID | Don't clear set-user-ID and set-group-ID permission bits when a file is modified. |
+| KILL | Bypass permission checks for sending signals. |
+| SETGID | Make arbitrary manipulations of process GIDs and supplementary GID list. |
+| SETUID | Make arbitrary manipulations of process UIDs. |
+| LINUX_IMMUTABLE | Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags. |
+| NET_BIND_SERVICE | Bind a socket to internet domain privileged ports (port numbers less than 1024). |
+| NET_BROADCAST | Make socket broadcasts, and listen to multicasts. |
+| IPC_LOCK | Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)). |
+| IPC_OWNER | Bypass permission checks for operations on System V IPC objects. |
+| SYS_CHROOT | Use chroot(2), change root directory. |
+| SYS_PTRACE | Trace arbitrary processes using ptrace(2). |
+| SYS_BOOT | Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution. |
+| LEASE | Establish leases on arbitrary files (see fcntl(2)). |
+| SETFCAP | Set file capabilities.|
+| WAKE_ALARM | Trigger something that will wake up the system. |
+| BLOCK_SUSPEND | Employ features that can block system suspend. |
+
+Further reference information is available on the [capabilities(7) - Linux man page](http://linux.die.net/man/7/capabilities)
+
+Both flags support the value `all`, so if the
operator wants to have all capabilities but `MKNOD` they could use:
- $ sudo docker run --cap-add=ALL --cap-drop=MKNOD ...
+ $ docker run --cap-add=ALL --cap-drop=MKNOD ...
For interacting with the network stack, instead of using `--privileged` they
should use `--cap-add=NET_ADMIN` to modify the network interfaces.
@@ -647,21 +863,29 @@ familiar with using LXC directly.
You can specify a different logging driver for the container than for the daemon.
-### Logging driver: none
+#### Logging driver: none
Disables any logging for the container. `docker logs` won't be available with
this driver.
-### Log driver: json-file
+#### Logging driver: json-file
Default logging driver for Docker. Writes JSON messages to file. `docker logs`
command is available only for this logging driver
-## Logging driver: syslog
+#### Logging driver: syslog
Syslog logging driver for Docker. Writes log messages to syslog. `docker logs`
command is not available for this logging driver
+#### Logging driver: journald
+
+Journald logging driver for Docker. Writes log messages to journald; the container id will be stored in the journal's `CONTAINER_ID` field. `docker logs` command is not available for this logging driver. For detailed information on working with this logging driver, see [the journald logging driver](reference/logging/journald) reference documentation.
+
+#### Log Opts :
+
+Logging options for configuring a log driver. The following log options are supported: [none]
+
## Overriding Dockerfile image defaults
When a developer builds an image from a [*Dockerfile*](/reference/builder)
@@ -687,7 +911,7 @@ Dockerfile instruction and how the operator can override that setting.
Recall the optional `COMMAND` in the Docker
commandline:
- $ sudo docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
+ $ docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
This command is optional because the person who created the `IMAGE` may
have already provided a default `COMMAND` using the Dockerfile `CMD`
@@ -714,12 +938,12 @@ runtime by using a string to specify the new `ENTRYPOINT`. Here is an
example of how to run a shell in a container that has been set up to
automatically run something else (like `/usr/bin/redis-server`):
- $ sudo docker run -i -t --entrypoint /bin/bash example/redis
+ $ docker run -i -t --entrypoint /bin/bash example/redis
or two examples of how to pass more parameters to that ENTRYPOINT:
- $ sudo docker run -i -t --entrypoint /bin/bash example/redis -c ls -l
- $ sudo docker run -i -t --entrypoint /usr/bin/redis-cli example/redis --help
+ $ docker run -i -t --entrypoint /bin/bash example/redis -c ls -l
+ $ docker run -i -t --entrypoint /usr/bin/redis-cli example/redis --help
## EXPOSE (incoming ports)
@@ -736,7 +960,7 @@ or override the Dockerfile's exposed defaults:
Both hostPort and containerPort can be specified as a range of ports.
When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`)
(use 'docker port' to see the actual mapping)
- --link="" : Add link to another container (:alias)
+ --link="" : Add link to another container (:alias or )
As mentioned previously, `EXPOSE` (and `--expose`) makes ports available
**in** a container for incoming connections. The port number on the
@@ -806,7 +1030,7 @@ Additionally, the operator can **set any environment variable** in the
container by using one or more `-e` flags, even overriding those mentioned
above, or already defined by the developer with a Dockerfile `ENV`:
- $ sudo docker run -e "deep=purple" --rm ubuntu /bin/bash -c export
+ $ docker run -e "deep=purple" --rm ubuntu /bin/bash -c export
declare -x HOME="/"
declare -x HOSTNAME="85bc26a0e200"
declare -x OLDPWD
@@ -824,23 +1048,23 @@ information for connecting to the service container. Let's imagine we have a
container running Redis:
# Start the service container, named redis-name
- $ sudo docker run -d --name redis-name dockerfiles/redis
+ $ docker run -d --name redis-name dockerfiles/redis
4241164edf6f5aca5b0e9e4c9eccd899b0b8080c64c0cd26efe02166c73208f3
# The redis-name container exposed port 6379
- $ sudo docker ps
+ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4241164edf6f $ dockerfiles/redis:latest /redis-stable/src/re 5 seconds ago Up 4 seconds 6379/tcp redis-name
# Note that there are no public ports exposed since we didn᾿t use -p or -P
- $ sudo docker port 4241164edf6f 6379
+ $ docker port 4241164edf6f 6379
2014/01/25 00:55:38 Error: No public port '6379' published for 4241164edf6f
Yet we can get information about the Redis container's exposed ports
with `--link`. Choose an alias that will form a
valid environment variable!
- $ sudo docker run --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c export
+ $ docker run --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c export
declare -x HOME="/"
declare -x HOSTNAME="acda7f7b1cdc"
declare -x OLDPWD
@@ -857,15 +1081,15 @@ valid environment variable!
And we can use that information to connect from another container as a client:
- $ sudo docker run -i -t --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c '/redis-stable/src/redis-cli -h $REDIS_ALIAS_PORT_6379_TCP_ADDR -p $REDIS_ALIAS_PORT_6379_TCP_PORT'
+ $ docker run -i -t --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c '/redis-stable/src/redis-cli -h $REDIS_ALIAS_PORT_6379_TCP_ADDR -p $REDIS_ALIAS_PORT_6379_TCP_PORT'
172.17.0.32:6379>
Docker will also map the private IP address to the alias of a linked
container by inserting an entry into `/etc/hosts`. You can use this
mechanism to communicate with a linked container by its alias:
- $ sudo docker run -d --name servicename busybox sleep 30
- $ sudo docker run -i -t --link servicename:servicealias busybox ping -c 1 servicealias
+ $ docker run -d --name servicename busybox sleep 30
+ $ docker run -i -t --link servicename:servicealias busybox ping -c 1 servicealias
If you restart the source container (`servicename` in this case), the recipient
container's `/etc/hosts` entry will be automatically updated.
diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md
index fe79d881d..f01d78385 100644
--- a/docs/sources/release-notes.md
+++ b/docs/sources/release-notes.md
@@ -1,75 +1,136 @@
-page_title: Docker 1.x Series Release Notes
-page_description: Release Notes for Docker 1.x.
+page_title: Docker 1.x series release notes
+page_description: Release notes for Docker 1.x.
page_keywords: docker, documentation, about, technology, understanding, release
-# Release Notes
+# Release notes version 1.6.0
+(2015-04-16)
You can view release notes for earlier version of Docker by selecting the
-desired version from the drop-down list at the top right of this page.
+desired version from the drop-down list at the top right of this page. For the
+formal release announcement, see [the Docker
+blog](https://blog.docker.com/2015/04/docker-release-1-6/).
-## Version 1.5.0
-(2015-02-03)
-For a complete list of patches, fixes, and other improvements, see the
-[merge PR on GitHub](https://github.com/docker/docker/pull/10286).
-*New Features*
+## Docker Engine 1.6.0 features
-* [1.6] The Docker daemon will no longer ignore unknown commands
- while processing a `Dockerfile`. Instead it will generate an error and halt
- processing.
-* The Docker daemon has now supports for IPv6 networking between containers
- and on the `docker0` bridge. For more information see the
- [IPv6 networking reference](/articles/networking/#ipv6).
-* Docker container filesystems can now be set to`--read-only`, restricting your
- container to writing to volumes [PR# 10093](https://github.com/docker/docker/pull/10093).
-* A new `docker stats CONTAINERID` command has been added to allow users to view a
- continuously updating stream of container resource usage statistics. See the
- [`stats` command line reference](/reference/commandline/cli/#stats) and the
- [container `stats` API reference](/reference/api/docker_remote_api_v1.17/#get-container-stats-based-on-resource-usage).
- **Note**: this feature is only enabled for the `libcontainer` exec-driver at this point.
-* Users can now specify the file to use as the `Dockerfile` by running
- `docker build -f alternate.dockerfile .`. This will allow the definition of multiple
- `Dockerfile`s for a single project. See the [`docker build` command reference](
-/reference/commandline/cli/#build) for more information.
-* The v1 Open Image specification has been created to document the current Docker image
- format and metadata. Please see [the Open Image specification document](
-https://github.com/docker/docker/blob/master/image/spec/v1.md) for more details.
-* This release also includes a number of significant performance improvements in
- build and image management ([PR #9720](https://github.com/docker/docker/pull/9720),
- [PR #8827](https://github.com/docker/docker/pull/8827))
-* The `docker inspect` command now lists ExecIDs generated for each `docker exec` process.
- See [PR #9800](https://github.com/docker/docker/pull/9800)) for more details.
-* The `docker inspect` command now shows the number of container restarts when there
- is a restart policy ([PR #9621](https://github.com/docker/docker/pull/9621))
-* This version of Docker is built using Go 1.4
+For a complete list of engine patches, fixes, and other improvements, see the
+[merge PR on GitHub](https://github.com/docker/docker/pull/11635). You'll also
+find [a changelog in the project
+repository](https://github.com/docker/docker/blob/master/CHANGELOG.md).
-> **Note:**
-> Development history prior to version 1.0 can be found by
-> searching in the [Docker GitHub repo](https://github.com/docker/docker).
-## Known Issues
+| Feature | Description |
+|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Container and Image Labels | Labels allow you to attach user-defined metadata to containers and images that can be used by your tools. For additional information on using labels, see [Apply custom metadata](http://docs.docker.com/userguide/labels-custom-metadata/#add-labels-to-images-the-label-instruction) in the documentation. |
+| Windows Client preview | The Windows Client can be used just like the Mac OS X client is today with a remote host. Our testing infrastructure was scaled out to accommodate Windows Client testing on every PR to the Engine. See the Azure blog for [details on using this new client](http://azure.microsoft.com/blog/2015/04/16/docker-client-for-windows-is-now-available). |
+| Logging drivers | The new logging driver follows the exec driver and storage driver concepts already available in Engine today. There is a new option `--log-driver` to `docker run` command. See the `run` reference for a [description on how to use this option](http://docs.docker.com/reference/run/#logging-drivers-log-driver). |
+| Image digests | When you pull, build, or run images, you specify them in the form `namespace/repository:tag`, or even just `repository`. In this release, you are now able to pull, run, build and refer to images by a new content addressable identifier called a “digest” with the syntax `namespace/repo@digest`. See the the command line reference for [examples of using the digest](http://docs.docker.com/reference/commandline/cli/#listing-image-digests). |
+| Custom cgroups | Containers are made from a combination of namespaces, capabilities, and cgroups. Docker already supports custom namespaces and capabilities. Additionally, in this release we’ve added support for custom cgroups. Using the `--cgroup-parent` flag, you can pass a specific `cgroup` to run a container in. See [the command line reference for more information](http://docs.docker.com/reference/commandline/cli/#create). |
+| Ulimits | You can now specify the default `ulimit` settings for all containers when configuring the daemon. For example:`docker -d --default-ulimit nproc=1024:2048` See [Default Ulimits](http://docs.docker.com/reference/commandline/cli/#default-ulimits) in this documentation. |
+| Commit and import Dockerfile | You can now make changes to images on the fly without having to re-build the entire image. The feature `commit --change` and `import --change` allows you to apply standard changes to a new image. These are expressed in the Dockerfile syntax and used to modify the image. For details on how to use these, see the [commit](http://docs.docker.com/reference/commandline/cli/#commit) and [import](http://docs.docker.com/reference/commandline/cli/#import). |
-This section lists significant known issues present in Docker as of release
-date. It is not exhaustive; it lists only issues with potentially significant
-impact on users. This list will be updated as issues are resolved.
+### Known issues in Engine
-* **Unexpected File Permissions in Containers**
-An idiosyncrasy in AUFS prevents permissions from propagating predictably
-between upper and lower layers. This can cause issues with accessing private
-keys, database instances, etc.
+This section lists significant known issues present in Docker as of release date.
+For an exhaustive list of issues, see [the issues list on the project
+repository](https://github.com/docker/docker/issues/).
-For systems that have recent aufs version (i.e., `dirperm1` mount option can
-be set), docker will attempt to fix the issue automatically by mounting
-the layers with `dirperm1` option. More details on `dirperm1` option can be
-found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
-
-For complete information and workarounds see
+* *Unexpected File Permissions in Containers*
+An idiosyncrasy in AUFS prevented permissions from propagating predictably
+between upper and lower layers. This caused issues with accessing private
+keys, database instances, etc. This issue was closed in this release:
[Github Issue 783](https://github.com/docker/docker/issues/783).
-* **Docker Hub incompatible with Safari 8**
-Docker Hub has multiple issues displaying on Safari 8, the default browser
-for OS X 10.10 (Yosemite). Users should access the hub using a different
-browser. Most notably, changes in the way Safari handles cookies means that the
-user is repeatedly logged out. For more information, see the [Docker
-forum post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300).
+
+* *Docker Hub incompatible with Safari 8*
+Docker Hub had multiple issues displaying on Safari 8, the default browser for
+OS X 10.10 (Yosemite). Most notably, changes in the way Safari handled cookies
+means that the user was repeatedly logged out.
+Recently, Safari fixed the bug that was causing all the issues. If you upgrade
+to Safari 8.0.5 which was just released last week and see if that fixes your
+issues. You might have to flush your cookies if it doesn't work right away.
+For more information, see the [Docker forum
+post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300).
+
+## Docker Registry 2.0 features
+
+This release includes Registry 2.0. The Docker Registry is a central server for
+pushing and pulling images. In this release, it was completely rewritten in Go
+around a new set of distribution APIs
+
+- **Webhook notifications**: You can now configure the Registry to send Webhooks
+when images are pushed. Spin off a CI build, send a notification to IRC –
+whatever you want! Included in the documentation is a detailed [notification
+specification](http://docs.docker.com/registry/notifications/).
+
+- **Native TLS support**: This release makes it easier to secure a registry with
+TLS. This documentation includes [expanded examples of secure
+deployments](http://docs.docker.com/registry/deploying/).
+
+- **New Distribution APIs**: This release includes an expanded set of new
+distribution APIs. You can read the [detailed specification
+here](http://docs.docker.com/registry/spec/api/).
+
+
+## Docker Compose 1.2
+
+For a complete list of compose patches, fixes, and other improvements, see the
+[changelog in the project
+repository](https://github.com/docker/compose/blob/master/CHANGES.md). The
+project also makes a [set of release
+notes](https://github.com/docker/compose/releases/tag/1.2.0) on the project.
+
+- **extends**: You can use `extends` to share configuration between services
+with the keyword “extends”. With extends, you can refer to a service defined
+elsewhere and include its configuration in a locally-defined service, while also
+adding or overriding configuration as necessary. The documentation describes
+[how to use extends in your
+configuration](http://docs.docker.com/compose/extends/#extending-services-in-
+compose).
+
+- **Relative directory handling may cause breaking change**: Compose now treats
+directories passed to build, filenames passed to `env_file` and volume host
+paths passed to volumes as relative to the configuration file's directory.
+Previously, they were treated as relative to the directory where you were
+running `docker-compose`. In the majority of cases, the location of the
+configuration file and where you ran `docker-compose` were the same directory.
+Now, you can use the `-f|--file` argument to specify a configuration file in
+another directory.
+
+
+## Docker Swarm 0.2
+
+You'll find the [release for download on
+GitHub](https://github.com/docker/swarm/releases/tag/v0.2.0) and [the
+documentation here](http://docs.docker.com/swarm/). This release includes the
+following features:
+
+- **Spread strategy**: A new strategy for scheduling containers on your cluster
+which evenly spreads them over available nodes.
+- **More Docker commands supported**: More progress has been made towards
+supporting the complete Docker API, such as pulling and inspecting images.
+- **Clustering drivers**: There are not any third-party drivers yet, but the
+first steps have been made towards making a pluggable driver interface that will
+make it possible to use Swarm with clustering systems such as Mesos.
+
+
+## Docker Machine 0.2 Pre-release
+
+You'll find the [release for download on
+GitHub](https://github.com/docker/machine/releases) and [the documentation
+here](http://docs.docker.com/machine/). For a complete list of machine changes
+see [the changelog in the project
+repository](https://github.com/docker/machine/blob/master/CHANGES.md#020-2015-03
+-22).
+
+- **Cleaner driver interface**: It is now much easier to write drivers for providers.
+- **More reliable and consistent provisioning**: Provisioning servers is now
+handled centrally by Machine instead of letting each driver individually do it.
+- **Regenerate TLS certificates**: A new command has been added to regenerate a
+host’s TLS certificates for good security practice and for if a host’s IP
+address changes.
+
+## Docker Hub Enterprise & Commercially Supported Docker Engine
+
+See the [DHE and CS Docker Engine release notes](docker-hub-enterprise/release-notes.md).
diff --git a/docs/sources/terms/container.md b/docs/sources/terms/container.md
index 8b4286878..d0c31c245 100644
--- a/docs/sources/terms/container.md
+++ b/docs/sources/terms/container.md
@@ -17,7 +17,7 @@ Image*](/terms/image)
and some additional information like its unique id, networking
configuration, and resource limits is called a **container**.
-## Container State
+## Container state
Containers can change, and so they have state. A container may be
**running** or **exited**.
diff --git a/docs/sources/terms/filesystem.md b/docs/sources/terms/filesystem.md
index 5587e3c83..814246d8b 100644
--- a/docs/sources/terms/filesystem.md
+++ b/docs/sources/terms/filesystem.md
@@ -1,8 +1,8 @@
-page_title: File Systems
+page_title: File system
page_description: How Linux organizes its persistent storage
page_keywords: containers, files, linux
-# File System
+# File system
## Introduction
diff --git a/docs/sources/terms/image.md b/docs/sources/terms/image.md
index e42a6cfa1..0a11d91c9 100644
--- a/docs/sources/terms/image.md
+++ b/docs/sources/terms/image.md
@@ -1,4 +1,4 @@
-page_title: Images
+page_title: Image
page_description: Definition of an image
page_keywords: containers, lxc, concepts, explanation, image, container
@@ -19,7 +19,7 @@ images do not have state.

-## Parent Image
+## Parent image

@@ -27,7 +27,7 @@ Each image may depend on one more image which forms the layer beneath
it. We sometimes say that the lower image is the **parent** of the upper
image.
-## Base Image
+## Base image
An image that has no parent is a **base image**.
diff --git a/docs/sources/terms/registry.md b/docs/sources/terms/registry.md
index 8a7e6237e..ad5a81d64 100644
--- a/docs/sources/terms/registry.md
+++ b/docs/sources/terms/registry.md
@@ -12,9 +12,9 @@ A Registry is a hosted service containing
The default registry can be accessed using a browser at
[Docker Hub](https://hub.docker.com) or using the
-`sudo docker search` command.
+`docker search` command.
-## Further Reading
+## Further reading
For more information see [*Working with
Repositories*](/userguide/dockerrepos/#working-with-the-repository)
diff --git a/docs/sources/terms/repository.md b/docs/sources/terms/repository.md
index c4d1d4353..4b8579924 100644
--- a/docs/sources/terms/repository.md
+++ b/docs/sources/terms/repository.md
@@ -13,11 +13,11 @@ server.
Images can be associated with a repository (or multiple) by giving them
an image name using one of three different commands:
-1. At build time (e.g., `sudo docker build -t IMAGENAME`),
+1. At build time (e.g., `docker build -t IMAGENAME`),
2. When committing a container (e.g.,
- `sudo docker commit CONTAINERID IMAGENAME`) or
+ `docker commit CONTAINERID IMAGENAME`) or
3. When tagging an image id with an image name (e.g.,
- `sudo docker tag IMAGEID IMAGENAME`).
+ `docker tag IMAGEID IMAGENAME`).
A Fully Qualified Image Name (FQIN) can be made up of 3 parts:
@@ -29,7 +29,7 @@ A Fully Qualified Image Name (FQIN) can be made up of 3 parts:
If you create a new repository which you want to share, you will need to
set at least the `user_name`, as the `default` blank `user_name` prefix is
-reserved for official Docker images.
+reserved for [Official Repositories](/docker-hub/official_repos).
For more information see [*Working with
Repositories*](/userguide/dockerrepos/#working-with-the-repository)
diff --git a/docs/sources/userguide/dockerhub.md b/docs/sources/userguide/dockerhub.md
index 62438b994..2f7170d64 100644
--- a/docs/sources/userguide/dockerhub.md
+++ b/docs/sources/userguide/dockerhub.md
@@ -2,7 +2,7 @@ page_title: Getting started with Docker Hub
page_description: Introductory guide to getting an account on Docker Hub
page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, central service, services, how to, container, containers, automation, collaboration, collaborators, registry, repo, repository, technology, github webhooks, trusted builds
-# Getting Started with Docker Hub
+# Getting started with Docker Hub
This section provides a quick introduction to the [Docker Hub](https://hub.docker.com),
@@ -10,7 +10,7 @@ including how to create an account.
The [Docker Hub](https://hub.docker.com) is a centralized resource for working with
Docker and its components. Docker Hub helps you collaborate with colleagues and get the
-most out of Docker.To do this, it provides services such as:
+most out of Docker. To do this, it provides services such as:
* Docker image hosting.
* User authentication.
@@ -21,7 +21,7 @@ most out of Docker.To do this, it provides services such as:
In order to use Docker Hub, you will first need to register and create an account. Don't
worry, creating an account is simple and free.
-## Creating a Docker Hub Account
+## Creating a Docker Hub account
There are two ways for you to register and create an account:
@@ -42,7 +42,7 @@ going on in the world of Docker.
You can also create a Docker Hub account via the command line with the
`docker login` command.
- $ sudo docker login
+ $ docker login
### Confirm your email
@@ -58,7 +58,7 @@ After you complete the confirmation process, you can login using the web console
Or via the command line with the `docker login` command:
- $ sudo docker login
+ $ docker login
Your Docker Hub account is now active and ready to use.
diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md
index 6224479fb..d2305152e 100644
--- a/docs/sources/userguide/dockerimages.md
+++ b/docs/sources/userguide/dockerimages.md
@@ -1,8 +1,8 @@
-page_title: Working with Docker Images
+page_title: Working with Docker images
page_description: How to work with Docker images.
page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration
-# Working with Docker Images
+# Working with Docker images
In the [introduction](/introduction/understanding-docker/) we've discovered that Docker
images are the basis of containers. In the
@@ -27,7 +27,7 @@ including:
Let's start with listing the images we have locally on our host. You can
do this using the `docker images` command like so:
- $ sudo docker images
+ $ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
training/webapp latest fc77f57ad303 3 weeks ago 280.5 MB
ubuntu 13.10 5e019ab7bf6d 4 weeks ago 180 MB
@@ -54,6 +54,13 @@ We can see three crucial pieces of information about our images in the listing.
* The tags for each image, for example `14.04`.
* The image ID of each image.
+> **Note:**
+> Previously, the `docker images` command supported the `--tree` and `--dot`
+> arguments, which displayed different visualizations of the image data. Docker
+> core removed this functionality in the 1.7 version. If you liked this
+> functionality, you can still find it in
+> [the third-party dockviz tool](https://github.com/justone/dockviz).
+
A repository potentially holds multiple variants of an image. In the case of
our `ubuntu` image we can see multiple variants covering Ubuntu 10.04, 12.04,
12.10, 13.04, 13.10 and 14.04. Each variant is identified by a tag and you can
@@ -63,11 +70,11 @@ refer to a tagged image like so:
So when we run a container we refer to a tagged image like so:
- $ sudo docker run -t -i ubuntu:14.04 /bin/bash
+ $ docker run -t -i ubuntu:14.04 /bin/bash
If instead we wanted to run an Ubuntu 12.04 image we'd use:
- $ sudo docker run -t -i ubuntu:12.04 /bin/bash
+ $ docker run -t -i ubuntu:12.04 /bin/bash
If you don't specify a variant, for example you just use `ubuntu`, then Docker
will default to using the `ubuntu:latest` image.
@@ -85,7 +92,7 @@ add some time to the launch of a container. If we want to pre-load an image we
can download it using the `docker pull` command. Let's say we'd like to
download the `centos` image.
- $ sudo docker pull centos
+ $ docker pull centos
Pulling repository centos
b7de3133ff98: Pulling dependent layers
5cc9e91966f7: Pulling fs layer
@@ -99,7 +106,7 @@ We can see that each layer of the image has been pulled down and now we
can run a container from this image and we won't have to wait to
download the image.
- $ sudo docker run -t -i centos /bin/bash
+ $ docker run -t -i centos /bin/bash
bash-4.1#
## Finding images
@@ -117,7 +124,7 @@ which to do our web application development. We can search for a suitable image
by using the `docker search` command to find all the images that contain the
term `sinatra`.
- $ sudo docker search sinatra
+ $ docker search sinatra
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
training/sinatra Sinatra training image 0 [OK]
marceldegraaf/sinatra Sinatra test app 0
@@ -131,11 +138,11 @@ term `sinatra`.
We can see we've returned a lot of images that use the term `sinatra`. We've
returned a list of image names, descriptions, Stars (which measure the social
popularity of images - if a user likes an image then they can "star" it), and
-the Official and Automated build statuses. Official repositories are built and
-maintained by the [Stackbrew](https://github.com/docker/stackbrew) project,
-and Automated repositories are [Automated Builds](
-/userguide/dockerrepos/#automated-builds) that allow you to validate the source
-and content of an image.
+the Official and Automated build statuses.
+[Official Repositories](/docker-hub/official_repos) are a carefully curated set
+of Docker repositories supported by Docker, Inc. Automated repositories are
+[Automated Builds](/userguide/dockerrepos/#automated-builds) that allow you to
+validate the source and content of an image.
We've reviewed the images available to use and we decided to use the
`training/sinatra` image. So far we've seen two types of images repositories,
@@ -152,11 +159,11 @@ prefixed with the user name, here `training`, of the user that created them.
We've identified a suitable image, `training/sinatra`, and now we can download it using the `docker pull` command.
- $ sudo docker pull training/sinatra
+ $ docker pull training/sinatra
The team can now use this image by running their own containers.
- $ sudo docker run -t -i training/sinatra /bin/bash
+ $ docker run -t -i training/sinatra /bin/bash
root@a8cb6ce02d85:/#
## Creating our own images
@@ -174,7 +181,7 @@ update and create images.
To update an image we first need to create a container from the image
we'd like to update.
- $ sudo docker run -t -i training/sinatra /bin/bash
+ $ docker run -t -i training/sinatra /bin/bash
root@0b2616b0e5a8:/#
> **Note:**
@@ -192,7 +199,7 @@ Now we have a container with the change we want to make. We can then
commit a copy of this container to an image using the `docker commit`
command.
- $ sudo docker commit -m "Added json gem" -a "Kate Smith" \
+ $ docker commit -m "Added json gem" -a "Kate Smith" \
0b2616b0e5a8 ouruser/sinatra:v2
4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c
@@ -215,7 +222,7 @@ the image: `v2`.
We can then look at our new `ouruser/sinatra` image using the `docker images`
command.
- $ sudo docker images
+ $ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
training/sinatra latest 5bc342fa0b91 10 hours ago 446.7 MB
ouruser/sinatra v2 3c59e02ddd1a 10 hours ago 446.7 MB
@@ -223,7 +230,7 @@ command.
To use our new image to create a container we can then:
- $ sudo docker run -t -i ouruser/sinatra:v2 /bin/bash
+ $ docker run -t -i ouruser/sinatra:v2 /bin/bash
root@78e82f680994:/#
### Building an image from a `Dockerfile`
@@ -242,6 +249,9 @@ Let's create a directory and a `Dockerfile` first.
$ cd sinatra
$ touch Dockerfile
+If you are using Boot2Docker on Windows, you may access your host
+directory by `cd` to `/c/Users/your_user_name`.
+
Each instruction creates a new layer of the image. Let's look at a simple
example now for building our own Sinatra image for our development team.
@@ -273,7 +283,7 @@ Sinatra gem.
Now let's take our `Dockerfile` and use the `docker build` command to build an image.
- $ sudo docker build -t ouruser/sinatra:v2 .
+ $ docker build -t ouruser/sinatra:v2 .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
@@ -467,7 +477,7 @@ containers will get removed to clean things up.
We can then create a container from our new image.
- $ sudo docker run -t -i ouruser/sinatra:v2 /bin/bash
+ $ docker run -t -i ouruser/sinatra:v2 /bin/bash
root@8196968dac35:/#
> **Note:**
@@ -489,19 +499,38 @@ You can also add a tag to an existing image after you commit or build it. We
can do this using the `docker tag` command. Let's add a new tag to our
`ouruser/sinatra` image.
- $ sudo docker tag 5db5f8471261 ouruser/sinatra:devel
+ $ docker tag 5db5f8471261 ouruser/sinatra:devel
The `docker tag` command takes the ID of the image, here `5db5f8471261`, and our
user name, the repository name and the new tag.
Let's see our new tag using the `docker images` command.
- $ sudo docker images ouruser/sinatra
+ $ docker images ouruser/sinatra
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ouruser/sinatra latest 5db5f8471261 11 hours ago 446.7 MB
ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB
ouruser/sinatra v2 5db5f8471261 11 hours ago 446.7 MB
+## Image Digests
+
+Images that use the v2 or later format have a content-addressable identifier
+called a `digest`. As long as the input used to generate the image is
+unchanged, the digest value is predictable. To list image digest values, use
+the `--digests` flag:
+
+ $ docker images --digests | head
+ REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE
+ ouruser/sinatra latest sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 5db5f8471261 11 hours ago 446.7 MB
+
+When pushing or pulling to a 2.0 registry, the `push` or `pull` command
+output includes the image digest. You can `pull` using a digest value.
+
+ $ docker pull ouruser/sinatra@cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
+
+You can also reference by digest in `create`, `run`, and `rmi` commands, as well as the
+`FROM` image reference in a Dockerfile.
+
## Push an image to Docker Hub
Once you've built or created a new image you can push it to [Docker
@@ -509,7 +538,7 @@ Hub](https://hub.docker.com) using the `docker push` command. This
allows you to share it with others, either publicly, or push it into [a
private repository](https://registry.hub.docker.com/plans/).
- $ sudo docker push ouruser/sinatra
+ $ docker push ouruser/sinatra
The push refers to a repository [ouruser/sinatra] (len: 1)
Sending image list
Pushing repository ouruser/sinatra (3 tags)
@@ -523,7 +552,7 @@ containers](
Let's delete the `training/sinatra` image as we don't need it anymore.
- $ sudo docker rmi training/sinatra
+ $ docker rmi training/sinatra
Untagged: training/sinatra:latest
Deleted: 5bc342fa0b91cabf65246837015197eecfa24b2213ed6a51a8974ae250fedd8d
Deleted: ed0fffdcdae5eb2c3a55549857a8be7fc8bc4241fb19ad714364cbfd7a56b22f
diff --git a/docs/sources/userguide/dockerizing.md b/docs/sources/userguide/dockerizing.md
index cc7bc8e1c..7124ba6c9 100644
--- a/docs/sources/userguide/dockerizing.md
+++ b/docs/sources/userguide/dockerizing.md
@@ -1,8 +1,8 @@
-page_title: Dockerizing Applications: A "Hello world"
+page_title: Dockerizing applications: A "Hello world"
page_description: A simple "Hello world" exercise that introduced you to Docker.
page_keywords: docker guide, docker, docker platform, virtualization framework, how to, dockerize, dockerizing apps, dockerizing applications, container, containers
-# Dockerizing Applications: A "Hello world"
+# Dockerizing applications: A "Hello world"
*So what's this Docker thing all about?*
@@ -15,7 +15,7 @@ application inside a container takes a single command: `docker run`.
Let's try it now.
- $ sudo docker run ubuntu:14.04 /bin/echo 'Hello world'
+ $ docker run ubuntu:14.04 /bin/echo 'Hello world'
Hello world
And you just launched your first container!
@@ -48,12 +48,12 @@ So what happened to our container after that? Well Docker containers
only run as long as the command you specify is active. Here, as soon as
`Hello world` was echoed, the container stopped.
-## An Interactive Container
+## An interactive container
Let's try the `docker run` command again, this time specifying a new
command to run in our container.
- $ sudo docker run -t -i ubuntu:14.04 /bin/bash
+ $ docker run -t -i ubuntu:14.04 /bin/bash
root@af8bae53bdd3:/#
Here we've again specified the `docker run` command and launched an
@@ -90,7 +90,7 @@ use the `exit` command or enter Ctrl-D to finish.
As with our previous container, once the Bash shell process has
finished, the container is stopped.
-## A Daemonized Hello world
+## A daemonized Hello world
Now a container that runs a command and then exits has some uses but
it's not overly helpful. Let's create a container that runs as a daemon,
@@ -98,7 +98,7 @@ like most of the applications we're probably going to run with Docker.
Again we can do this with the `docker run` command:
- $ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
+ $ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147
Wait, what? Where's our "hello world" output? Let's look at what we've run here.
@@ -135,7 +135,7 @@ do that with the `docker ps` command. The `docker ps` command queries
the Docker daemon for information about all the containers it knows
about.
- $ sudo docker ps
+ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e5535038e28 ubuntu:14.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute insane_babbage
@@ -155,7 +155,7 @@ Okay, so we now know it's running. But is it doing what we asked it to do? To se
we're going to look inside the container using the `docker logs`
command. Let's use the container name Docker assigned.
- $ sudo docker logs insane_babbage
+ $ docker logs insane_babbage
hello world
hello world
hello world
@@ -171,7 +171,7 @@ Now we've established we can create our own containers let's tidy up
after ourselves and stop our daemonized container. To do this we use the
`docker stop` command.
- $ sudo docker stop insane_babbage
+ $ docker stop insane_babbage
insane_babbage
The `docker stop` command tells Docker to politely stop the running
@@ -180,7 +180,7 @@ has just stopped.
Let's check it worked with the `docker ps` command.
- $ sudo docker ps
+ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Excellent. Our container has been stopped.
diff --git a/docs/sources/userguide/dockerlinks.md b/docs/sources/userguide/dockerlinks.md
index 79ba17900..2b7d30cd4 100644
--- a/docs/sources/userguide/dockerlinks.md
+++ b/docs/sources/userguide/dockerlinks.md
@@ -1,8 +1,8 @@
-page_title: Linking Containers Together
+page_title: Linking containers together
page_description: Learn how to connect Docker containers together.
page_keywords: Examples, Usage, user guide, links, linking, docker, documentation, examples, names, name, container naming, port, map, network port, network
-# Linking Containers Together
+# Linking containers together
In [the Using Docker section](/userguide/usingdocker), you saw how you can
connect to a service running inside a Docker container via a network
@@ -11,12 +11,12 @@ applications running inside Docker containers. In this section, we'll briefly re
connecting via a network port and then we'll introduce you to another method of access:
container linking.
-## Connect using Network port mapping
+## Connect using network port mapping
In [the Using Docker section](/userguide/usingdocker), you created a
container that ran a Python Flask application:
- $ sudo docker run -d -P training/webapp python app.py
+ $ docker run -d -P training/webapp python app.py
> **Note:**
> Containers have an internal network and an IP address
@@ -30,14 +30,15 @@ any network port inside it to a random high port within an *ephemeral port
range* on your Docker host. Next, when `docker ps` was run, you saw that port
5000 in the container was bound to port 49155 on the host.
- $ sudo docker ps nostalgic_morse
+ $ docker ps nostalgic_morse
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse
You also saw how you can bind a container's ports to a specific port using
-the `-p` flag:
+the `-p` flag. Here port 80 of the host is mapped to port 5000 of the
+container:
- $ sudo docker run -d -p 5000:5000 training/webapp python app.py
+ $ docker run -d -p 80:5000 training/webapp python app.py
And you saw why this isn't such a great idea because it constrains you to
only one container on that specific port.
@@ -47,26 +48,26 @@ default the `-p` flag will bind the specified port to all interfaces on
the host machine. But you can also specify a binding to a specific
interface, for example only to the `localhost`.
- $ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
+ $ docker run -d -p 127.0.0.1:80:5000 training/webapp python app.py
-This would bind port 5000 inside the container to port 5000 on the
+This would bind port 5000 inside the container to port 80 on the
`localhost` or `127.0.0.1` interface on the host machine.
Or, to bind port 5000 of the container to a dynamic port but only on the
`localhost`, you could use:
- $ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py
+ $ docker run -d -p 127.0.0.1::5000 training/webapp python app.py
You can also bind UDP ports by adding a trailing `/udp`. For example:
- $ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
+ $ docker run -d -p 127.0.0.1:80:5000/udp training/webapp python app.py
You also learned about the useful `docker port` shortcut which showed us the
current port bindings. This is also useful for showing you specific port
configurations. For example, if you've bound the container port to the
`localhost` on the host machine, then the `docker port` output will reflect that.
- $ sudo docker port nostalgic_morse 5000
+ $ docker port nostalgic_morse 5000
127.0.0.1:49155
> **Note:**
@@ -98,22 +99,22 @@ yourself. This naming provides two useful functions:
You can name your container by using the `--name` flag, for example:
- $ sudo docker run -d -P --name web training/webapp python app.py
+ $ docker run -d -P --name web training/webapp python app.py
This launches a new container and uses the `--name` flag to
name the container `web`. You can see the container's name using the
`docker ps` command.
- $ sudo docker ps -l
+ $ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aed84ee21bde training/webapp:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web
You can also use `docker inspect` to return the container's name.
- $ sudo docker inspect -f "{{ .Name }}" aed84ee21bde
+ $ docker inspect -f "{{ .Name }}" aed84ee21bde
/web
-> **Note:**
+> **Note:**
> Container names have to be unique. That means you can only call
> one container `web`. If you want to re-use a container name you must delete
> the old container (with `docker rm`) before you can create a new
@@ -129,7 +130,7 @@ source container and a recipient container. The recipient can then access select
about the source. To create a link, you use the `--link` flag. First, create a new
container, this time one containing a database.
- $ sudo docker run -d --name db training/postgres
+ $ docker run -d --name db training/postgres
This creates a new container called `db` from the `training/postgres`
image, which contains a PostgreSQL database.
@@ -137,11 +138,11 @@ image, which contains a PostgreSQL database.
Now, you need to delete the `web` container you created previously so you can replace it
with a linked one:
- $ sudo docker rm -f web
+ $ docker rm -f web
Now, create a new `web` container and link it with your `db` container.
- $ sudo docker run -d -P --name web --link db:db training/webapp python app.py
+ $ docker run -d -P --name web --link db:db training/webapp python app.py
This will link the new `web` container with the `db` container you created
earlier. The `--link` flag takes the form:
@@ -150,10 +151,18 @@ earlier. The `--link` flag takes the form:
Where `name` is the name of the container we're linking to and `alias` is an
alias for the link name. You'll see how that alias gets used shortly.
+The `--link` flag also takes the form:
+
+ --link
+
+In which case the alias will match the name. You could have written the previous
+example as:
+
+ $ docker run -d -P --name web --link db training/webapp python app.py
Next, inspect your linked containers with `docker inspect`:
- $ sudo docker inspect -f "{{ .HostConfig.Links }}" web
+ $ docker inspect -f "{{ .HostConfig.Links }}" web
[/db:/web/db]
You can see that the `web` container is now linked to the `db` container
@@ -174,7 +183,7 @@ recipient container in two ways:
* Environment variables,
* Updating the `/etc/hosts` file.
-### Environment Variables
+### Environment variables
Docker creates several environment variables when you link containers. Docker
automatically creates environment variables in the target container based on
@@ -239,7 +248,7 @@ Returning back to our database example, you can run the `env`
command to list the specified container's environment variables.
```
- $ sudo docker run --rm --name web2 --link db:db training/webapp env
+ $ docker run --rm --name web2 --link db:db training/webapp env
. . .
DB_NAME=/web2/db
DB_PORT=tcp://172.17.0.5:5432
@@ -276,7 +285,7 @@ In addition to the environment variables, Docker adds a host entry for the
source container to the `/etc/hosts` file. Here's an entry for the `web`
container:
- $ sudo docker run -t -i --rm --link db:webdb training/webapp /bin/bash
+ $ docker run -t -i --rm --link db:webdb training/webapp /bin/bash
root@aed84ee21bde:/opt/webapp# cat /etc/hosts
172.17.0.7 aed84ee21bde
. . .
@@ -314,9 +323,9 @@ If you restart the source container, the linked containers `/etc/hosts` files
will be automatically updated with the source container's new IP address,
allowing linked communication to continue.
- $ sudo docker restart db
+ $ docker restart db
db
- $ sudo docker run -t -i --rm --link db:db training/webapp /bin/bash
+ $ docker run -t -i --rm --link db:db training/webapp /bin/bash
root@aed84ee21bde:/opt/webapp# cat /etc/hosts
172.17.0.7 aed84ee21bde
. . .
diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md
index d8dc44e69..8fc2ba637 100644
--- a/docs/sources/userguide/dockerrepos.md
+++ b/docs/sources/userguide/dockerrepos.md
@@ -27,7 +27,7 @@ Typically, you'll want to start by creating an account on Docker Hub (if you hav
already) and logging in. You can create your account directly on
[Docker Hub](https://hub.docker.com/account/signup/), or by running:
- $ sudo docker login
+ $ docker login
This will prompt you for a user name, which will become the public namespace for your
public repositories.
@@ -45,22 +45,22 @@ You can search the [Docker Hub](https://hub.docker.com) registry via its search
interface or by using the command line interface. Searching can find images by image
name, user name, or description:
- $ sudo docker search centos
+ $ docker search centos
NAME DESCRIPTION STARS OFFICIAL TRUSTED
centos Official CentOS 6 Image as of 12 April 2014 88
tianon/centos CentOS 5 and 6, created using rinse instea... 21
...
-There you can see two example results: `centos` and
-`tianon/centos`. The second result shows that it comes from
-the public repository of a user, named `tianon/`, while the first result,
-`centos`, doesn't explicitly list a repository which means that it comes from the
-trusted top-level namespace. The `/` character separates a user's
-repository from the image name.
+There you can see two example results: `centos` and `tianon/centos`. The second
+result shows that it comes from the public repository of a user, named
+`tianon/`, while the first result, `centos`, doesn't explicitly list a
+repository which means that it comes from the trusted top-level namespace for
+[Official Repositories](/docker-hub/official_repos). The `/` character separates
+a user's repository from the image name.
Once you've found the image you want, you can download it with `docker pull `:
- $ sudo docker pull centos
+ $ docker pull centos
Pulling repository centos
0b443ba03958: Download complete
539c0211cd76: Download complete
@@ -86,7 +86,7 @@ or committed your container to a named image as we saw
Now you can push this repository to the registry designated by its name or tag.
- $ sudo docker push yourname/newimage
+ $ docker push yourname/newimage
The image will then be uploaded and available for use by your team-mates and/or the
community.
@@ -101,7 +101,7 @@ information [here](http://docs.docker.com/docker-hub/).
* Automated Builds
* Webhooks
-### Private Repositories
+### Private repositories
Sometimes you have images you don't want to make public and share with
everyone. So Docker Hub allows you to have private repositories. You can
@@ -150,7 +150,7 @@ repository.
You can create multiple Automated Builds per repository and configure them
to point to specific `Dockerfile`'s or Git branches.
-#### Build Triggers
+#### Build triggers
Automated Builds can also be triggered via a URL on Docker Hub. This
allows you to rebuild an Automated build image on demand.
diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md
index d53322465..c7126d7c3 100644
--- a/docs/sources/userguide/dockervolumes.md
+++ b/docs/sources/userguide/dockervolumes.md
@@ -1,8 +1,8 @@
-page_title: Managing Data in Containers
+page_title: Managing data in containers
page_description: How to manage data inside your Docker containers.
page_keywords: Examples, Usage, volume, docker, documentation, user guide, data, volumes
-# Managing Data in Containers
+# Managing data in containers
So far we've been introduced to some [basic Docker
concepts](/userguide/usingdocker/), seen how to work with [Docker
@@ -25,8 +25,8 @@ System*](/terms/layer/#union-file-system). Data volumes provide several
useful features for persistent or shared data:
- Volumes are initialized when a container is created. If the container's
- base image contains data at the specified mount point, that data is
- copied into the new volume.
+ base image contains data at the specified mount point, that existing data is
+ copied into the new volume upon volume initialization.
- Data volumes can be shared and reused among containers.
- Changes to a data volume are made directly.
- Changes to a data volume will not be included when you update an image.
@@ -44,7 +44,7 @@ You can add a data volume to a container using the `-v` flag with the
to mount multiple data volumes. Let's mount a single volume now in our web
application container.
- $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py
+ $ docker run -d -P --name web -v /webapp training/webapp python app.py
This will create a new volume inside a container at `/webapp`.
@@ -52,7 +52,28 @@ This will create a new volume inside a container at `/webapp`.
> You can also use the `VOLUME` instruction in a `Dockerfile` to add one or
> more new volumes to any container created from that image.
-### Mount a Host Directory as a Data Volume
+### Locating a volume
+
+You can locate the volume on the host by utilizing the 'docker inspect' command.
+
+ $ docker inspect web
+
+The output will provide details on the container configurations including the
+volumes. The output should look something similar to the following:
+
+ ...
+ "Volumes": {
+ "/webapp": "/var/lib/docker/volumes/fac362...80535"
+ },
+ "VolumesRW": {
+ "/webapp": true
+ }
+ ...
+
+You will notice in the above 'Volumes' is specifying the location on the host and
+'VolumesRW' is specifying that the volume is read/write.
+
+### Mount a host directory as a data volume
In addition to creating a volume using the `-v` flag you can also mount a
directory from your Docker daemon's host into a container.
@@ -65,7 +86,7 @@ directory from your Docker daemon's host into a container.
> `docker run -v /c/Users/:/ come from the Boot2Docker virtual machine's filesystem.
- $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
+ $ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
This will mount the host directory, `/src/webapp`, into the container at
`/opt/webapp`.
@@ -90,17 +111,17 @@ create it for you.
Docker defaults to a read-write volume but we can also mount a directory
read-only.
- $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py
+ $ docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py
Here we've mounted the same `/src/webapp` directory but we've added the `ro`
option to specify that the mount should be read-only.
-### Mount a Host File as a Data Volume
+### Mount a host file as a data volume
The `-v` flag can also be used to mount a single file - instead of *just*
directories - from the host machine.
- $ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
+ $ docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
This will drop you into a bash shell in a new container, you will have your bash
history from the host and when you exit the container, the host will have the
@@ -113,7 +134,7 @@ history of the commands typed while in the container.
> you want to edit the mounted file, it is often easiest to instead mount the
> parent directory.
-## Creating and mounting a Data Volume Container
+## Creating and mounting a data volume container
If you have some persistent data that you want to share between
containers, or want to use from non-persistent containers, it's best to
@@ -124,15 +145,15 @@ Let's create a new named container with a volume to share.
While this container doesn't run an application, it reuses the `training/postgres`
image so that all containers are using layers in common, saving disk space.
- $ sudo docker create -v /dbdata --name dbdata training/postgres /bin/true
+ $ docker create -v /dbdata --name dbdata training/postgres /bin/true
You can then use the `--volumes-from` flag to mount the `/dbdata` volume in another container.
- $ sudo docker run -d --volumes-from dbdata --name db1 training/postgres
+ $ docker run -d --volumes-from dbdata --name db1 training/postgres
And another:
- $ sudo docker run -d --volumes-from dbdata --name db2 training/postgres
+ $ docker run -d --volumes-from dbdata --name db2 training/postgres
In this case, if the `postgres` image contained a directory called `/dbdata`
then mounting the volumes from the `dbdata` container hides the
@@ -145,7 +166,7 @@ volumes from multiple containers.
You can also extend the chain by mounting the volume that came from the
`dbdata` container in yet another container via the `db1` or `db2` containers.
- $ sudo docker run -d --name db3 --volumes-from db1 training/postgres
+ $ docker run -d --name db3 --volumes-from db1 training/postgres
If you remove containers that mount volumes, including the initial `dbdata`
container, or the subsequent containers `db1` and `db2`, the volumes will not
@@ -168,7 +189,7 @@ backups, restores or migrations. We do this by using the
`--volumes-from` flag to create a new container that mounts that volume,
like so:
- $ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
+ $ docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
Here we've launched a new container and mounted the volume from the
`dbdata` container. We've then mounted a local host directory as
@@ -180,11 +201,11 @@ we'll be left with a backup of our `dbdata` volume.
You could then restore it to the same container, or another that you've made
elsewhere. Create a new container.
- $ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
+ $ docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
Then un-tar the backup file in the new container's data volume.
- $ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
+ $ docker run --volumes-from dbdata2 -v $(pwd):/backup ubuntu cd /dbdata && tar xvf /backup/backup.tar
You can use the techniques above to automate backup, migration and
restore testing using your preferred tools.
diff --git a/docs/sources/userguide/index.md b/docs/sources/userguide/index.md
index d0dbdb84e..9cc1c6db3 100644
--- a/docs/sources/userguide/index.md
+++ b/docs/sources/userguide/index.md
@@ -1,8 +1,8 @@
-page_title: The Docker User Guide
-page_description: The Docker User Guide home page
+page_title: The Docker user guide
+page_description: The Docker user guide home page
page_keywords: docker, introduction, documentation, about, technology, docker.io, user, guide, user's, manual, platform, framework, virtualization, home, intro
-# Welcome to the Docker User Guide
+# Welcome to the Docker user guide
In the [Introduction](/) you got a taste of what Docker is and how it
works. In this guide we're going to take you through the fundamentals of
@@ -19,7 +19,7 @@ We’ll teach you how to use Docker to:
We've broken this guide into major sections that take you through
the Docker life cycle:
-## Getting Started with Docker Hub
+## Getting started with Docker Hub
*How do I use Docker Hub?*
@@ -29,7 +29,7 @@ environment. To learn more:
Go to [Using Docker Hub](/userguide/dockerhub).
-## Dockerizing Applications: A "Hello world"
+## Dockerizing applications: A "Hello world"
*How do I run applications inside containers?*
@@ -38,7 +38,7 @@ applications. To learn how to Dockerize applications and run them:
Go to [Dockerizing Applications](/userguide/dockerizing).
-## Working with Containers
+## Working with containers
*How do I manage my containers?*
@@ -48,7 +48,7 @@ about how to inspect, monitor and manage containers:
Go to [Working With Containers](/userguide/usingdocker).
-## Working with Docker Images
+## Working with Docker images
*How can I access, share and build my own images?*
@@ -57,7 +57,7 @@ learn how to build your own application images with Docker.
Go to [Working with Docker Images](/userguide/dockerimages).
-## Linking Containers Together
+## Linking containers together
Until now we've seen how to build individual applications inside Docker
containers. Now learn how to build whole application stacks with Docker
@@ -65,7 +65,7 @@ by linking together multiple Docker containers.
Go to [Linking Containers Together](/userguide/dockerlinks).
-## Managing Data in Containers
+## Managing data in containers
Now we know how to link Docker containers together the next step is
learning how to manage data, volumes and mounts inside our containers.
diff --git a/docs/sources/userguide/labels-custom-metadata.md b/docs/sources/userguide/labels-custom-metadata.md
index 7cf25c060..79ac42ebf 100644
--- a/docs/sources/userguide/labels-custom-metadata.md
+++ b/docs/sources/userguide/labels-custom-metadata.md
@@ -129,10 +129,14 @@ You can view the labels via the `docker inspect` command:
}
...
- $ docker inspect -f "{{json .Labels }}" 4fa6e0f0c678
+ # Inspect labels on container
+ $ docker inspect -f "{{json .Config.Labels }}" 4fa6e0f0c678
{"Vendor":"ACME Incorporated","com.example.is-beta":"","com.example.version":"0.0.1-beta","com.example.release-date":"2015-02-12"}
+ # Inspect labels on images
+ $ docker inspect -f "{{json .ContainerConfig.Labels }}" myimage
+
## Query labels
@@ -171,6 +175,7 @@ These labels appear as part of the `docker info` output for the daemon:
Backing Filesystem: extfs
Dirs: 697
Execution Driver: native-0.2
+ Logging Driver: json-file
Kernel Version: 3.13.0-32-generic
Operating System: Ubuntu 14.04.1 LTS
CPUs: 1
@@ -179,7 +184,7 @@ These labels appear as part of the `docker info` output for the daemon:
ID: RC3P:JTCT:32YS:XYSB:YUBG:VFED:AAJZ:W3YW:76XO:D7NN:TEVU:UCRW
Debug mode (server): false
Debug mode (client): true
- Fds: 11
+ File Descriptors: 11
Goroutines: 14
EventsListeners: 0
Init Path: /usr/bin/docker
diff --git a/docs/sources/userguide/level1.md b/docs/sources/userguide/level1.md
index cca77dc36..320fbfee0 100644
--- a/docs/sources/userguide/level1.md
+++ b/docs/sources/userguide/level1.md
@@ -1,10 +1,10 @@
-page_title: Docker Images Test
+page_title: Docker images test
page_description: How to work with Docker images.
page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration
Back
-# Dockerfile Tutorial
+# Dockerfile tutorial
## Test your Dockerfile knowledge - Level 1
diff --git a/docs/sources/userguide/level2.md b/docs/sources/userguide/level2.md
index fe6654e71..96e91a1c6 100644
--- a/docs/sources/userguide/level2.md
+++ b/docs/sources/userguide/level2.md
@@ -1,10 +1,10 @@
-page_title: Docker Images Test
+page_title: Docker images test
page_description: How to work with Docker images.
page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration
Back
-#Dockerfile Tutorial
+#Dockerfile tutorial
## Test your Dockerfile knowledge - Level 2
diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md
index 8d57def4e..2c73929c9 100644
--- a/docs/sources/userguide/usingdocker.md
+++ b/docs/sources/userguide/usingdocker.md
@@ -1,8 +1,8 @@
-page_title: Working with Containers
+page_title: Working with containers
page_description: Learn how to manage and operate Docker containers.
page_keywords: docker, the docker guide, documentation, docker.io, monitoring containers, docker top, docker inspect, docker port, ports, docker logs, log, Logs
-# Working with Containers
+# Working with containers
In the [last section of the Docker User Guide](/userguide/dockerizing)
we launched our first containers. We launched two containers using the
@@ -27,12 +27,12 @@ flags and arguments.
# Usage: [sudo] docker [command] [flags] [arguments] ..
# Example:
- $ sudo docker run -i -t ubuntu /bin/bash
+ $ docker run -i -t ubuntu /bin/bash
Let's see this in action by using the `docker version` command to return
version information on the currently installed Docker client and daemon.
- $ sudo docker version
+ $ docker version
This command will not only provide you the version of Docker client and
daemon you are using, but also the version of Go (the programming
@@ -49,49 +49,31 @@ language powering Docker).
Last stable version: 0.8.0
-### Seeing what the Docker client can do
+## Get Docker command help
-We can see all of the commands available to us with the Docker client by
-running the `docker` binary without any options.
+You can display the help for specific Docker commands. The help details the
+options and their usage. To see a list of all the possible commands, use the
+following:
- $ sudo docker
+ $ docker --help
-You will see a list of all currently available commands.
+To see usage for a specific command, specify the command with the `--help` flag:
- Commands:
- attach Attach to a running container
- build Build an image from a Dockerfile
- commit Create a new image from a container's changes
- . . .
-
-### Seeing Docker command usage
-
-You can also zoom in and review the usage for specific Docker commands.
-
-Try typing Docker followed with a `[command]` to see the usage for that
-command:
-
- $ sudo docker attach
- Help output . . .
-
-Or you can also pass the `--help` flag to the `docker` binary.
-
- $ sudo docker attach --help
-
-This will display the help text and all available flags:
+ $ docker attach --help
Usage: docker attach [OPTIONS] CONTAINER
Attach to a running container
- --no-stdin=false: Do not attach stdin
- --sig-proxy=true: Proxify all received signal to the process (non-TTY mode only)
+ --help=false Print usage
+ --no-stdin=false Do not attach stdin
+ --sig-proxy=true Proxy all received signals to the process
> **Note:**
-> You can see a full list of Docker's commands
-> [here](/reference/commandline/cli/).
+> For further details and examples of each command, see the
+> [command reference](/reference/commandline/cli/) in this guide.
-## Running a Web Application in Docker
+## Running a web application in Docker
So now we've learnt a bit more about the `docker` client let's move onto
the important stuff: running more containers. So far none of the
@@ -102,7 +84,7 @@ Docker.
For our web application we're going to run a Python Flask application.
Let's start with a `docker run` command.
- $ sudo docker run -d -P training/webapp python app.py
+ $ docker run -d -P training/webapp python app.py
Let's review what our command did. We've specified two flags: `-d` and
`-P`. We've already seen the `-d` flag which tells Docker to run the
@@ -121,11 +103,11 @@ Lastly, we've specified a command for our container to run: `python app.py`. Thi
> reference](/reference/commandline/cli/#run) and the [Docker Run
> Reference](/reference/run/).
-## Viewing our Web Application Container
+## Viewing our web application container
Now let's see our running container using the `docker ps` command.
- $ sudo docker ps -l
+ $ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse
@@ -160,9 +142,9 @@ to a high port (from *ephemeral port range* which typically ranges from 32768
to 61000) on the local Docker host. We can also bind Docker containers to
specific ports using the `-p` flag, for example:
- $ sudo docker run -d -p 5000:5000 training/webapp python app.py
+ $ docker run -d -p 80:5000 training/webapp python app.py
-This would map port 5000 inside our container to port 5000 on our local
+This would map port 5000 inside our container to port 80 on our local
host. You might be asking about now: why wouldn't we just want to always
use 1:1 port mappings in Docker containers rather than mapping to high
ports? Well 1:1 mappings have the constraint of only being able to map
@@ -179,35 +161,35 @@ see the application.
Our Python application is live!
> **Note:**
-> If you have used the boot2docker virtual machine on OS X, Windows or Linux,
+> If you have used the `boot2docker` virtual machine on OS X, Windows or Linux,
> you'll need to get the IP of the virtual host instead of using localhost.
-> You can do this by running the following in
-> the boot2docker shell.
+> You can do this by running the following outside of the `boot2docker` shell
+> (i.e., from your comment line or terminal application).
>
> $ boot2docker ip
> The VM's Host only interface IP address is: 192.168.59.103
>
> In this case you'd browse to http://192.168.59.103:49155 for the above example.
-## A Network Port Shortcut
+## A network port shortcut
Using the `docker ps` command to return the mapped port is a bit clumsy so
Docker has a useful shortcut we can use: `docker port`. To use `docker port` we
specify the ID or name of our container and then the port for which we need the
corresponding public-facing port.
- $ sudo docker port nostalgic_morse 5000
+ $ docker port nostalgic_morse 5000
0.0.0.0:49155
In this case we've looked up what port is mapped externally to port 5000 inside
the container.
-## Viewing the Web Application's Logs
+## Viewing the web application's logs
Let's also find out a bit more about what's happening with our application and
use another of the commands we've learnt, `docker logs`.
- $ sudo docker logs -f nostalgic_morse
+ $ docker logs -f nostalgic_morse
* Running on http://0.0.0.0:5000/
10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 -
10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 -
@@ -217,25 +199,25 @@ logs` command to act like the `tail -f` command and watch the
container's standard out. We can see here the logs from Flask showing
the application running on port 5000 and the access log entries for it.
-## Looking at our Web Application Container's processes
+## Looking at our web application container's processes
In addition to the container's logs we can also examine the processes
running inside it using the `docker top` command.
- $ sudo docker top nostalgic_morse
+ $ docker top nostalgic_morse
PID USER COMMAND
854 root python app.py
Here we can see our `python app.py` command is the only process running inside
the container.
-## Inspecting our Web Application Container
+## Inspecting our web application container
Lastly, we can take a low-level dive into our Docker container using the
`docker inspect` command. It returns a JSON hash of useful configuration
and status information about Docker containers.
- $ sudo docker inspect nostalgic_morse
+ $ docker inspect nostalgic_morse
Let's see a sample of that JSON output.
@@ -255,30 +237,30 @@ Let's see a sample of that JSON output.
We can also narrow down the information we want to return by requesting a
specific element, for example to return the container's IP address we would:
- $ sudo docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse
+ $ docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse
172.17.0.5
-## Stopping our Web Application Container
+## Stopping our web application container
Okay we've seen web application working. Now let's stop it using the
`docker stop` command and the name of our container: `nostalgic_morse`.
- $ sudo docker stop nostalgic_morse
+ $ docker stop nostalgic_morse
nostalgic_morse
We can now use the `docker ps` command to check if the container has
been stopped.
- $ sudo docker ps -l
+ $ docker ps -l
-## Restarting our Web Application Container
+## Restarting our web application container
Oops! Just after you stopped the container you get a call to say another
developer needs the container back. From here you have two choices: you
can create a new container or restart the old one. Let's look at
starting our previous container back up.
- $ sudo docker start nostalgic_morse
+ $ docker start nostalgic_morse
nostalgic_morse
Now quickly run `docker ps -l` again to see the running container is
@@ -289,22 +271,22 @@ responds.
> Also available is the `docker restart` command that runs a stop and
> then start on the container.
-## Removing our Web Application Container
+## Removing our web application container
Your colleague has let you know that they've now finished with the container
and won't need it again. So let's remove it using the `docker rm` command.
- $ sudo docker rm nostalgic_morse
+ $ docker rm nostalgic_morse
Error: Impossible to remove a running container, please stop it first or use -f
2014/05/24 08:12:56 Error: failed to remove one or more containers
-What's happened? We can't actually remove a running container. This protects
+What happened? We can't actually remove a running container. This protects
you from accidentally removing a running container you might need. Let's try
this again by stopping the container first.
- $ sudo docker stop nostalgic_morse
+ $ docker stop nostalgic_morse
nostalgic_morse
- $ sudo docker rm nostalgic_morse
+ $ docker rm nostalgic_morse
nostalgic_morse
And now our container is stopped and deleted.
diff --git a/engine/engine.go b/engine/engine.go
deleted file mode 100644
index 60532349a..000000000
--- a/engine/engine.go
+++ /dev/null
@@ -1,255 +0,0 @@
-package engine
-
-import (
- "bufio"
- "fmt"
- "io"
- "os"
- "sort"
- "strings"
- "sync"
- "time"
-
- "github.com/docker/docker/pkg/common"
- "github.com/docker/docker/pkg/ioutils"
-)
-
-// Installer is a standard interface for objects which can "install" themselves
-// on an engine by registering handlers.
-// This can be used as an entrypoint for external plugins etc.
-type Installer interface {
- Install(*Engine) error
-}
-
-type Handler func(*Job) Status
-
-var globalHandlers map[string]Handler
-
-func init() {
- globalHandlers = make(map[string]Handler)
-}
-
-func Register(name string, handler Handler) error {
- _, exists := globalHandlers[name]
- if exists {
- return fmt.Errorf("Can't overwrite global handler for command %s", name)
- }
- globalHandlers[name] = handler
- return nil
-}
-
-func unregister(name string) {
- delete(globalHandlers, name)
-}
-
-// The Engine is the core of Docker.
-// It acts as a store for *containers*, and allows manipulation of these
-// containers by executing *jobs*.
-type Engine struct {
- handlers map[string]Handler
- catchall Handler
- hack Hack // data for temporary hackery (see hack.go)
- id string
- Stdout io.Writer
- Stderr io.Writer
- Stdin io.Reader
- Logging bool
- tasks sync.WaitGroup
- l sync.RWMutex // lock for shutdown
- shutdownWait sync.WaitGroup
- shutdown bool
- onShutdown []func() // shutdown handlers
-}
-
-func (eng *Engine) Register(name string, handler Handler) error {
- _, exists := eng.handlers[name]
- if exists {
- return fmt.Errorf("Can't overwrite handler for command %s", name)
- }
- eng.handlers[name] = handler
- return nil
-}
-
-func (eng *Engine) RegisterCatchall(catchall Handler) {
- eng.catchall = catchall
-}
-
-// New initializes a new engine.
-func New() *Engine {
- eng := &Engine{
- handlers: make(map[string]Handler),
- id: common.RandomString(),
- Stdout: os.Stdout,
- Stderr: os.Stderr,
- Stdin: os.Stdin,
- Logging: true,
- }
- eng.Register("commands", func(job *Job) Status {
- for _, name := range eng.commands() {
- job.Printf("%s\n", name)
- }
- return StatusOK
- })
- // Copy existing global handlers
- for k, v := range globalHandlers {
- eng.handlers[k] = v
- }
- return eng
-}
-
-func (eng *Engine) String() string {
- return fmt.Sprintf("%s", eng.id[:8])
-}
-
-// Commands returns a list of all currently registered commands,
-// sorted alphabetically.
-func (eng *Engine) commands() []string {
- names := make([]string, 0, len(eng.handlers))
- for name := range eng.handlers {
- names = append(names, name)
- }
- sort.Strings(names)
- return names
-}
-
-// Job creates a new job which can later be executed.
-// This function mimics `Command` from the standard os/exec package.
-func (eng *Engine) Job(name string, args ...string) *Job {
- job := &Job{
- Eng: eng,
- Name: name,
- Args: args,
- Stdin: NewInput(),
- Stdout: NewOutput(),
- Stderr: NewOutput(),
- env: &Env{},
- closeIO: true,
-
- cancelled: make(chan struct{}),
- }
- if eng.Logging {
- job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr))
- }
-
- // Catchall is shadowed by specific Register.
- if handler, exists := eng.handlers[name]; exists {
- job.handler = handler
- } else if eng.catchall != nil && name != "" {
- // empty job names are illegal, catchall or not.
- job.handler = eng.catchall
- }
- return job
-}
-
-// OnShutdown registers a new callback to be called by Shutdown.
-// This is typically used by services to perform cleanup.
-func (eng *Engine) OnShutdown(h func()) {
- eng.l.Lock()
- eng.onShutdown = append(eng.onShutdown, h)
- eng.shutdownWait.Add(1)
- eng.l.Unlock()
-}
-
-// Shutdown permanently shuts down eng as follows:
-// - It refuses all new jobs, permanently.
-// - It waits for all active jobs to complete (with no timeout)
-// - It calls all shutdown handlers concurrently (if any)
-// - It returns when all handlers complete, or after 15 seconds,
-// whichever happens first.
-func (eng *Engine) Shutdown() {
- eng.l.Lock()
- if eng.shutdown {
- eng.l.Unlock()
- eng.shutdownWait.Wait()
- return
- }
- eng.shutdown = true
- eng.l.Unlock()
- // We don't need to protect the rest with a lock, to allow
- // for other calls to immediately fail with "shutdown" instead
- // of hanging for 15 seconds.
- // This requires all concurrent calls to check for shutdown, otherwise
- // it might cause a race.
-
- // Wait for all jobs to complete.
- // Timeout after 5 seconds.
- tasksDone := make(chan struct{})
- go func() {
- eng.tasks.Wait()
- close(tasksDone)
- }()
- select {
- case <-time.After(time.Second * 5):
- case <-tasksDone:
- }
-
- // Call shutdown handlers, if any.
- // Timeout after 10 seconds.
- for _, h := range eng.onShutdown {
- go func(h func()) {
- h()
- eng.shutdownWait.Done()
- }(h)
- }
- done := make(chan struct{})
- go func() {
- eng.shutdownWait.Wait()
- close(done)
- }()
- select {
- case <-time.After(time.Second * 10):
- case <-done:
- }
- return
-}
-
-// IsShutdown returns true if the engine is in the process
-// of shutting down, or already shut down.
-// Otherwise it returns false.
-func (eng *Engine) IsShutdown() bool {
- eng.l.RLock()
- defer eng.l.RUnlock()
- return eng.shutdown
-}
-
-// ParseJob creates a new job from a text description using a shell-like syntax.
-//
-// The following syntax is used to parse `input`:
-//
-// * Words are separated using standard whitespaces as separators.
-// * Quotes and backslashes are not interpreted.
-// * Words of the form 'KEY=[VALUE]' are added to the job environment.
-// * All other words are added to the job arguments.
-//
-// For example:
-//
-// job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world")
-//
-// The resulting job will have:
-// job.Args={"echo", "hello", "world"}
-// job.Env={"VERBOSE":"1", "TEST":"true"}
-//
-func (eng *Engine) ParseJob(input string) (*Job, error) {
- // FIXME: use a full-featured command parser
- scanner := bufio.NewScanner(strings.NewReader(input))
- scanner.Split(bufio.ScanWords)
- var (
- cmd []string
- env Env
- )
- for scanner.Scan() {
- word := scanner.Text()
- kv := strings.SplitN(word, "=", 2)
- if len(kv) == 2 {
- env.Set(kv[0], kv[1])
- } else {
- cmd = append(cmd, word)
- }
- }
- if len(cmd) == 0 {
- return nil, fmt.Errorf("empty command: '%s'", input)
- }
- job := eng.Job(cmd[0], cmd[1:]...)
- job.Env().Init(&env)
- return job, nil
-}
diff --git a/engine/engine_test.go b/engine/engine_test.go
deleted file mode 100644
index 96c3f0df3..000000000
--- a/engine/engine_test.go
+++ /dev/null
@@ -1,236 +0,0 @@
-package engine
-
-import (
- "bytes"
- "strings"
- "testing"
-
- "github.com/docker/docker/pkg/ioutils"
-)
-
-func TestRegister(t *testing.T) {
- if err := Register("dummy1", nil); err != nil {
- t.Fatal(err)
- }
-
- if err := Register("dummy1", nil); err == nil {
- t.Fatalf("Expecting error, got none")
- }
- // Register is global so let's cleanup to avoid conflicts
- defer unregister("dummy1")
-
- eng := New()
-
- //Should fail because global handlers are copied
- //at the engine creation
- if err := eng.Register("dummy1", nil); err == nil {
- t.Fatalf("Expecting error, got none")
- }
-
- if err := eng.Register("dummy2", nil); err != nil {
- t.Fatal(err)
- }
-
- if err := eng.Register("dummy2", nil); err == nil {
- t.Fatalf("Expecting error, got none")
- }
- defer unregister("dummy2")
-}
-
-func TestJob(t *testing.T) {
- eng := New()
- job1 := eng.Job("dummy1", "--level=awesome")
-
- if job1.handler != nil {
- t.Fatalf("job1.handler should be empty")
- }
-
- h := func(j *Job) Status {
- j.Printf("%s\n", j.Name)
- return 42
- }
-
- eng.Register("dummy2", h)
- defer unregister("dummy2")
- job2 := eng.Job("dummy2", "--level=awesome")
-
- if job2.handler == nil {
- t.Fatalf("job2.handler shouldn't be nil")
- }
-
- if job2.handler(job2) != 42 {
- t.Fatalf("handler dummy2 was not found in job2")
- }
-}
-
-func TestEngineShutdown(t *testing.T) {
- eng := New()
- if eng.IsShutdown() {
- t.Fatalf("Engine should not show as shutdown")
- }
- eng.Shutdown()
- if !eng.IsShutdown() {
- t.Fatalf("Engine should show as shutdown")
- }
-}
-
-func TestEngineCommands(t *testing.T) {
- eng := New()
- handler := func(job *Job) Status { return StatusOK }
- eng.Register("foo", handler)
- eng.Register("bar", handler)
- eng.Register("echo", handler)
- eng.Register("die", handler)
- var output bytes.Buffer
- commands := eng.Job("commands")
- commands.Stdout.Add(&output)
- commands.Run()
- expected := "bar\ncommands\ndie\necho\nfoo\n"
- if result := output.String(); result != expected {
- t.Fatalf("Unexpected output:\nExpected = %v\nResult = %v\n", expected, result)
- }
-}
-
-func TestEngineString(t *testing.T) {
- eng1 := New()
- eng2 := New()
- s1 := eng1.String()
- s2 := eng2.String()
- if eng1 == eng2 {
- t.Fatalf("Different engines should have different names (%v == %v)", s1, s2)
- }
-}
-
-func TestParseJob(t *testing.T) {
- eng := New()
- // Verify that the resulting job calls to the right place
- var called bool
- eng.Register("echo", func(job *Job) Status {
- called = true
- return StatusOK
- })
- input := "echo DEBUG=1 hello world VERBOSITY=42"
- job, err := eng.ParseJob(input)
- if err != nil {
- t.Fatal(err)
- }
- if job.Name != "echo" {
- t.Fatalf("Invalid job name: %v", job.Name)
- }
- if strings.Join(job.Args, ":::") != "hello:::world" {
- t.Fatalf("Invalid job args: %v", job.Args)
- }
- if job.Env().Get("DEBUG") != "1" {
- t.Fatalf("Invalid job env: %v", job.Env)
- }
- if job.Env().Get("VERBOSITY") != "42" {
- t.Fatalf("Invalid job env: %v", job.Env)
- }
- if len(job.Env().Map()) != 2 {
- t.Fatalf("Invalid job env: %v", job.Env)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- if !called {
- t.Fatalf("Job was not called")
- }
-}
-
-func TestCatchallEmptyName(t *testing.T) {
- eng := New()
- var called bool
- eng.RegisterCatchall(func(job *Job) Status {
- called = true
- return StatusOK
- })
- err := eng.Job("").Run()
- if err == nil {
- t.Fatalf("Engine.Job(\"\").Run() should return an error")
- }
- if called {
- t.Fatalf("Engine.Job(\"\").Run() should return an error")
- }
-}
-
-// Ensure that a job within a job both using the same underlying standard
-// output writer does not close the output of the outer job when the inner
-// job's stdout is wrapped with a NopCloser. When not wrapped, it should
-// close the outer job's output.
-func TestNestedJobSharedOutput(t *testing.T) {
- var (
- outerHandler Handler
- innerHandler Handler
- wrapOutput bool
- )
-
- outerHandler = func(job *Job) Status {
- job.Stdout.Write([]byte("outer1"))
-
- innerJob := job.Eng.Job("innerJob")
-
- if wrapOutput {
- innerJob.Stdout.Add(ioutils.NopWriteCloser(job.Stdout))
- } else {
- innerJob.Stdout.Add(job.Stdout)
- }
-
- if err := innerJob.Run(); err != nil {
- t.Fatal(err)
- }
-
- // If wrapOutput was *false* this write will do nothing.
- // FIXME (jlhawn): It should cause an error to write to
- // closed output.
- job.Stdout.Write([]byte(" outer2"))
-
- return StatusOK
- }
-
- innerHandler = func(job *Job) Status {
- job.Stdout.Write([]byte(" inner"))
-
- return StatusOK
- }
-
- eng := New()
- eng.Register("outerJob", outerHandler)
- eng.Register("innerJob", innerHandler)
-
- // wrapOutput starts *false* so the expected
- // output of running the outer job will be:
- //
- // "outer1 inner"
- //
- outBuf := new(bytes.Buffer)
- outerJob := eng.Job("outerJob")
- outerJob.Stdout.Add(outBuf)
-
- if err := outerJob.Run(); err != nil {
- t.Fatal(err)
- }
-
- expectedOutput := "outer1 inner"
- if outBuf.String() != expectedOutput {
- t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String())
- }
-
- // Set wrapOutput to true so that the expected
- // output of running the outer job will be:
- //
- // "outer1 inner outer2"
- //
- wrapOutput = true
- outBuf.Reset()
- outerJob = eng.Job("outerJob")
- outerJob.Stdout.Add(outBuf)
-
- if err := outerJob.Run(); err != nil {
- t.Fatal(err)
- }
-
- expectedOutput = "outer1 inner outer2"
- if outBuf.String() != expectedOutput {
- t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String())
- }
-}
diff --git a/engine/env.go b/engine/env.go
deleted file mode 100644
index a671f13c6..000000000
--- a/engine/env.go
+++ /dev/null
@@ -1,310 +0,0 @@
-package engine
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "strconv"
- "strings"
- "time"
-
- "github.com/docker/docker/utils"
-)
-
-type Env []string
-
-// Get returns the last value associated with the given key. If there are no
-// values associated with the key, Get returns the empty string.
-func (env *Env) Get(key string) (value string) {
- // not using Map() because of the extra allocations https://github.com/docker/docker/pull/7488#issuecomment-51638315
- for _, kv := range *env {
- if strings.Index(kv, "=") == -1 {
- continue
- }
- parts := strings.SplitN(kv, "=", 2)
- if parts[0] != key {
- continue
- }
- if len(parts) < 2 {
- value = ""
- } else {
- value = parts[1]
- }
- }
- return
-}
-
-func (env *Env) Exists(key string) bool {
- _, exists := env.Map()[key]
- return exists
-}
-
-// Len returns the number of keys in the environment.
-// Note that len(env) might be different from env.Len(),
-// because the same key might be set multiple times.
-func (env *Env) Len() int {
- return len(env.Map())
-}
-
-func (env *Env) Init(src *Env) {
- (*env) = make([]string, 0, len(*src))
- for _, val := range *src {
- (*env) = append((*env), val)
- }
-}
-
-func (env *Env) GetBool(key string) (value bool) {
- s := strings.ToLower(strings.Trim(env.Get(key), " \t"))
- if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
- return false
- }
- return true
-}
-
-func (env *Env) SetBool(key string, value bool) {
- if value {
- env.Set(key, "1")
- } else {
- env.Set(key, "0")
- }
-}
-
-func (env *Env) GetTime(key string) (time.Time, error) {
- t, err := time.Parse(time.RFC3339Nano, env.Get(key))
- return t, err
-}
-
-func (env *Env) SetTime(key string, t time.Time) {
- env.Set(key, t.Format(time.RFC3339Nano))
-}
-
-func (env *Env) GetInt(key string) int {
- return int(env.GetInt64(key))
-}
-
-func (env *Env) GetInt64(key string) int64 {
- s := strings.Trim(env.Get(key), " \t")
- val, err := strconv.ParseInt(s, 10, 64)
- if err != nil {
- return 0
- }
- return val
-}
-
-func (env *Env) SetInt(key string, value int) {
- env.Set(key, fmt.Sprintf("%d", value))
-}
-
-func (env *Env) SetInt64(key string, value int64) {
- env.Set(key, fmt.Sprintf("%d", value))
-}
-
-// Returns nil if key not found
-func (env *Env) GetList(key string) []string {
- sval := env.Get(key)
- if sval == "" {
- return nil
- }
- l := make([]string, 0, 1)
- if err := json.Unmarshal([]byte(sval), &l); err != nil {
- l = append(l, sval)
- }
- return l
-}
-
-func (env *Env) GetSubEnv(key string) *Env {
- sval := env.Get(key)
- if sval == "" {
- return nil
- }
- buf := bytes.NewBufferString(sval)
- var sub Env
- if err := sub.Decode(buf); err != nil {
- return nil
- }
- return &sub
-}
-
-func (env *Env) SetSubEnv(key string, sub *Env) error {
- var buf bytes.Buffer
- if err := sub.Encode(&buf); err != nil {
- return err
- }
- env.Set(key, string(buf.Bytes()))
- return nil
-}
-
-func (env *Env) GetJson(key string, iface interface{}) error {
- sval := env.Get(key)
- if sval == "" {
- return nil
- }
- return json.Unmarshal([]byte(sval), iface)
-}
-
-func (env *Env) SetJson(key string, value interface{}) error {
- sval, err := json.Marshal(value)
- if err != nil {
- return err
- }
- env.Set(key, string(sval))
- return nil
-}
-
-func (env *Env) SetList(key string, value []string) error {
- return env.SetJson(key, value)
-}
-
-func (env *Env) Set(key, value string) {
- *env = append(*env, key+"="+value)
-}
-
-func NewDecoder(src io.Reader) *Decoder {
- return &Decoder{
- json.NewDecoder(src),
- }
-}
-
-type Decoder struct {
- *json.Decoder
-}
-
-func (decoder *Decoder) Decode() (*Env, error) {
- m := make(map[string]interface{})
- if err := decoder.Decoder.Decode(&m); err != nil {
- return nil, err
- }
- env := &Env{}
- for key, value := range m {
- env.SetAuto(key, value)
- }
- return env, nil
-}
-
-// DecodeEnv decodes `src` as a json dictionary, and adds
-// each decoded key-value pair to the environment.
-//
-// If `src` cannot be decoded as a json dictionary, an error
-// is returned.
-func (env *Env) Decode(src io.Reader) error {
- m := make(map[string]interface{})
- if err := json.NewDecoder(src).Decode(&m); err != nil {
- return err
- }
- for k, v := range m {
- env.SetAuto(k, v)
- }
- return nil
-}
-
-func (env *Env) SetAuto(k string, v interface{}) {
- // Issue 7941 - if the value in the incoming JSON is null then treat it
- // as if they never specified the property at all.
- if v == nil {
- return
- }
-
- // FIXME: we fix-convert float values to int, because
- // encoding/json decodes integers to float64, but cannot encode them back.
- // (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
- if fval, ok := v.(float64); ok {
- env.SetInt64(k, int64(fval))
- } else if sval, ok := v.(string); ok {
- env.Set(k, sval)
- } else if val, err := json.Marshal(v); err == nil {
- env.Set(k, string(val))
- } else {
- env.Set(k, fmt.Sprintf("%v", v))
- }
-}
-
-func changeFloats(v interface{}) interface{} {
- switch v := v.(type) {
- case float64:
- return int(v)
- case map[string]interface{}:
- for key, val := range v {
- v[key] = changeFloats(val)
- }
- case []interface{}:
- for idx, val := range v {
- v[idx] = changeFloats(val)
- }
- }
- return v
-}
-
-func (env *Env) Encode(dst io.Writer) error {
- m := make(map[string]interface{})
- for k, v := range env.Map() {
- var val interface{}
- if err := json.Unmarshal([]byte(v), &val); err == nil {
- // FIXME: we fix-convert float values to int, because
- // encoding/json decodes integers to float64, but cannot encode them back.
- // (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
- m[k] = changeFloats(val)
- } else {
- m[k] = v
- }
- }
- if err := json.NewEncoder(dst).Encode(&m); err != nil {
- return err
- }
- return nil
-}
-
-func (env *Env) WriteTo(dst io.Writer) (int64, error) {
- wc := utils.NewWriteCounter(dst)
- err := env.Encode(wc)
- return wc.Count, err
-}
-
-func (env *Env) Import(src interface{}) (err error) {
- defer func() {
- if err != nil {
- err = fmt.Errorf("ImportEnv: %s", err)
- }
- }()
- var buf bytes.Buffer
- if err := json.NewEncoder(&buf).Encode(src); err != nil {
- return err
- }
- if err := env.Decode(&buf); err != nil {
- return err
- }
- return nil
-}
-
-func (env *Env) Map() map[string]string {
- m := make(map[string]string)
- for _, kv := range *env {
- parts := strings.SplitN(kv, "=", 2)
- m[parts[0]] = parts[1]
- }
- return m
-}
-
-// MultiMap returns a representation of env as a
-// map of string arrays, keyed by string.
-// This is the same structure as http headers for example,
-// which allow each key to have multiple values.
-func (env *Env) MultiMap() map[string][]string {
- m := make(map[string][]string)
- for _, kv := range *env {
- parts := strings.SplitN(kv, "=", 2)
- m[parts[0]] = append(m[parts[0]], parts[1])
- }
- return m
-}
-
-// InitMultiMap removes all values in env, then initializes
-// new values from the contents of m.
-func (env *Env) InitMultiMap(m map[string][]string) {
- (*env) = make([]string, 0, len(m))
- for k, vals := range m {
- for _, v := range vals {
- env.Set(k, v)
- }
- }
-}
diff --git a/engine/env_test.go b/engine/env_test.go
deleted file mode 100644
index 2ed99d0fe..000000000
--- a/engine/env_test.go
+++ /dev/null
@@ -1,346 +0,0 @@
-package engine
-
-import (
- "bytes"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/docker/docker/pkg/testutils"
-)
-
-func TestEnvLenZero(t *testing.T) {
- env := &Env{}
- if env.Len() != 0 {
- t.Fatalf("%d", env.Len())
- }
-}
-
-func TestEnvLenNotZero(t *testing.T) {
- env := &Env{}
- env.Set("foo", "bar")
- env.Set("ga", "bu")
- if env.Len() != 2 {
- t.Fatalf("%d", env.Len())
- }
-}
-
-func TestEnvLenDup(t *testing.T) {
- env := &Env{
- "foo=bar",
- "foo=baz",
- "a=b",
- }
- // len(env) != env.Len()
- if env.Len() != 2 {
- t.Fatalf("%d", env.Len())
- }
-}
-
-func TestEnvGetDup(t *testing.T) {
- env := &Env{
- "foo=bar",
- "foo=baz",
- "foo=bif",
- }
- expected := "bif"
- if v := env.Get("foo"); v != expected {
- t.Fatalf("expect %q, got %q", expected, v)
- }
-}
-
-func TestNewJob(t *testing.T) {
- job := mkJob(t, "dummy", "--level=awesome")
- if job.Name != "dummy" {
- t.Fatalf("Wrong job name: %s", job.Name)
- }
- if len(job.Args) != 1 {
- t.Fatalf("Wrong number of job arguments: %d", len(job.Args))
- }
- if job.Args[0] != "--level=awesome" {
- t.Fatalf("Wrong job arguments: %s", job.Args[0])
- }
-}
-
-func TestSetenv(t *testing.T) {
- job := mkJob(t, "dummy")
- job.Setenv("foo", "bar")
- if val := job.Getenv("foo"); val != "bar" {
- t.Fatalf("Getenv returns incorrect value: %s", val)
- }
-
- job.Setenv("bar", "")
- if val := job.Getenv("bar"); val != "" {
- t.Fatalf("Getenv returns incorrect value: %s", val)
- }
- if val := job.Getenv("nonexistent"); val != "" {
- t.Fatalf("Getenv returns incorrect value: %s", val)
- }
-}
-
-func TestSetenvBool(t *testing.T) {
- job := mkJob(t, "dummy")
- job.SetenvBool("foo", true)
- if val := job.GetenvBool("foo"); !val {
- t.Fatalf("GetenvBool returns incorrect value: %t", val)
- }
-
- job.SetenvBool("bar", false)
- if val := job.GetenvBool("bar"); val {
- t.Fatalf("GetenvBool returns incorrect value: %t", val)
- }
-
- if val := job.GetenvBool("nonexistent"); val {
- t.Fatalf("GetenvBool returns incorrect value: %t", val)
- }
-}
-
-func TestSetenvTime(t *testing.T) {
- job := mkJob(t, "dummy")
-
- now := time.Now()
- job.SetenvTime("foo", now)
- if val, err := job.GetenvTime("foo"); err != nil {
- t.Fatalf("GetenvTime failed to parse: %v", err)
- } else {
- nowStr := now.Format(time.RFC3339)
- valStr := val.Format(time.RFC3339)
- if nowStr != valStr {
- t.Fatalf("GetenvTime returns incorrect value: %s, Expected: %s", valStr, nowStr)
- }
- }
-
- job.Setenv("bar", "Obviously I'm not a date")
- if val, err := job.GetenvTime("bar"); err == nil {
- t.Fatalf("GetenvTime was supposed to fail, instead returned: %s", val)
- }
-}
-
-func TestSetenvInt(t *testing.T) {
- job := mkJob(t, "dummy")
-
- job.SetenvInt("foo", -42)
- if val := job.GetenvInt("foo"); val != -42 {
- t.Fatalf("GetenvInt returns incorrect value: %d", val)
- }
-
- job.SetenvInt("bar", 42)
- if val := job.GetenvInt("bar"); val != 42 {
- t.Fatalf("GetenvInt returns incorrect value: %d", val)
- }
- if val := job.GetenvInt("nonexistent"); val != 0 {
- t.Fatalf("GetenvInt returns incorrect value: %d", val)
- }
-}
-
-func TestSetenvList(t *testing.T) {
- job := mkJob(t, "dummy")
-
- job.SetenvList("foo", []string{"bar"})
- if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
- t.Fatalf("GetenvList returns incorrect value: %v", val)
- }
-
- job.SetenvList("bar", nil)
- if val := job.GetenvList("bar"); val != nil {
- t.Fatalf("GetenvList returns incorrect value: %v", val)
- }
- if val := job.GetenvList("nonexistent"); val != nil {
- t.Fatalf("GetenvList returns incorrect value: %v", val)
- }
-}
-
-func TestEnviron(t *testing.T) {
- job := mkJob(t, "dummy")
- job.Setenv("foo", "bar")
- val, exists := job.Environ()["foo"]
- if !exists {
- t.Fatalf("foo not found in the environ")
- }
- if val != "bar" {
- t.Fatalf("bar not found in the environ")
- }
-}
-
-func TestMultiMap(t *testing.T) {
- e := &Env{}
- e.Set("foo", "bar")
- e.Set("bar", "baz")
- e.Set("hello", "world")
- m := e.MultiMap()
- e2 := &Env{}
- e2.Set("old_key", "something something something")
- e2.InitMultiMap(m)
- if v := e2.Get("old_key"); v != "" {
- t.Fatalf("%#v", v)
- }
- if v := e2.Get("bar"); v != "baz" {
- t.Fatalf("%#v", v)
- }
- if v := e2.Get("hello"); v != "world" {
- t.Fatalf("%#v", v)
- }
-}
-
-func testMap(l int) [][2]string {
- res := make([][2]string, l)
- for i := 0; i < l; i++ {
- t := [2]string{testutils.RandomString(5), testutils.RandomString(20)}
- res[i] = t
- }
- return res
-}
-
-func BenchmarkSet(b *testing.B) {
- fix := testMap(100)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- env := &Env{}
- for _, kv := range fix {
- env.Set(kv[0], kv[1])
- }
- }
-}
-
-func BenchmarkSetJson(b *testing.B) {
- fix := testMap(100)
- type X struct {
- f string
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- env := &Env{}
- for _, kv := range fix {
- if err := env.SetJson(kv[0], X{kv[1]}); err != nil {
- b.Fatal(err)
- }
- }
- }
-}
-
-func BenchmarkGet(b *testing.B) {
- fix := testMap(100)
- env := &Env{}
- for _, kv := range fix {
- env.Set(kv[0], kv[1])
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- for _, kv := range fix {
- env.Get(kv[0])
- }
- }
-}
-
-func BenchmarkGetJson(b *testing.B) {
- fix := testMap(100)
- env := &Env{}
- type X struct {
- f string
- }
- for _, kv := range fix {
- env.SetJson(kv[0], X{kv[1]})
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- for _, kv := range fix {
- if err := env.GetJson(kv[0], &X{}); err != nil {
- b.Fatal(err)
- }
- }
- }
-}
-
-func BenchmarkEncode(b *testing.B) {
- fix := testMap(100)
- env := &Env{}
- type X struct {
- f string
- }
- // half a json
- for i, kv := range fix {
- if i%2 != 0 {
- if err := env.SetJson(kv[0], X{kv[1]}); err != nil {
- b.Fatal(err)
- }
- continue
- }
- env.Set(kv[0], kv[1])
- }
- var writer bytes.Buffer
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- env.Encode(&writer)
- writer.Reset()
- }
-}
-
-func BenchmarkDecode(b *testing.B) {
- fix := testMap(100)
- env := &Env{}
- type X struct {
- f string
- }
- // half a json
- for i, kv := range fix {
- if i%2 != 0 {
- if err := env.SetJson(kv[0], X{kv[1]}); err != nil {
- b.Fatal(err)
- }
- continue
- }
- env.Set(kv[0], kv[1])
- }
- var writer bytes.Buffer
- env.Encode(&writer)
- denv := &Env{}
- reader := bytes.NewReader(writer.Bytes())
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- err := denv.Decode(reader)
- if err != nil {
- b.Fatal(err)
- }
- reader.Seek(0, 0)
- }
-}
-
-func TestLongNumbers(t *testing.T) {
- type T struct {
- TestNum int64
- }
- v := T{67108864}
- var buf bytes.Buffer
- e := &Env{}
- e.SetJson("Test", v)
- if err := e.Encode(&buf); err != nil {
- t.Fatal(err)
- }
- res := make(map[string]T)
- if err := json.Unmarshal(buf.Bytes(), &res); err != nil {
- t.Fatal(err)
- }
- if res["Test"].TestNum != v.TestNum {
- t.Fatalf("TestNum %d, expected %d", res["Test"].TestNum, v.TestNum)
- }
-}
-
-func TestLongNumbersArray(t *testing.T) {
- type T struct {
- TestNum []int64
- }
- v := T{[]int64{67108864}}
- var buf bytes.Buffer
- e := &Env{}
- e.SetJson("Test", v)
- if err := e.Encode(&buf); err != nil {
- t.Fatal(err)
- }
- res := make(map[string]T)
- if err := json.Unmarshal(buf.Bytes(), &res); err != nil {
- t.Fatal(err)
- }
- if res["Test"].TestNum[0] != v.TestNum[0] {
- t.Fatalf("TestNum %d, expected %d", res["Test"].TestNum, v.TestNum)
- }
-}
diff --git a/engine/hack.go b/engine/hack.go
deleted file mode 100644
index be4fadbe6..000000000
--- a/engine/hack.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package engine
-
-type Hack map[string]interface{}
-
-func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
- if eng.hack == nil {
- return nil
- }
- val, exists := eng.hack[key]
- if !exists {
- return nil
- }
- return val
-}
-
-func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
- if eng.hack == nil {
- eng.hack = make(Hack)
- }
- eng.hack[key] = val
-}
diff --git a/engine/helpers_test.go b/engine/helpers_test.go
deleted file mode 100644
index cfa11da7c..000000000
--- a/engine/helpers_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package engine
-
-import (
- "testing"
-)
-
-var globalTestID string
-
-func mkJob(t *testing.T, name string, args ...string) *Job {
- return New().Job(name, args...)
-}
diff --git a/engine/http.go b/engine/http.go
deleted file mode 100644
index 7e4dcd7bb..000000000
--- a/engine/http.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package engine
-
-import (
- "net/http"
- "path"
-)
-
-// ServeHTTP executes a job as specified by the http request `r`, and sends the
-// result as an http response.
-// This method allows an Engine instance to be passed as a standard http.Handler interface.
-//
-// Note that the protocol used in this method is a convenience wrapper and is not the canonical
-// implementation of remote job execution. This is because HTTP/1 does not handle stream multiplexing,
-// and so cannot differentiate stdout from stderr. Additionally, headers cannot be added to a response
-// once data has been written to the body, which makes it inconvenient to return metadata such
-// as the exit status.
-//
-func (eng *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- var (
- jobName = path.Base(r.URL.Path)
- jobArgs, exists = r.URL.Query()["a"]
- )
- if !exists {
- jobArgs = []string{}
- }
- w.Header().Set("Job-Name", jobName)
- for _, arg := range jobArgs {
- w.Header().Add("Job-Args", arg)
- }
- job := eng.Job(jobName, jobArgs...)
- job.Stdout.Add(w)
- job.Stderr.Add(w)
- // FIXME: distinguish job status from engine error in Run()
- // The former should be passed as a special header, the former
- // should cause a 500 status
- w.WriteHeader(http.StatusOK)
- // The exit status cannot be sent reliably with HTTP1, because headers
- // can only be sent before the body.
- // (we could possibly use http footers via chunked encoding, but I couldn't find
- // how to use them in net/http)
- job.Run()
-}
diff --git a/engine/job.go b/engine/job.go
deleted file mode 100644
index ecb68c3eb..000000000
--- a/engine/job.go
+++ /dev/null
@@ -1,269 +0,0 @@
-package engine
-
-import (
- "bytes"
- "fmt"
- "io"
- "strings"
- "sync"
- "time"
-
- log "github.com/Sirupsen/logrus"
-)
-
-// A job is the fundamental unit of work in the docker engine.
-// Everything docker can do should eventually be exposed as a job.
-// For example: execute a process in a container, create a new container,
-// download an archive from the internet, serve the http api, etc.
-//
-// The job API is designed after unix processes: a job has a name, arguments,
-// environment variables, standard streams for input, output and error, and
-// an exit status which can indicate success (0) or error (anything else).
-//
-// For status, 0 indicates success, and any other integers indicates an error.
-// This allows for richer error reporting.
-//
-type Job struct {
- Eng *Engine
- Name string
- Args []string
- env *Env
- Stdout *Output
- Stderr *Output
- Stdin *Input
- handler Handler
- status Status
- end time.Time
- closeIO bool
-
- // When closed, the job has been cancelled.
- // Note: not all jobs implement cancellation.
- // See Job.Cancel() and Job.WaitCancelled()
- cancelled chan struct{}
- cancelOnce sync.Once
-}
-
-type Status int
-
-const (
- StatusOK Status = 0
- StatusErr Status = 1
- StatusNotFound Status = 127
-)
-
-// Run executes the job and blocks until the job completes.
-// If the job returns a failure status, an error is returned
-// which includes the status.
-func (job *Job) Run() error {
- if job.Eng.IsShutdown() && !job.GetenvBool("overrideShutdown") {
- return fmt.Errorf("engine is shutdown")
- }
- // FIXME: this is a temporary workaround to avoid Engine.Shutdown
- // waiting 5 seconds for server/api.ServeApi to complete (which it never will)
- // everytime the daemon is cleanly restarted.
- // The permanent fix is to implement Job.Stop and Job.OnStop so that
- // ServeApi can cooperate and terminate cleanly.
- if job.Name != "serveapi" {
- job.Eng.l.Lock()
- job.Eng.tasks.Add(1)
- job.Eng.l.Unlock()
- defer job.Eng.tasks.Done()
- }
- // FIXME: make this thread-safe
- // FIXME: implement wait
- if !job.end.IsZero() {
- return fmt.Errorf("%s: job has already completed", job.Name)
- }
- // Log beginning and end of the job
- if job.Eng.Logging {
- log.Infof("+job %s", job.CallString())
- defer func() {
- log.Infof("-job %s%s", job.CallString(), job.StatusString())
- }()
- }
- var errorMessage = bytes.NewBuffer(nil)
- job.Stderr.Add(errorMessage)
- if job.handler == nil {
- job.Errorf("%s: command not found", job.Name)
- job.status = 127
- } else {
- job.status = job.handler(job)
- job.end = time.Now()
- }
- if job.closeIO {
- // Wait for all background tasks to complete
- if err := job.Stdout.Close(); err != nil {
- return err
- }
- if err := job.Stderr.Close(); err != nil {
- return err
- }
- if err := job.Stdin.Close(); err != nil {
- return err
- }
- }
- if job.status != 0 {
- return fmt.Errorf("%s", Tail(errorMessage, 1))
- }
-
- return nil
-}
-
-func (job *Job) CallString() string {
- return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
-}
-
-func (job *Job) StatusString() string {
- // If the job hasn't completed, status string is empty
- if job.end.IsZero() {
- return ""
- }
- var okerr string
- if job.status == StatusOK {
- okerr = "OK"
- } else {
- okerr = "ERR"
- }
- return fmt.Sprintf(" = %s (%d)", okerr, job.status)
-}
-
-// String returns a human-readable description of `job`
-func (job *Job) String() string {
- return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
-}
-
-func (job *Job) Env() *Env {
- return job.env
-}
-
-func (job *Job) EnvExists(key string) (value bool) {
- return job.env.Exists(key)
-}
-
-func (job *Job) Getenv(key string) (value string) {
- return job.env.Get(key)
-}
-
-func (job *Job) GetenvBool(key string) (value bool) {
- return job.env.GetBool(key)
-}
-
-func (job *Job) SetenvBool(key string, value bool) {
- job.env.SetBool(key, value)
-}
-
-func (job *Job) GetenvTime(key string) (value time.Time, err error) {
- return job.env.GetTime(key)
-}
-
-func (job *Job) SetenvTime(key string, value time.Time) {
- job.env.SetTime(key, value)
-}
-
-func (job *Job) GetenvSubEnv(key string) *Env {
- return job.env.GetSubEnv(key)
-}
-
-func (job *Job) SetenvSubEnv(key string, value *Env) error {
- return job.env.SetSubEnv(key, value)
-}
-
-func (job *Job) GetenvInt64(key string) int64 {
- return job.env.GetInt64(key)
-}
-
-func (job *Job) GetenvInt(key string) int {
- return job.env.GetInt(key)
-}
-
-func (job *Job) SetenvInt64(key string, value int64) {
- job.env.SetInt64(key, value)
-}
-
-func (job *Job) SetenvInt(key string, value int) {
- job.env.SetInt(key, value)
-}
-
-// Returns nil if key not found
-func (job *Job) GetenvList(key string) []string {
- return job.env.GetList(key)
-}
-
-func (job *Job) GetenvJson(key string, iface interface{}) error {
- return job.env.GetJson(key, iface)
-}
-
-func (job *Job) SetenvJson(key string, value interface{}) error {
- return job.env.SetJson(key, value)
-}
-
-func (job *Job) SetenvList(key string, value []string) error {
- return job.env.SetJson(key, value)
-}
-
-func (job *Job) Setenv(key, value string) {
- job.env.Set(key, value)
-}
-
-// DecodeEnv decodes `src` as a json dictionary, and adds
-// each decoded key-value pair to the environment.
-//
-// If `src` cannot be decoded as a json dictionary, an error
-// is returned.
-func (job *Job) DecodeEnv(src io.Reader) error {
- return job.env.Decode(src)
-}
-
-func (job *Job) EncodeEnv(dst io.Writer) error {
- return job.env.Encode(dst)
-}
-
-func (job *Job) ImportEnv(src interface{}) (err error) {
- return job.env.Import(src)
-}
-
-func (job *Job) Environ() map[string]string {
- return job.env.Map()
-}
-
-func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
- prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
- return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
-}
-
-func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
- return fmt.Fprintf(job.Stdout, format, args...)
-}
-
-func (job *Job) Errorf(format string, args ...interface{}) Status {
- if format[len(format)-1] != '\n' {
- format = format + "\n"
- }
- fmt.Fprintf(job.Stderr, format, args...)
- return StatusErr
-}
-
-func (job *Job) Error(err error) Status {
- fmt.Fprintf(job.Stderr, "%s\n", err)
- return StatusErr
-}
-
-func (job *Job) StatusCode() int {
- return int(job.status)
-}
-
-func (job *Job) SetCloseIO(val bool) {
- job.closeIO = val
-}
-
-// When called, causes the Job.WaitCancelled channel to unblock.
-func (job *Job) Cancel() {
- job.cancelOnce.Do(func() {
- close(job.cancelled)
- })
-}
-
-// Returns a channel which is closed ("never blocks") when the job is cancelled.
-func (job *Job) WaitCancelled() <-chan struct{} {
- return job.cancelled
-}
diff --git a/engine/job_test.go b/engine/job_test.go
deleted file mode 100644
index 9f8c76095..000000000
--- a/engine/job_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package engine
-
-import (
- "bytes"
- "fmt"
- "testing"
-)
-
-func TestJobStatusOK(t *testing.T) {
- eng := New()
- eng.Register("return_ok", func(job *Job) Status { return StatusOK })
- err := eng.Job("return_ok").Run()
- if err != nil {
- t.Fatalf("Expected: err=%v\nReceived: err=%v", nil, err)
- }
-}
-
-func TestJobStatusErr(t *testing.T) {
- eng := New()
- eng.Register("return_err", func(job *Job) Status { return StatusErr })
- err := eng.Job("return_err").Run()
- if err == nil {
- t.Fatalf("When a job returns StatusErr, Run() should return an error")
- }
-}
-
-func TestJobStatusNotFound(t *testing.T) {
- eng := New()
- eng.Register("return_not_found", func(job *Job) Status { return StatusNotFound })
- err := eng.Job("return_not_found").Run()
- if err == nil {
- t.Fatalf("When a job returns StatusNotFound, Run() should return an error")
- }
-}
-
-func TestJobStdoutString(t *testing.T) {
- eng := New()
- // FIXME: test multiple combinations of output and status
- eng.Register("say_something_in_stdout", func(job *Job) Status {
- job.Printf("Hello world\n")
- return StatusOK
- })
-
- job := eng.Job("say_something_in_stdout")
- var outputBuffer = bytes.NewBuffer(nil)
- job.Stdout.Add(outputBuffer)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- fmt.Println(outputBuffer)
- var output = Tail(outputBuffer, 1)
- if expectedOutput := "Hello world"; output != expectedOutput {
- t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
- }
-}
-
-func TestJobStderrString(t *testing.T) {
- eng := New()
- // FIXME: test multiple combinations of output and status
- eng.Register("say_something_in_stderr", func(job *Job) Status {
- job.Errorf("Something might happen\nHere it comes!\nOh no...\nSomething happened\n")
- return StatusOK
- })
-
- job := eng.Job("say_something_in_stderr")
- var outputBuffer = bytes.NewBuffer(nil)
- job.Stderr.Add(outputBuffer)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- var output = Tail(outputBuffer, 1)
- if expectedOutput := "Something happened"; output != expectedOutput {
- t.Fatalf("Stderr last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
- }
-}
diff --git a/engine/shutdown_test.go b/engine/shutdown_test.go
deleted file mode 100644
index 13d804926..000000000
--- a/engine/shutdown_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package engine
-
-import (
- "testing"
- "time"
-)
-
-func TestShutdownEmpty(t *testing.T) {
- eng := New()
- if eng.IsShutdown() {
- t.Fatalf("IsShutdown should be false")
- }
- eng.Shutdown()
- if !eng.IsShutdown() {
- t.Fatalf("IsShutdown should be true")
- }
-}
-
-func TestShutdownAfterRun(t *testing.T) {
- eng := New()
- var called bool
- eng.Register("foo", func(job *Job) Status {
- called = true
- return StatusOK
- })
- if err := eng.Job("foo").Run(); err != nil {
- t.Fatal(err)
- }
- eng.Shutdown()
- if err := eng.Job("foo").Run(); err == nil {
- t.Fatalf("%#v", *eng)
- }
-}
-
-// An approximate and racy, but better-than-nothing test that
-//
-func TestShutdownDuringRun(t *testing.T) {
- var (
- jobDelay time.Duration = 500 * time.Millisecond
- jobDelayLow time.Duration = 100 * time.Millisecond
- jobDelayHigh time.Duration = 700 * time.Millisecond
- )
- eng := New()
- var completed bool
- eng.Register("foo", func(job *Job) Status {
- time.Sleep(jobDelay)
- completed = true
- return StatusOK
- })
- go eng.Job("foo").Run()
- time.Sleep(50 * time.Millisecond)
- done := make(chan struct{})
- var startShutdown time.Time
- go func() {
- startShutdown = time.Now()
- eng.Shutdown()
- close(done)
- }()
- time.Sleep(50 * time.Millisecond)
- if err := eng.Job("foo").Run(); err == nil {
- t.Fatalf("run on shutdown should fail: %#v", *eng)
- }
- <-done
- // Verify that Shutdown() blocks for roughly 500ms, instead
- // of returning almost instantly.
- //
- // We use >100ms to leave ample margin for race conditions between
- // goroutines. It's possible (but unlikely in reasonable testing
- // conditions), that this test will cause a false positive or false
- // negative. But it's probably better than not having any test
- // for the 99.999% of time where testing conditions are reasonable.
- if d := time.Since(startShutdown); d.Nanoseconds() < jobDelayLow.Nanoseconds() {
- t.Fatalf("shutdown did not block long enough: %v", d)
- } else if d.Nanoseconds() > jobDelayHigh.Nanoseconds() {
- t.Fatalf("shutdown blocked too long: %v", d)
- }
- if !completed {
- t.Fatalf("job did not complete")
- }
-}
diff --git a/engine/streams.go b/engine/streams.go
deleted file mode 100644
index 216fb8980..000000000
--- a/engine/streams.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package engine
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "strings"
- "sync"
- "unicode"
-)
-
-type Output struct {
- sync.Mutex
- dests []io.Writer
- tasks sync.WaitGroup
- used bool
-}
-
-// Tail returns the n last lines of a buffer
-// stripped out of trailing white spaces, if any.
-//
-// if n <= 0, returns an empty string
-func Tail(buffer *bytes.Buffer, n int) string {
- if n <= 0 {
- return ""
- }
- s := strings.TrimRightFunc(buffer.String(), unicode.IsSpace)
- i := len(s) - 1
- for ; i >= 0 && n > 0; i-- {
- if s[i] == '\n' {
- n--
- if n == 0 {
- break
- }
- }
- }
- // when i == -1, return the whole string which is s[0:]
- return s[i+1:]
-}
-
-// NewOutput returns a new Output object with no destinations attached.
-// Writing to an empty Output will cause the written data to be discarded.
-func NewOutput() *Output {
- return &Output{}
-}
-
-// Return true if something was written on this output
-func (o *Output) Used() bool {
- o.Lock()
- defer o.Unlock()
- return o.used
-}
-
-// Add attaches a new destination to the Output. Any data subsequently written
-// to the output will be written to the new destination in addition to all the others.
-// This method is thread-safe.
-func (o *Output) Add(dst io.Writer) {
- o.Lock()
- defer o.Unlock()
- o.dests = append(o.dests, dst)
-}
-
-// Set closes and remove existing destination and then attaches a new destination to
-// the Output. Any data subsequently written to the output will be written to the new
-// destination in addition to all the others. This method is thread-safe.
-func (o *Output) Set(dst io.Writer) {
- o.Close()
- o.Lock()
- defer o.Unlock()
- o.dests = []io.Writer{dst}
-}
-
-// AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination,
-// and returns its reading end for consumption by the caller.
-// This is a rough equivalent similar to Cmd.StdoutPipe() in the standard os/exec package.
-// This method is thread-safe.
-func (o *Output) AddPipe() (io.Reader, error) {
- r, w := io.Pipe()
- o.Add(w)
- return r, nil
-}
-
-// Write writes the same data to all registered destinations.
-// This method is thread-safe.
-func (o *Output) Write(p []byte) (n int, err error) {
- o.Lock()
- defer o.Unlock()
- o.used = true
- var firstErr error
- for _, dst := range o.dests {
- _, err := dst.Write(p)
- if err != nil && firstErr == nil {
- firstErr = err
- }
- }
- return len(p), firstErr
-}
-
-// Close unregisters all destinations and waits for all background
-// AddTail and AddString tasks to complete.
-// The Close method of each destination is called if it exists.
-func (o *Output) Close() error {
- o.Lock()
- defer o.Unlock()
- var firstErr error
- for _, dst := range o.dests {
- if closer, ok := dst.(io.Closer); ok {
- err := closer.Close()
- if err != nil && firstErr == nil {
- firstErr = err
- }
- }
- }
- o.tasks.Wait()
- o.dests = nil
- return firstErr
-}
-
-type Input struct {
- src io.Reader
- sync.Mutex
-}
-
-// NewInput returns a new Input object with no source attached.
-// Reading to an empty Input will return io.EOF.
-func NewInput() *Input {
- return &Input{}
-}
-
-// Read reads from the input in a thread-safe way.
-func (i *Input) Read(p []byte) (n int, err error) {
- i.Mutex.Lock()
- defer i.Mutex.Unlock()
- if i.src == nil {
- return 0, io.EOF
- }
- return i.src.Read(p)
-}
-
-// Closes the src
-// Not thread safe on purpose
-func (i *Input) Close() error {
- if i.src != nil {
- if closer, ok := i.src.(io.Closer); ok {
- return closer.Close()
- }
- }
- return nil
-}
-
-// Add attaches a new source to the input.
-// Add can only be called once per input. Subsequent calls will
-// return an error.
-func (i *Input) Add(src io.Reader) error {
- i.Mutex.Lock()
- defer i.Mutex.Unlock()
- if i.src != nil {
- return fmt.Errorf("Maximum number of sources reached: 1")
- }
- i.src = src
- return nil
-}
-
-// AddEnv starts a new goroutine which will decode all subsequent data
-// as a stream of json-encoded objects, and point `dst` to the last
-// decoded object.
-// The result `env` can be queried using the type-neutral Env interface.
-// It is not safe to query `env` until the Output is closed.
-func (o *Output) AddEnv() (dst *Env, err error) {
- src, err := o.AddPipe()
- if err != nil {
- return nil, err
- }
- dst = &Env{}
- o.tasks.Add(1)
- go func() {
- defer o.tasks.Done()
- decoder := NewDecoder(src)
- for {
- env, err := decoder.Decode()
- if err != nil {
- return
- }
- *dst = *env
- }
- }()
- return dst, nil
-}
-
-func (o *Output) AddListTable() (dst *Table, err error) {
- src, err := o.AddPipe()
- if err != nil {
- return nil, err
- }
- dst = NewTable("", 0)
- o.tasks.Add(1)
- go func() {
- defer o.tasks.Done()
- content, err := ioutil.ReadAll(src)
- if err != nil {
- return
- }
- if _, err := dst.ReadListFrom(content); err != nil {
- return
- }
- }()
- return dst, nil
-}
-
-func (o *Output) AddTable() (dst *Table, err error) {
- src, err := o.AddPipe()
- if err != nil {
- return nil, err
- }
- dst = NewTable("", 0)
- o.tasks.Add(1)
- go func() {
- defer o.tasks.Done()
- if _, err := dst.ReadFrom(src); err != nil {
- return
- }
- }()
- return dst, nil
-}
diff --git a/engine/streams_test.go b/engine/streams_test.go
deleted file mode 100644
index 476a721ba..000000000
--- a/engine/streams_test.go
+++ /dev/null
@@ -1,215 +0,0 @@
-package engine
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "strings"
- "testing"
-)
-
-type sentinelWriteCloser struct {
- calledWrite bool
- calledClose bool
-}
-
-func (w *sentinelWriteCloser) Write(p []byte) (int, error) {
- w.calledWrite = true
- return len(p), nil
-}
-
-func (w *sentinelWriteCloser) Close() error {
- w.calledClose = true
- return nil
-}
-
-func TestOutputAddEnv(t *testing.T) {
- input := "{\"foo\": \"bar\", \"answer_to_life_the_universe_and_everything\": 42}"
- o := NewOutput()
- result, err := o.AddEnv()
- if err != nil {
- t.Fatal(err)
- }
- o.Write([]byte(input))
- o.Close()
- if v := result.Get("foo"); v != "bar" {
- t.Errorf("Expected %v, got %v", "bar", v)
- }
- if v := result.GetInt("answer_to_life_the_universe_and_everything"); v != 42 {
- t.Errorf("Expected %v, got %v", 42, v)
- }
- if v := result.Get("this-value-doesnt-exist"); v != "" {
- t.Errorf("Expected %v, got %v", "", v)
- }
-}
-
-func TestOutputAddClose(t *testing.T) {
- o := NewOutput()
- var s sentinelWriteCloser
- o.Add(&s)
- if err := o.Close(); err != nil {
- t.Fatal(err)
- }
- // Write data after the output is closed.
- // Write should succeed, but no destination should receive it.
- if _, err := o.Write([]byte("foo bar")); err != nil {
- t.Fatal(err)
- }
- if !s.calledClose {
- t.Fatal("Output.Close() didn't close the destination")
- }
-}
-
-func TestOutputAddPipe(t *testing.T) {
- var testInputs = []string{
- "hello, world!",
- "One\nTwo\nThree",
- "",
- "A line\nThen another nl-terminated line\n",
- "A line followed by an empty line\n\n",
- }
- for _, input := range testInputs {
- expectedOutput := input
- o := NewOutput()
- r, err := o.AddPipe()
- if err != nil {
- t.Fatal(err)
- }
- go func(o *Output) {
- if n, err := o.Write([]byte(input)); err != nil {
- t.Error(err)
- } else if n != len(input) {
- t.Errorf("Expected %d, got %d", len(input), n)
- }
- if err := o.Close(); err != nil {
- t.Error(err)
- }
- }(o)
- output, err := ioutil.ReadAll(r)
- if err != nil {
- t.Fatal(err)
- }
- if string(output) != expectedOutput {
- t.Errorf("Last line is not stored as return string.\nExpected: '%s'\nGot: '%s'", expectedOutput, output)
- }
- }
-}
-
-func TestTail(t *testing.T) {
- var tests = make(map[string][]string)
- tests["hello, world!"] = []string{
- "",
- "hello, world!",
- "hello, world!",
- "hello, world!",
- }
- tests["One\nTwo\nThree"] = []string{
- "",
- "Three",
- "Two\nThree",
- "One\nTwo\nThree",
- }
- tests["One\nTwo\n\n\n"] = []string{
- "",
- "Two",
- "One\nTwo",
- }
- for input, outputs := range tests {
- for n, expectedOutput := range outputs {
- output := Tail(bytes.NewBufferString(input), n)
- if output != expectedOutput {
- t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot : '%s'", n, expectedOutput, output)
- }
- }
- }
-}
-
-func lastLine(txt string) string {
- scanner := bufio.NewScanner(strings.NewReader(txt))
- var lastLine string
- for scanner.Scan() {
- lastLine = scanner.Text()
- }
- return lastLine
-}
-
-func TestOutputAdd(t *testing.T) {
- o := NewOutput()
- b := &bytes.Buffer{}
- o.Add(b)
- input := "hello, world!"
- if n, err := o.Write([]byte(input)); err != nil {
- t.Fatal(err)
- } else if n != len(input) {
- t.Fatalf("Expected %d, got %d", len(input), n)
- }
- if output := b.String(); output != input {
- t.Fatalf("Received wrong data from Add.\nExpected: '%s'\nGot: '%s'", input, output)
- }
-}
-
-func TestOutputWriteError(t *testing.T) {
- o := NewOutput()
- buf := &bytes.Buffer{}
- o.Add(buf)
- r, w := io.Pipe()
- input := "Hello there"
- expectedErr := fmt.Errorf("This is an error")
- r.CloseWithError(expectedErr)
- o.Add(w)
- n, err := o.Write([]byte(input))
- if err != expectedErr {
- t.Fatalf("Output.Write() should return the first error encountered, if any")
- }
- if buf.String() != input {
- t.Fatalf("Output.Write() should attempt write on all destinations, even after encountering an error")
- }
- if n != len(input) {
- t.Fatalf("Output.Write() should return the size of the input if it successfully writes to at least one destination")
- }
-}
-
-func TestInputAddEmpty(t *testing.T) {
- i := NewInput()
- var b bytes.Buffer
- if err := i.Add(&b); err != nil {
- t.Fatal(err)
- }
- data, err := ioutil.ReadAll(i)
- if err != nil {
- t.Fatal(err)
- }
- if len(data) > 0 {
- t.Fatalf("Read from empty input shoul yield no data")
- }
-}
-
-func TestInputAddTwo(t *testing.T) {
- i := NewInput()
- var b1 bytes.Buffer
- // First add should succeed
- if err := i.Add(&b1); err != nil {
- t.Fatal(err)
- }
- var b2 bytes.Buffer
- // Second add should fail
- if err := i.Add(&b2); err == nil {
- t.Fatalf("Adding a second source should return an error")
- }
-}
-
-func TestInputAddNotEmpty(t *testing.T) {
- i := NewInput()
- b := bytes.NewBufferString("hello world\nabc")
- expectedResult := b.String()
- i.Add(b)
- result, err := ioutil.ReadAll(i)
- if err != nil {
- t.Fatal(err)
- }
- if string(result) != expectedResult {
- t.Fatalf("Expected: %v\nReceived: %v", expectedResult, result)
- }
-}
diff --git a/engine/table.go b/engine/table.go
deleted file mode 100644
index 4498bdf1e..000000000
--- a/engine/table.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package engine
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "sort"
- "strconv"
-)
-
-type Table struct {
- Data []*Env
- sortKey string
- Chan chan *Env
-}
-
-func NewTable(sortKey string, sizeHint int) *Table {
- return &Table{
- make([]*Env, 0, sizeHint),
- sortKey,
- make(chan *Env),
- }
-}
-
-func (t *Table) SetKey(sortKey string) {
- t.sortKey = sortKey
-}
-
-func (t *Table) Add(env *Env) {
- t.Data = append(t.Data, env)
-}
-
-func (t *Table) Len() int {
- return len(t.Data)
-}
-
-func (t *Table) Less(a, b int) bool {
- return t.lessBy(a, b, t.sortKey)
-}
-
-func (t *Table) lessBy(a, b int, by string) bool {
- keyA := t.Data[a].Get(by)
- keyB := t.Data[b].Get(by)
- intA, errA := strconv.ParseInt(keyA, 10, 64)
- intB, errB := strconv.ParseInt(keyB, 10, 64)
- if errA == nil && errB == nil {
- return intA < intB
- }
- return keyA < keyB
-}
-
-func (t *Table) Swap(a, b int) {
- tmp := t.Data[a]
- t.Data[a] = t.Data[b]
- t.Data[b] = tmp
-}
-
-func (t *Table) Sort() {
- sort.Sort(t)
-}
-
-func (t *Table) ReverseSort() {
- sort.Sort(sort.Reverse(t))
-}
-
-func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) {
- if _, err := dst.Write([]byte{'['}); err != nil {
- return -1, err
- }
- n = 1
- for i, env := range t.Data {
- bytes, err := env.WriteTo(dst)
- if err != nil {
- return -1, err
- }
- n += bytes
- if i != len(t.Data)-1 {
- if _, err := dst.Write([]byte{','}); err != nil {
- return -1, err
- }
- n++
- }
- }
- if _, err := dst.Write([]byte{']'}); err != nil {
- return -1, err
- }
- return n + 1, nil
-}
-
-func (t *Table) ToListString() (string, error) {
- buffer := bytes.NewBuffer(nil)
- if _, err := t.WriteListTo(buffer); err != nil {
- return "", err
- }
- return buffer.String(), nil
-}
-
-func (t *Table) WriteTo(dst io.Writer) (n int64, err error) {
- for _, env := range t.Data {
- bytes, err := env.WriteTo(dst)
- if err != nil {
- return -1, err
- }
- n += bytes
- }
- return n, nil
-}
-
-func (t *Table) ReadListFrom(src []byte) (n int64, err error) {
- var array []interface{}
-
- if err := json.Unmarshal(src, &array); err != nil {
- return -1, err
- }
-
- for _, item := range array {
- if m, ok := item.(map[string]interface{}); ok {
- env := &Env{}
- for key, value := range m {
- env.SetAuto(key, value)
- }
- t.Add(env)
- }
- }
-
- return int64(len(src)), nil
-}
-
-func (t *Table) ReadFrom(src io.Reader) (n int64, err error) {
- decoder := NewDecoder(src)
- for {
- env, err := decoder.Decode()
- if err == io.EOF {
- return 0, nil
- } else if err != nil {
- return -1, err
- }
- t.Add(env)
- }
-}
diff --git a/engine/table_test.go b/engine/table_test.go
deleted file mode 100644
index 9a32ac9cd..000000000
--- a/engine/table_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package engine
-
-import (
- "bytes"
- "encoding/json"
- "testing"
-)
-
-func TestTableWriteTo(t *testing.T) {
- table := NewTable("", 0)
- e := &Env{}
- e.Set("foo", "bar")
- table.Add(e)
- var buf bytes.Buffer
- if _, err := table.WriteTo(&buf); err != nil {
- t.Fatal(err)
- }
- output := make(map[string]string)
- if err := json.Unmarshal(buf.Bytes(), &output); err != nil {
- t.Fatal(err)
- }
- if len(output) != 1 {
- t.Fatalf("Incorrect output: %v", output)
- }
- if val, exists := output["foo"]; !exists || val != "bar" {
- t.Fatalf("Inccorect output: %v", output)
- }
-}
-
-func TestTableSortStringValue(t *testing.T) {
- table := NewTable("Key", 0)
-
- e := &Env{}
- e.Set("Key", "A")
- table.Add(e)
-
- e = &Env{}
- e.Set("Key", "D")
- table.Add(e)
-
- e = &Env{}
- e.Set("Key", "B")
- table.Add(e)
-
- e = &Env{}
- e.Set("Key", "C")
- table.Add(e)
-
- table.Sort()
-
- if len := table.Len(); len != 4 {
- t.Fatalf("Expected 4, got %d", len)
- }
-
- if value := table.Data[0].Get("Key"); value != "A" {
- t.Fatalf("Expected A, got %s", value)
- }
-
- if value := table.Data[1].Get("Key"); value != "B" {
- t.Fatalf("Expected B, got %s", value)
- }
-
- if value := table.Data[2].Get("Key"); value != "C" {
- t.Fatalf("Expected C, got %s", value)
- }
-
- if value := table.Data[3].Get("Key"); value != "D" {
- t.Fatalf("Expected D, got %s", value)
- }
-}
-
-func TestTableReverseSortStringValue(t *testing.T) {
- table := NewTable("Key", 0)
-
- e := &Env{}
- e.Set("Key", "A")
- table.Add(e)
-
- e = &Env{}
- e.Set("Key", "D")
- table.Add(e)
-
- e = &Env{}
- e.Set("Key", "B")
- table.Add(e)
-
- e = &Env{}
- e.Set("Key", "C")
- table.Add(e)
-
- table.ReverseSort()
-
- if len := table.Len(); len != 4 {
- t.Fatalf("Expected 4, got %d", len)
- }
-
- if value := table.Data[0].Get("Key"); value != "D" {
- t.Fatalf("Expected D, got %s", value)
- }
-
- if value := table.Data[1].Get("Key"); value != "C" {
- t.Fatalf("Expected B, got %s", value)
- }
-
- if value := table.Data[2].Get("Key"); value != "B" {
- t.Fatalf("Expected C, got %s", value)
- }
-
- if value := table.Data[3].Get("Key"); value != "A" {
- t.Fatalf("Expected A, got %s", value)
- }
-}
diff --git a/events/events.go b/events/events.go
deleted file mode 100644
index 559bf687e..000000000
--- a/events/events.go
+++ /dev/null
@@ -1,230 +0,0 @@
-package events
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "strings"
- "sync"
- "time"
-
- "github.com/docker/docker/engine"
- "github.com/docker/docker/pkg/parsers/filters"
- "github.com/docker/docker/utils"
-)
-
-const eventsLimit = 64
-
-type listener chan<- *utils.JSONMessage
-
-type Events struct {
- mu sync.RWMutex
- events []*utils.JSONMessage
- subscribers []listener
-}
-
-func New() *Events {
- return &Events{
- events: make([]*utils.JSONMessage, 0, eventsLimit),
- }
-}
-
-// Install installs events public api in docker engine
-func (e *Events) Install(eng *engine.Engine) error {
- // Here you should describe public interface
- jobs := map[string]engine.Handler{
- "events": e.Get,
- "log": e.Log,
- "subscribers_count": e.SubscribersCount,
- }
- for name, job := range jobs {
- if err := eng.Register(name, job); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (e *Events) Get(job *engine.Job) engine.Status {
- var (
- since = job.GetenvInt64("since")
- until = job.GetenvInt64("until")
- timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now()))
- )
-
- eventFilters, err := filters.FromParam(job.Getenv("filters"))
- if err != nil {
- return job.Error(err)
- }
-
- // If no until, disable timeout
- if until == 0 {
- timeout.Stop()
- }
-
- listener := make(chan *utils.JSONMessage)
- e.subscribe(listener)
- defer e.unsubscribe(listener)
-
- job.Stdout.Write(nil)
-
- // Resend every event in the [since, until] time interval.
- if since != 0 {
- if err := e.writeCurrent(job, since, until, eventFilters); err != nil {
- return job.Error(err)
- }
- }
-
- for {
- select {
- case event, ok := <-listener:
- if !ok {
- return engine.StatusOK
- }
- if err := writeEvent(job, event, eventFilters); err != nil {
- return job.Error(err)
- }
- case <-timeout.C:
- return engine.StatusOK
- }
- }
-}
-
-func (e *Events) Log(job *engine.Job) engine.Status {
- if len(job.Args) != 3 {
- return job.Errorf("usage: %s ACTION ID FROM", job.Name)
- }
- // not waiting for receivers
- go e.log(job.Args[0], job.Args[1], job.Args[2])
- return engine.StatusOK
-}
-
-func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
- ret := &engine.Env{}
- ret.SetInt("count", e.subscribersCount())
- ret.WriteTo(job.Stdout)
- return engine.StatusOK
-}
-
-func writeEvent(job *engine.Job, event *utils.JSONMessage, eventFilters filters.Args) error {
- isFiltered := func(field string, filter []string) bool {
- if len(filter) == 0 {
- return false
- }
- for _, v := range filter {
- if v == field {
- return false
- }
- if strings.Contains(field, ":") {
- image := strings.Split(field, ":")
- if image[0] == v {
- return false
- }
- }
- }
- return true
- }
-
- //incoming container filter can be name,id or partial id, convert and replace as a full container id
- for i, cn := range eventFilters["container"] {
- eventFilters["container"][i] = GetContainerId(job.Eng, cn)
- }
-
- if isFiltered(event.Status, eventFilters["event"]) || isFiltered(event.From, eventFilters["image"]) ||
- isFiltered(event.ID, eventFilters["container"]) {
- return nil
- }
-
- // When sending an event JSON serialization errors are ignored, but all
- // other errors lead to the eviction of the listener.
- if b, err := json.Marshal(event); err == nil {
- if _, err = job.Stdout.Write(b); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (e *Events) writeCurrent(job *engine.Job, since, until int64, eventFilters filters.Args) error {
- e.mu.RLock()
- for _, event := range e.events {
- if event.Time >= since && (event.Time <= until || until == 0) {
- if err := writeEvent(job, event, eventFilters); err != nil {
- e.mu.RUnlock()
- return err
- }
- }
- }
- e.mu.RUnlock()
- return nil
-}
-
-func (e *Events) subscribersCount() int {
- e.mu.RLock()
- c := len(e.subscribers)
- e.mu.RUnlock()
- return c
-}
-
-func (e *Events) log(action, id, from string) {
- e.mu.Lock()
- now := time.Now().UTC().Unix()
- jm := &utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
- if len(e.events) == cap(e.events) {
- // discard oldest event
- copy(e.events, e.events[1:])
- e.events[len(e.events)-1] = jm
- } else {
- e.events = append(e.events, jm)
- }
- for _, s := range e.subscribers {
- // We give each subscriber a 100ms time window to receive the event,
- // after which we move to the next.
- select {
- case s <- jm:
- case <-time.After(100 * time.Millisecond):
- }
- }
- e.mu.Unlock()
-}
-
-func (e *Events) subscribe(l listener) {
- e.mu.Lock()
- e.subscribers = append(e.subscribers, l)
- e.mu.Unlock()
-}
-
-// unsubscribe closes and removes the specified listener from the list of
-// previously registed ones.
-// It returns a boolean value indicating if the listener was successfully
-// found, closed and unregistered.
-func (e *Events) unsubscribe(l listener) bool {
- e.mu.Lock()
- for i, subscriber := range e.subscribers {
- if subscriber == l {
- close(l)
- e.subscribers = append(e.subscribers[:i], e.subscribers[i+1:]...)
- e.mu.Unlock()
- return true
- }
- }
- e.mu.Unlock()
- return false
-}
-
-func GetContainerId(eng *engine.Engine, name string) string {
- var buf bytes.Buffer
- job := eng.Job("container_inspect", name)
-
- var outStream io.Writer
-
- outStream = &buf
- job.Stdout.Set(outStream)
-
- if err := job.Run(); err != nil {
- return ""
- }
- var out struct{ ID string }
- json.NewDecoder(&buf).Decode(&out)
- return out.ID
-}
diff --git a/events/events_test.go b/events/events_test.go
deleted file mode 100644
index d4fc664ba..000000000
--- a/events/events_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-package events
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "testing"
- "time"
-
- "github.com/docker/docker/engine"
- "github.com/docker/docker/utils"
-)
-
-func TestEventsPublish(t *testing.T) {
- e := New()
- l1 := make(chan *utils.JSONMessage)
- l2 := make(chan *utils.JSONMessage)
- e.subscribe(l1)
- e.subscribe(l2)
- count := e.subscribersCount()
- if count != 2 {
- t.Fatalf("Must be 2 subscribers, got %d", count)
- }
- go e.log("test", "cont", "image")
- select {
- case msg := <-l1:
- if len(e.events) != 1 {
- t.Fatalf("Must be only one event, got %d", len(e.events))
- }
- if msg.Status != "test" {
- t.Fatalf("Status should be test, got %s", msg.Status)
- }
- if msg.ID != "cont" {
- t.Fatalf("ID should be cont, got %s", msg.ID)
- }
- if msg.From != "image" {
- t.Fatalf("From should be image, got %s", msg.From)
- }
- case <-time.After(1 * time.Second):
- t.Fatal("Timeout waiting for broadcasted message")
- }
- select {
- case msg := <-l2:
- if len(e.events) != 1 {
- t.Fatalf("Must be only one event, got %d", len(e.events))
- }
- if msg.Status != "test" {
- t.Fatalf("Status should be test, got %s", msg.Status)
- }
- if msg.ID != "cont" {
- t.Fatalf("ID should be cont, got %s", msg.ID)
- }
- if msg.From != "image" {
- t.Fatalf("From should be image, got %s", msg.From)
- }
- case <-time.After(1 * time.Second):
- t.Fatal("Timeout waiting for broadcasted message")
- }
-}
-
-func TestEventsPublishTimeout(t *testing.T) {
- e := New()
- l := make(chan *utils.JSONMessage)
- e.subscribe(l)
-
- c := make(chan struct{})
- go func() {
- e.log("test", "cont", "image")
- close(c)
- }()
-
- select {
- case <-c:
- case <-time.After(time.Second):
- t.Fatal("Timeout publishing message")
- }
-}
-
-func TestLogEvents(t *testing.T) {
- e := New()
- eng := engine.New()
- if err := e.Install(eng); err != nil {
- t.Fatal(err)
- }
-
- for i := 0; i < eventsLimit+16; i++ {
- action := fmt.Sprintf("action_%d", i)
- id := fmt.Sprintf("cont_%d", i)
- from := fmt.Sprintf("image_%d", i)
- job := eng.Job("log", action, id, from)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- }
- time.Sleep(50 * time.Millisecond)
- if len(e.events) != eventsLimit {
- t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
- }
-
- job := eng.Job("events")
- job.SetenvInt64("since", 1)
- job.SetenvInt64("until", time.Now().Unix())
- buf := bytes.NewBuffer(nil)
- job.Stdout.Add(buf)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- buf = bytes.NewBuffer(buf.Bytes())
- dec := json.NewDecoder(buf)
- var msgs []utils.JSONMessage
- for {
- var jm utils.JSONMessage
- if err := dec.Decode(&jm); err != nil {
- if err == io.EOF {
- break
- }
- t.Fatal(err)
- }
- msgs = append(msgs, jm)
- }
- if len(msgs) != eventsLimit {
- t.Fatalf("Must be %d events, got %d", eventsLimit, len(msgs))
- }
- first := msgs[0]
- if first.Status != "action_16" {
- t.Fatalf("First action is %s, must be action_15", first.Status)
- }
- last := msgs[len(msgs)-1]
- if last.Status != "action_79" {
- t.Fatalf("First action is %s, must be action_79", first.Status)
- }
-}
-
-func TestEventsCountJob(t *testing.T) {
- e := New()
- eng := engine.New()
- if err := e.Install(eng); err != nil {
- t.Fatal(err)
- }
- l1 := make(chan *utils.JSONMessage)
- l2 := make(chan *utils.JSONMessage)
- e.subscribe(l1)
- e.subscribe(l2)
- job := eng.Job("subscribers_count")
- env, _ := job.Stdout.AddEnv()
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- count := env.GetInt("count")
- if count != 2 {
- t.Fatalf("There must be 2 subscribers, got %d", count)
- }
-}
diff --git a/graph/export.go b/graph/export.go
index 3f7ecd3c4..c356a2322 100644
--- a/graph/export.go
+++ b/graph/export.go
@@ -5,10 +5,9 @@ import (
"io"
"io/ioutil"
"os"
- "path"
+ "path/filepath"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
@@ -19,42 +18,45 @@ import (
// uncompressed tar ball.
// name is the set of tags to export.
// out is the writer where the images are written to.
-func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
- if len(job.Args) < 1 {
- return job.Errorf("Usage: %s IMAGE [IMAGE...]\n", job.Name)
- }
+type ImageExportConfig struct {
+ Names []string
+ Outstream io.Writer
+}
+
+func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error {
+
// get image json
tempdir, err := ioutil.TempDir("", "docker-export-")
if err != nil {
- return job.Error(err)
+ return err
}
defer os.RemoveAll(tempdir)
rootRepoMap := map[string]Repository{}
addKey := func(name string, tag string, id string) {
- log.Debugf("add key [%s:%s]", name, tag)
+ logrus.Debugf("add key [%s:%s]", name, tag)
if repo, ok := rootRepoMap[name]; !ok {
rootRepoMap[name] = Repository{tag: id}
} else {
repo[tag] = id
}
}
- for _, name := range job.Args {
+ for _, name := range imageExportConfig.Names {
name = registry.NormalizeLocalName(name)
- log.Debugf("Serializing %s", name)
+ logrus.Debugf("Serializing %s", name)
rootRepo := s.Repositories[name]
if rootRepo != nil {
// this is a base repo name, like 'busybox'
for tag, id := range rootRepo {
addKey(name, tag, id)
- if err := s.exportImage(job.Eng, id, tempdir); err != nil {
- return job.Error(err)
+ if err := s.exportImage(id, tempdir); err != nil {
+ return err
}
}
} else {
img, err := s.LookupImage(name)
if err != nil {
- return job.Error(err)
+ return err
}
if img != nil {
@@ -66,47 +68,54 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
if len(repoTag) > 0 {
addKey(repoName, repoTag, img.ID)
}
- if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil {
- return job.Error(err)
+ if err := s.exportImage(img.ID, tempdir); err != nil {
+ return err
}
} else {
// this must be an ID that didn't get looked up just right?
- if err := s.exportImage(job.Eng, name, tempdir); err != nil {
- return job.Error(err)
+ if err := s.exportImage(name, tempdir); err != nil {
+ return err
}
}
}
- log.Debugf("End Serializing %s", name)
+ logrus.Debugf("End Serializing %s", name)
}
// write repositories, if there is something to write
if len(rootRepoMap) > 0 {
- rootRepoJson, _ := json.Marshal(rootRepoMap)
- if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil {
- return job.Error(err)
+ f, err := os.OpenFile(filepath.Join(tempdir, "repositories"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ f.Close()
+ return err
+ }
+ if err := json.NewEncoder(f).Encode(rootRepoMap); err != nil {
+ return err
+ }
+ if err := f.Close(); err != nil {
+ return err
}
} else {
- log.Debugf("There were no repositories to write")
+ logrus.Debugf("There were no repositories to write")
}
fs, err := archive.Tar(tempdir, archive.Uncompressed)
if err != nil {
- return job.Error(err)
+ return err
}
defer fs.Close()
- if _, err := io.Copy(job.Stdout, fs); err != nil {
- return job.Error(err)
+ if _, err := io.Copy(imageExportConfig.Outstream, fs); err != nil {
+ return err
}
- log.Debugf("End export job: %s", job.Name)
- return engine.StatusOK
+ logrus.Debugf("End export image")
+ return nil
}
// FIXME: this should be a top-level function, not a class method
-func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error {
+func (s *TagStore) exportImage(name, tempdir string) error {
for n := name; n != ""; {
// temporary directory
- tmpImageDir := path.Join(tempdir, n)
+ tmpImageDir := filepath.Join(tempdir, n)
if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
if os.IsExist(err) {
return nil
@@ -117,40 +126,42 @@ func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error {
var version = "1.0"
var versionBuf = []byte(version)
- if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil {
+ if err := ioutil.WriteFile(filepath.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil {
return err
}
// serialize json
- json, err := os.Create(path.Join(tmpImageDir, "json"))
+ json, err := os.Create(filepath.Join(tmpImageDir, "json"))
if err != nil {
return err
}
- job := eng.Job("image_inspect", n)
- job.SetenvBool("raw", true)
- job.Stdout.Add(json)
- if err := job.Run(); err != nil {
+ imageInspectRaw, err := s.LookupRaw(n)
+ if err != nil {
return err
}
+ written, err := json.Write(imageInspectRaw)
+ if err != nil {
+ return err
+ }
+ if written != len(imageInspectRaw) {
+ logrus.Warnf("%d byes should have been written instead %d have been written", written, len(imageInspectRaw))
+ }
// serialize filesystem
- fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
+ fsTar, err := os.Create(filepath.Join(tmpImageDir, "layer.tar"))
if err != nil {
return err
}
- job = eng.Job("image_tarlayer", n)
- job.Stdout.Add(fsTar)
- if err := job.Run(); err != nil {
+ if err := s.ImageTarLayer(n, fsTar); err != nil {
return err
}
// find parent
- job = eng.Job("image_get", n)
- info, _ := job.Stdout.AddEnv()
- if err := job.Run(); err != nil {
+ img, err := s.LookupImage(n)
+ if err != nil {
return err
}
- n = info.Get("Parent")
+ n = img.Parent
}
return nil
}
diff --git a/graph/graph.go b/graph/graph.go
index 5c6ac9701..e95887fab 100644
--- a/graph/graph.go
+++ b/graph/graph.go
@@ -7,24 +7,24 @@ import (
"io"
"io/ioutil"
"os"
- "path"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
- "github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/progressreader"
+ "github.com/docker/docker/pkg/streamformatter"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/truncindex"
"github.com/docker/docker/runconfig"
- "github.com/docker/docker/utils"
)
// A Graph is a store for versioned filesystem images and the relationship between them.
@@ -42,7 +42,7 @@ func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) {
return nil, err
}
// Create the root directory if it doesn't exists
- if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
+ if err := system.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
@@ -70,14 +70,14 @@ func (graph *Graph) restore() error {
}
}
graph.idIndex = truncindex.NewTruncIndex(ids)
- log.Debugf("Restored %d elements", len(dir))
+ logrus.Debugf("Restored %d elements", len(dir))
return nil
}
// FIXME: Implement error subclass instead of looking at the error text
// Note: This is the way golang implements os.IsNotExists on Plan9
-func (graph *Graph) IsNotExist(err error) bool {
- return err != nil && (strings.Contains(strings.ToLower(err.Error()), "does not exist") || strings.Contains(strings.ToLower(err.Error()), "no such"))
+func (graph *Graph) IsNotExist(err error, id string) bool {
+ return err != nil && (strings.Contains(strings.ToLower(err.Error()), "does not exist") || strings.Contains(strings.ToLower(err.Error()), "no such")) && strings.Contains(err.Error(), id)
}
// Exists returns true if an image is registered at the given id.
@@ -121,7 +121,7 @@ func (graph *Graph) Get(name string) (*image.Image, error) {
// Create creates a new image and registers it in the graph.
func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, containerImage, comment, author string, containerConfig, config *runconfig.Config) (*image.Image, error) {
img := &image.Image{
- ID: common.GenerateRandomID(),
+ ID: stringid.GenerateRandomID(),
Comment: comment,
Created: time.Now().UTC(),
DockerVersion: dockerversion.VERSION,
@@ -153,7 +153,7 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader)
graph.driver.Remove(img.ID)
}
}()
- if err := utils.ValidateID(img.ID); err != nil {
+ if err := image.ValidateID(img.ID); err != nil {
return err
}
// (This is a convenience to save time. Race conditions are taken care of by os.Rename)
@@ -201,7 +201,7 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader)
// The archive is stored on disk and will be automatically deleted as soon as has been read.
// If output is not nil, a human-readable progress bar will be written to it.
// FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives?
-func (graph *Graph) TempLayerArchive(id string, sf *utils.StreamFormatter, output io.Writer) (*archive.TempArchive, error) {
+func (graph *Graph) TempLayerArchive(id string, sf *streamformatter.StreamFormatter, output io.Writer) (*archive.TempArchive, error) {
image, err := graph.Get(id)
if err != nil {
return nil, err
@@ -220,7 +220,7 @@ func (graph *Graph) TempLayerArchive(id string, sf *utils.StreamFormatter, outpu
Formatter: sf,
Size: 0,
NewLines: false,
- ID: common.TruncateID(id),
+ ID: stringid.TruncateID(id),
Action: "Buffering to disk",
})
defer progressReader.Close()
@@ -229,8 +229,8 @@ func (graph *Graph) TempLayerArchive(id string, sf *utils.StreamFormatter, outpu
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
func (graph *Graph) Mktemp(id string) (string, error) {
- dir := path.Join(graph.Root, "_tmp", common.GenerateRandomID())
- if err := os.MkdirAll(dir, 0700); err != nil {
+ dir := filepath.Join(graph.Root, "_tmp", stringid.GenerateRandomID())
+ if err := system.MkdirAll(dir, 0700); err != nil {
return "", err
}
return dir, nil
@@ -254,9 +254,6 @@ func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) {
if err != nil {
return 0, "", err
}
- if err = f.Sync(); err != nil {
- return 0, "", err
- }
n, err := f.Seek(0, os.SEEK_CUR)
if err != nil {
return 0, "", err
@@ -290,28 +287,28 @@ func SetupInitLayer(initLayer string) error {
parts := strings.Split(pth, "/")
prev := "/"
for _, p := range parts[1:] {
- prev = path.Join(prev, p)
- syscall.Unlink(path.Join(initLayer, prev))
+ prev = filepath.Join(prev, p)
+ syscall.Unlink(filepath.Join(initLayer, prev))
}
- if _, err := os.Stat(path.Join(initLayer, pth)); err != nil {
+ if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil {
if os.IsNotExist(err) {
- if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil {
+ if err := system.MkdirAll(filepath.Join(initLayer, filepath.Dir(pth)), 0755); err != nil {
return err
}
switch typ {
case "dir":
- if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil {
+ if err := system.MkdirAll(filepath.Join(initLayer, pth), 0755); err != nil {
return err
}
case "file":
- f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755)
+ f, err := os.OpenFile(filepath.Join(initLayer, pth), os.O_CREATE, 0755)
if err != nil {
return err
}
f.Close()
default:
- if err := os.Symlink(typ, path.Join(initLayer, pth)); err != nil {
+ if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil {
return err
}
}
@@ -348,9 +345,8 @@ func (graph *Graph) Delete(name string) error {
tmp, err := graph.Mktemp("")
graph.idIndex.Delete(id)
if err == nil {
- err = os.Rename(graph.ImageRoot(id), tmp)
- // On err make tmp point to old dir and cleanup unused tmp dir
- if err != nil {
+ if err := os.Rename(graph.ImageRoot(id), tmp); err != nil {
+ // On err make tmp point to old dir and cleanup unused tmp dir
os.RemoveAll(tmp)
tmp = graph.ImageRoot(id)
}
@@ -433,7 +429,7 @@ func (graph *Graph) Heads() (map[string]*image.Image, error) {
}
func (graph *Graph) ImageRoot(id string) string {
- return path.Join(graph.Root, id)
+ return filepath.Join(graph.Root, id)
}
func (graph *Graph) Driver() graphdriver.Driver {
diff --git a/integration/graph_test.go b/graph/graph_test.go
similarity index 88%
rename from integration/graph_test.go
rename to graph/graph_test.go
index 8518fae8e..81471b674 100644
--- a/integration/graph_test.go
+++ b/graph/graph_test.go
@@ -1,20 +1,18 @@
-package docker
+package graph
import (
"errors"
- "github.com/docker/docker/autogen/dockerversion"
- "github.com/docker/docker/daemon/graphdriver"
- "github.com/docker/docker/graph"
- "github.com/docker/docker/image"
- "github.com/docker/docker/pkg/archive"
- "github.com/docker/docker/pkg/common"
- "github.com/docker/docker/utils"
"io"
"io/ioutil"
"os"
"path"
"testing"
"time"
+
+ "github.com/docker/docker/autogen/dockerversion"
+ "github.com/docker/docker/daemon/graphdriver"
+ "github.com/docker/docker/image"
+ "github.com/docker/docker/pkg/stringid"
)
func TestMount(t *testing.T) {
@@ -47,6 +45,7 @@ func TestMount(t *testing.T) {
if _, err := driver.Get(image.ID, ""); err != nil {
t.Fatal(err)
}
+
}
func TestInit(t *testing.T) {
@@ -70,7 +69,7 @@ func TestInterruptedRegister(t *testing.T) {
defer nukeGraph(graph)
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
image := &image.Image{
- ID: common.GenerateRandomID(),
+ ID: stringid.GenerateRandomID(),
Comment: "testing",
Created: time.Now(),
}
@@ -102,7 +101,7 @@ func TestGraphCreate(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if err := utils.ValidateID(img.ID); err != nil {
+ if err := image.ValidateID(img.ID); err != nil {
t.Fatal(err)
}
if img.Comment != "Testing" {
@@ -130,7 +129,7 @@ func TestRegister(t *testing.T) {
t.Fatal(err)
}
image := &image.Image{
- ID: common.GenerateRandomID(),
+ ID: stringid.GenerateRandomID(),
Comment: "testing",
Created: time.Now(),
}
@@ -160,24 +159,12 @@ func TestDeletePrefix(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
img := createTestImage(graph, t)
- if err := graph.Delete(common.TruncateID(img.ID)); err != nil {
+ if err := graph.Delete(stringid.TruncateID(img.ID)); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
}
-func createTestImage(graph *graph.Graph, t *testing.T) *image.Image {
- archive, err := fakeTar()
- if err != nil {
- t.Fatal(err)
- }
- img, err := graph.Create(archive, "", "", "Test image", "", nil, nil)
- if err != nil {
- t.Fatal(err)
- }
- return img
-}
-
func TestDelete(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
@@ -246,19 +233,19 @@ func TestByParent(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
parentImage := &image.Image{
- ID: common.GenerateRandomID(),
+ ID: stringid.GenerateRandomID(),
Comment: "parent",
Created: time.Now(),
Parent: "",
}
childImage1 := &image.Image{
- ID: common.GenerateRandomID(),
+ ID: stringid.GenerateRandomID(),
Comment: "child1",
Created: time.Now(),
Parent: parentImage.ID,
}
childImage2 := &image.Image{
- ID: common.GenerateRandomID(),
+ ID: stringid.GenerateRandomID(),
Comment: "child2",
Created: time.Now(),
Parent: parentImage.ID,
@@ -277,11 +264,19 @@ func TestByParent(t *testing.T) {
}
}
-/*
- * HELPER FUNCTIONS
- */
+func createTestImage(graph *Graph, t *testing.T) *image.Image {
+ archive, err := fakeTar()
+ if err != nil {
+ t.Fatal(err)
+ }
+ img, err := graph.Create(archive, "", "", "Test image", "", nil, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return img
+}
-func assertNImages(graph *graph.Graph, t *testing.T, n int) {
+func assertNImages(graph *Graph, t *testing.T, n int) {
if images, err := graph.Map(); err != nil {
t.Fatal(err)
} else if actualN := len(images); actualN != n {
@@ -289,7 +284,7 @@ func assertNImages(graph *graph.Graph, t *testing.T, n int) {
}
}
-func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) {
+func tempGraph(t *testing.T) (*Graph, graphdriver.Driver) {
tmp, err := ioutil.TempDir("", "docker-graph-")
if err != nil {
t.Fatal(err)
@@ -298,22 +293,14 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) {
if err != nil {
t.Fatal(err)
}
- graph, err := graph.NewGraph(tmp, driver)
+ graph, err := NewGraph(tmp, driver)
if err != nil {
t.Fatal(err)
}
return graph, driver
}
-func nukeGraph(graph *graph.Graph) {
+func nukeGraph(graph *Graph) {
graph.Driver().Cleanup()
os.RemoveAll(graph.Root)
}
-
-func testArchive(t *testing.T) archive.Archive {
- archive, err := fakeTar()
- if err != nil {
- t.Fatal(err)
- }
- return archive
-}
diff --git a/graph/history.go b/graph/history.go
index 7f5063e91..56e759a8e 100644
--- a/graph/history.go
+++ b/graph/history.go
@@ -3,19 +3,15 @@ package graph
import (
"strings"
- "github.com/docker/docker/engine"
+ "github.com/docker/docker/api/types"
"github.com/docker/docker/image"
"github.com/docker/docker/utils"
)
-func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.Errorf("Usage: %s IMAGE", job.Name)
- }
- name := job.Args[0]
+func (s *TagStore) History(name string) ([]*types.ImageHistory, error) {
foundImage, err := s.LookupImage(name)
if err != nil {
- return job.Error(err)
+ return nil, err
}
lookupMap := make(map[string][]string)
@@ -29,19 +25,19 @@ func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
}
}
- outs := engine.NewTable("Created", 0)
+ history := []*types.ImageHistory{}
+
err = foundImage.WalkHistory(func(img *image.Image) error {
- out := &engine.Env{}
- out.SetJson("Id", img.ID)
- out.SetInt64("Created", img.Created.Unix())
- out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " "))
- out.SetList("Tags", lookupMap[img.ID])
- out.SetInt64("Size", img.Size)
- outs.Add(out)
+ history = append(history, &types.ImageHistory{
+ ID: img.ID,
+ Created: img.Created.Unix(),
+ CreatedBy: strings.Join(img.ContainerConfig.Cmd.Slice(), " "),
+ Tags: lookupMap[img.ID],
+ Size: img.Size,
+ Comment: img.Comment,
+ })
return nil
})
- if _, err := outs.WriteListTo(job.Stdout); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
+
+ return history, err
}
diff --git a/graph/import.go b/graph/import.go
index 44b1ecbd5..2e08e6c84 100644
--- a/graph/import.go
+++ b/graph/import.go
@@ -1,57 +1,52 @@
package graph
import (
- "bytes"
- "encoding/json"
+ "io"
"net/http"
"net/url"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
"github.com/docker/docker/pkg/archive"
+ "github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/progressreader"
+ "github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
-func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 2 && n != 3 {
- return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name)
- }
+type ImageImportConfig struct {
+ Changes []string
+ InConfig io.ReadCloser
+ OutStream io.Writer
+ ContainerConfig *runconfig.Config
+}
+
+func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig) error {
var (
- src = job.Args[0]
- repo = job.Args[1]
- tag string
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
- archive archive.ArchiveReader
- resp *http.Response
- stdoutBuffer = bytes.NewBuffer(nil)
- newConfig runconfig.Config
+ sf = streamformatter.NewJSONStreamFormatter()
+ archive archive.ArchiveReader
+ resp *http.Response
)
- if len(job.Args) > 2 {
- tag = job.Args[2]
- }
if src == "-" {
- archive = job.Stdin
+ archive = imageImportConfig.InConfig
} else {
u, err := url.Parse(src)
if err != nil {
- return job.Error(err)
+ return err
}
if u.Scheme == "" {
u.Scheme = "http"
u.Host = src
u.Path = ""
}
- job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u))
- resp, err = utils.Download(u.String())
+ imageImportConfig.OutStream.Write(sf.FormatStatus("", "Downloading from %s", u))
+ resp, err = httputils.Download(u.String())
if err != nil {
- return job.Error(err)
+ return err
}
progressReader := progressreader.New(progressreader.Config{
In: resp.Body,
- Out: job.Stdout,
+ Out: imageImportConfig.OutStream,
Formatter: sf,
Size: int(resp.ContentLength),
NewLines: true,
@@ -62,36 +57,22 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
archive = progressReader
}
- buildConfigJob := job.Eng.Job("build_config")
- buildConfigJob.Stdout.Add(stdoutBuffer)
- buildConfigJob.Setenv("changes", job.Getenv("changes"))
- // FIXME this should be remove when we remove deprecated config param
- buildConfigJob.Setenv("config", job.Getenv("config"))
-
- if err := buildConfigJob.Run(); err != nil {
- return job.Error(err)
- }
- if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil {
- return job.Error(err)
- }
-
- img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, &newConfig)
+ img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, imageImportConfig.ContainerConfig)
if err != nil {
- return job.Error(err)
+ return err
}
// Optionally register the image at REPO/TAG
if repo != "" {
- if err := s.Set(repo, tag, img.ID, true); err != nil {
- return job.Error(err)
+ if err := s.Tag(repo, tag, img.ID, true); err != nil {
+ return err
}
}
- job.Stdout.Write(sf.FormatStatus("", img.ID))
+ imageImportConfig.OutStream.Write(sf.FormatStatus("", img.ID))
logID := img.ID
if tag != "" {
logID = utils.ImageReference(logID, tag)
}
- if err = job.Eng.Job("log", "import", logID, "").Run(); err != nil {
- log.Errorf("Error logging event 'import' for %s: %s", logID, err)
- }
- return engine.StatusOK
+
+ s.eventsService.Log("import", logID, "")
+ return nil
}
diff --git a/graph/list.go b/graph/list.go
index 9f7bccdfa..f95508e95 100644
--- a/graph/list.go
+++ b/graph/list.go
@@ -1,11 +1,13 @@
package graph
import (
+ "fmt"
"log"
"path"
+ "sort"
"strings"
- "github.com/docker/docker/engine"
+ "github.com/docker/docker/api/types"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/utils"
@@ -16,47 +18,60 @@ var acceptedImageFilterTags = map[string]struct{}{
"label": {},
}
-func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
+type ImagesConfig struct {
+ Filters string
+ Filter string
+ All bool
+}
+
+type ByCreated []*types.Image
+
+func (r ByCreated) Len() int { return len(r) }
+func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
+func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
+
+func (s *TagStore) Images(config *ImagesConfig) ([]*types.Image, error) {
var (
- allImages map[string]*image.Image
- err error
- filt_tagged = true
- filt_label = false
+ allImages map[string]*image.Image
+ err error
+ filtTagged = true
+ filtLabel = false
)
- imageFilters, err := filters.FromParam(job.Getenv("filters"))
+ imageFilters, err := filters.FromParam(config.Filters)
if err != nil {
- return job.Error(err)
+ return nil, err
}
for name := range imageFilters {
if _, ok := acceptedImageFilterTags[name]; !ok {
- return job.Errorf("Invalid filter '%s'", name)
+ return nil, fmt.Errorf("Invalid filter '%s'", name)
}
}
if i, ok := imageFilters["dangling"]; ok {
for _, value := range i {
if strings.ToLower(value) == "true" {
- filt_tagged = false
+ filtTagged = false
}
}
}
- _, filt_label = imageFilters["label"]
+ _, filtLabel = imageFilters["label"]
- if job.GetenvBool("all") && filt_tagged {
+ if config.All && filtTagged {
allImages, err = s.graph.Map()
} else {
allImages, err = s.graph.Heads()
}
if err != nil {
- return job.Error(err)
+ return nil, err
}
- lookup := make(map[string]*engine.Env)
+
+ lookup := make(map[string]*types.Image)
s.Lock()
for repoName, repository := range s.Repositories {
- if job.Getenv("filter") != "" {
- if match, _ := path.Match(job.Getenv("filter"), repoName); !match {
+ if config.Filter != "" {
+ if match, _ := path.Match(config.Filter, repoName); !match {
continue
}
}
@@ -68,12 +83,12 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
continue
}
- if out, exists := lookup[id]; exists {
- if filt_tagged {
+ if lImage, exists := lookup[id]; exists {
+ if filtTagged {
if utils.DigestReference(ref) {
- out.SetList("RepoDigests", append(out.GetList("RepoDigests"), imgRef))
+ lImage.RepoDigests = append(lImage.RepoDigests, imgRef)
} else { // Tag Ref.
- out.SetList("RepoTags", append(out.GetList("RepoTags"), imgRef))
+ lImage.RepoTags = append(lImage.RepoTags, imgRef)
}
}
} else {
@@ -82,24 +97,24 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
continue
}
- if filt_tagged {
- out := &engine.Env{}
- out.SetJson("ParentId", image.Parent)
- out.SetJson("Id", image.ID)
- out.SetInt64("Created", image.Created.Unix())
- out.SetInt64("Size", image.Size)
- out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
- out.SetJson("Labels", image.ContainerConfig.Labels)
+ if filtTagged {
+ newImage := new(types.Image)
+ newImage.ParentId = image.Parent
+ newImage.ID = image.ID
+ newImage.Created = int(image.Created.Unix())
+ newImage.Size = int(image.Size)
+ newImage.VirtualSize = int(image.GetParentsSize(0) + image.Size)
+ newImage.Labels = image.ContainerConfig.Labels
if utils.DigestReference(ref) {
- out.SetList("RepoTags", []string{})
- out.SetList("RepoDigests", []string{imgRef})
+ newImage.RepoTags = []string{}
+ newImage.RepoDigests = []string{imgRef}
} else {
- out.SetList("RepoTags", []string{imgRef})
- out.SetList("RepoDigests", []string{})
+ newImage.RepoTags = []string{imgRef}
+ newImage.RepoDigests = []string{}
}
- lookup[id] = out
+ lookup[id] = newImage
}
}
@@ -107,33 +122,32 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
}
s.Unlock()
- outs := engine.NewTable("Created", len(lookup))
+ images := []*types.Image{}
for _, value := range lookup {
- outs.Add(value)
+ images = append(images, value)
}
// Display images which aren't part of a repository/tag
- if job.Getenv("filter") == "" || filt_label {
+ if config.Filter == "" || filtLabel {
for _, image := range allImages {
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
continue
}
- out := &engine.Env{}
- out.SetJson("ParentId", image.Parent)
- out.SetList("RepoTags", []string{":"})
- out.SetList("RepoDigests", []string{"@"})
- out.SetJson("Id", image.ID)
- out.SetInt64("Created", image.Created.Unix())
- out.SetInt64("Size", image.Size)
- out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
- out.SetJson("Labels", image.ContainerConfig.Labels)
- outs.Add(out)
+ newImage := new(types.Image)
+ newImage.ParentId = image.Parent
+ newImage.RepoTags = []string{":"}
+ newImage.RepoDigests = []string{"@"}
+ newImage.ID = image.ID
+ newImage.Created = int(image.Created.Unix())
+ newImage.Size = int(image.Size)
+ newImage.VirtualSize = int(image.GetParentsSize(0) + image.Size)
+ newImage.Labels = image.ContainerConfig.Labels
+
+ images = append(images, newImage)
}
}
- outs.ReverseSort()
- if _, err := outs.WriteListTo(job.Stdout); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
+ sort.Sort(sort.Reverse(ByCreated(images)))
+
+ return images, nil
}
diff --git a/graph/load.go b/graph/load.go
index c257e9ef1..9afde34c9 100644
--- a/graph/load.go
+++ b/graph/load.go
@@ -1,40 +1,39 @@
-// +build linux
+// +build linux windows
package graph
import (
"encoding/json"
+ "io"
"io/ioutil"
"os"
- "path"
+ "path/filepath"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
- "github.com/docker/docker/utils"
)
// Loads a set of images into the repository. This is the complementary of ImageExport.
// The input stream is an uncompressed tar ball containing images and metadata.
-func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
+func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error {
tmpImageDir, err := ioutil.TempDir("", "docker-import-")
if err != nil {
- return job.Error(err)
+ return err
}
defer os.RemoveAll(tmpImageDir)
var (
- repoDir = path.Join(tmpImageDir, "repo")
+ repoDir = filepath.Join(tmpImageDir, "repo")
)
if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
- return job.Error(err)
+ return err
}
images, err := s.graph.Map()
if err != nil {
- return job.Error(err)
+ return err
}
excludes := make([]string, len(images))
i := 0
@@ -42,73 +41,77 @@ func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
excludes[i] = k
i++
}
- if err := chrootarchive.Untar(job.Stdin, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil {
- return job.Error(err)
+ if err := chrootarchive.Untar(inTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil {
+ return err
}
dirs, err := ioutil.ReadDir(repoDir)
if err != nil {
- return job.Error(err)
+ return err
}
for _, d := range dirs {
if d.IsDir() {
- if err := s.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil {
- return job.Error(err)
+ if err := s.recursiveLoad(d.Name(), tmpImageDir); err != nil {
+ return err
}
}
}
- repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
- if err == nil {
- repositories := map[string]Repository{}
- if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
- return job.Error(err)
+ reposJSONFile, err := os.Open(filepath.Join(tmpImageDir, "repo", "repositories"))
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return err
}
+ return nil
+ }
+ defer reposJSONFile.Close()
- for imageName, tagMap := range repositories {
- for tag, address := range tagMap {
- if err := s.Set(imageName, tag, address, true); err != nil {
- return job.Error(err)
- }
- }
- }
- } else if !os.IsNotExist(err) {
- return job.Error(err)
+ repositories := map[string]Repository{}
+ if err := json.NewDecoder(reposJSONFile).Decode(&repositories); err != nil {
+ return err
}
- return engine.StatusOK
+ for imageName, tagMap := range repositories {
+ for tag, address := range tagMap {
+ if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
}
-func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error {
- if err := eng.Job("image_get", address).Run(); err != nil {
- log.Debugf("Loading %s", address)
+func (s *TagStore) recursiveLoad(address, tmpImageDir string) error {
+ if _, err := s.LookupImage(address); err != nil {
+ logrus.Debugf("Loading %s", address)
- imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
+ imageJson, err := ioutil.ReadFile(filepath.Join(tmpImageDir, "repo", address, "json"))
if err != nil {
- log.Debugf("Error reading json", err)
+ logrus.Debugf("Error reading json", err)
return err
}
- layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
+ layer, err := os.Open(filepath.Join(tmpImageDir, "repo", address, "layer.tar"))
if err != nil {
- log.Debugf("Error reading embedded tar", err)
+ logrus.Debugf("Error reading embedded tar", err)
return err
}
img, err := image.NewImgJSON(imageJson)
if err != nil {
- log.Debugf("Error unmarshalling json", err)
+ logrus.Debugf("Error unmarshalling json", err)
return err
}
- if err := utils.ValidateID(img.ID); err != nil {
- log.Debugf("Error validating ID: %s", err)
+ if err := image.ValidateID(img.ID); err != nil {
+ logrus.Debugf("Error validating ID: %s", err)
return err
}
// ensure no two downloads of the same layer happen at the same time
if c, err := s.poolAdd("pull", "layer:"+img.ID); err != nil {
if c != nil {
- log.Debugf("Image (id: %s) load is already running, waiting: %v", img.ID, err)
+ logrus.Debugf("Image (id: %s) load is already running, waiting: %v", img.ID, err)
<-c
return nil
}
@@ -120,7 +123,7 @@ func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string
if img.Parent != "" {
if !s.graph.Exists(img.Parent) {
- if err := s.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil {
+ if err := s.recursiveLoad(img.Parent, tmpImageDir); err != nil {
return err
}
}
@@ -129,7 +132,7 @@ func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string
return err
}
}
- log.Debugf("Completed processing %s", address)
+ logrus.Debugf("Completed processing %s", address)
return nil
}
diff --git a/graph/load_unsupported.go b/graph/load_unsupported.go
index 164e9176a..45bdd98be 100644
--- a/graph/load_unsupported.go
+++ b/graph/load_unsupported.go
@@ -1,11 +1,12 @@
-// +build !linux
+// +build !linux,!windows
package graph
import (
- "github.com/docker/docker/engine"
+ "fmt"
+ "io"
)
-func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
- return job.Errorf("CmdLoad is not supported on this platform")
+func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error {
+ return fmt.Errorf("Load is not supported on this platform")
}
diff --git a/graph/manifest.go b/graph/manifest.go
index 3b1d82557..053a185ba 100644
--- a/graph/manifest.go
+++ b/graph/manifest.go
@@ -1,14 +1,13 @@
package graph
import (
- "bytes"
"encoding/json"
"fmt"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
- "github.com/docker/docker/engine"
"github.com/docker/docker/registry"
+ "github.com/docker/docker/trust"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)
@@ -18,7 +17,7 @@ import (
// contains no signatures by a trusted key for the name in the manifest, the
// image is not considered verified. The parsed manifest object and a boolean
// for whether the manifest is verified is returned.
-func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) {
+func (s *TagStore) loadManifest(manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) {
sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures")
if err != nil {
return nil, false, fmt.Errorf("error parsing payload: %s", err)
@@ -69,32 +68,28 @@ func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst,
var verified bool
for _, key := range keys {
- job := eng.Job("trust_key_check")
- b, err := key.MarshalJSON()
- if err != nil {
- return nil, false, fmt.Errorf("error marshalling public key: %s", err)
- }
namespace := manifest.Name
if namespace[0] != '/' {
namespace = "/" + namespace
}
- stdoutBuffer := bytes.NewBuffer(nil)
-
- job.Args = append(job.Args, namespace)
- job.Setenv("PublicKey", string(b))
- // Check key has read/write permission (0x03)
- job.SetenvInt("Permission", 0x03)
- job.Stdout.Add(stdoutBuffer)
- if err = job.Run(); err != nil {
- return nil, false, fmt.Errorf("error running key check: %s", err)
+ b, err := key.MarshalJSON()
+ if err != nil {
+ return nil, false, fmt.Errorf("error marshalling public key: %s", err)
}
- result := engine.Tail(stdoutBuffer, 1)
- log.Debugf("Key check result: %q", result)
- if result == "verified" {
- verified = true
+ // Check key has read/write permission (0x03)
+ v, err := s.trustService.CheckKey(namespace, b, 0x03)
+ if err != nil {
+ vErr, ok := err.(trust.NotVerifiedError)
+ if !ok {
+ return nil, false, fmt.Errorf("error running key check: %s", err)
+ }
+ logrus.Debugf("Key check result: %v", vErr)
+ }
+ verified = v
+ if verified {
+ logrus.Debug("Key check result: verified")
}
}
-
return &manifest, verified, nil
}
diff --git a/graph/manifest_test.go b/graph/manifest_test.go
index 913704182..2702dcaf5 100644
--- a/graph/manifest_test.go
+++ b/graph/manifest_test.go
@@ -135,7 +135,7 @@ func TestManifestTarsumCache(t *testing.T) {
if err := store.graph.Register(img, archive); err != nil {
t.Fatal(err)
}
- if err := store.Set(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil {
+ if err := store.Tag(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil {
t.Fatal(err)
}
diff --git a/graph/pull.go b/graph/pull.go
index adad6f323..b2a9ef178 100644
--- a/graph/pull.go
+++ b/graph/pull.go
@@ -10,104 +10,204 @@ import (
"strings"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
- "github.com/docker/docker/engine"
+ "github.com/docker/docker/cliconfig"
"github.com/docker/docker/image"
- "github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/progressreader"
+ "github.com/docker/docker/pkg/streamformatter"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/pkg/transport"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
-func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 && n != 2 {
- return job.Errorf("Usage: %s IMAGE [TAG|DIGEST]", job.Name)
- }
+type ImagePullConfig struct {
+ MetaHeaders map[string][]string
+ AuthConfig *cliconfig.AuthConfig
+ OutStream io.Writer
+}
+func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error {
var (
- localName = job.Args[0]
- tag string
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
- authConfig = ®istry.AuthConfig{}
- metaHeaders map[string][]string
+ sf = streamformatter.NewJSONStreamFormatter()
)
// Resolve the Repository name from fqn to RepositoryInfo
- repoInfo, err := registry.ResolveRepositoryInfo(job, localName)
+ repoInfo, err := s.registryService.ResolveRepository(image)
if err != nil {
- return job.Error(err)
+ return err
}
- if len(job.Args) > 1 {
- tag = job.Args[1]
+ if err := validateRepoName(repoInfo.LocalName); err != nil {
+ return err
}
- job.GetenvJson("authConfig", authConfig)
- job.GetenvJson("metaHeaders", &metaHeaders)
-
c, err := s.poolAdd("pull", utils.ImageReference(repoInfo.LocalName, tag))
if err != nil {
if c != nil {
// Another pull of the same repository is already taking place; just wait for it to finish
- job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName))
+ imagePullConfig.OutStream.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName))
<-c
- return engine.StatusOK
+ return nil
}
- return job.Error(err)
+ return err
}
defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag))
- log.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
- endpoint, err := repoInfo.GetEndpoint()
- if err != nil {
- return job.Error(err)
- }
-
- r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true)
- if err != nil {
- return job.Error(err)
- }
-
logName := repoInfo.LocalName
if tag != "" {
logName = utils.ImageReference(logName, tag)
}
+ // Attempt pulling official content from a provided v2 mirror
+ if repoInfo.Index.Official {
+ v2mirrorEndpoint, v2mirrorRepoInfo, err := configureV2Mirror(repoInfo, s.registryService)
+ if err != nil {
+ logrus.Errorf("Error configuring mirrors: %s", err)
+ return err
+ }
+
+ if v2mirrorEndpoint != nil {
+ logrus.Debugf("Attempting to pull from v2 mirror: %s", v2mirrorEndpoint.URL)
+ return s.pullFromV2Mirror(v2mirrorEndpoint, v2mirrorRepoInfo, imagePullConfig, tag, sf, logName)
+ }
+ }
+
+ logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
+
+ endpoint, err := repoInfo.GetEndpoint(imagePullConfig.MetaHeaders)
+ if err != nil {
+ return err
+ }
+ // TODO(tiborvass): reuse client from endpoint?
+ // Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+ tr := transport.NewTransport(
+ registry.NewTransport(registry.ReceiveTimeout, endpoint.IsSecure),
+ registry.DockerHeaders(imagePullConfig.MetaHeaders)...,
+ )
+ client := registry.HTTPClient(tr)
+ r, err := registry.NewSession(client, imagePullConfig.AuthConfig, endpoint)
+ if err != nil {
+ return err
+ }
+
if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Index.Official || endpoint.Version == registry.APIVersion2) {
if repoInfo.Official {
- j := job.Eng.Job("trust_update_base")
- if err = j.Run(); err != nil {
- log.Errorf("error updating trust base graph: %s", err)
- }
+ s.trustService.UpdateBase()
}
- log.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName)
- if err := s.pullV2Repository(job.Eng, r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err == nil {
- if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil {
- log.Errorf("Error logging event 'pull' for %s: %s", logName, err)
- }
- return engine.StatusOK
+ logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName)
+ if err := s.pullV2Repository(r, imagePullConfig.OutStream, repoInfo, tag, sf); err == nil {
+ s.eventsService.Log("pull", logName, "")
+ return nil
} else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable {
- log.Errorf("Error from V2 registry: %s", err)
+ logrus.Errorf("Error from V2 registry: %s", err)
}
- log.Debug("image does not exist on v2 registry, falling back to v1")
+ logrus.Debug("image does not exist on v2 registry, falling back to v1")
}
- log.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName)
- if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil {
- return job.Error(err)
+ if utils.DigestReference(tag) {
+ return fmt.Errorf("pulling with digest reference failed from v2 registry")
}
- if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil {
- log.Errorf("Error logging event 'pull' for %s: %s", logName, err)
+ logrus.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName)
+ if err = s.pullRepository(r, imagePullConfig.OutStream, repoInfo, tag, sf); err != nil {
+ return err
}
- return engine.StatusOK
+ s.eventsService.Log("pull", logName, "")
+
+ return nil
+
}
-func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
+func makeMirrorRepoInfo(repoInfo *registry.RepositoryInfo, mirror string) *registry.RepositoryInfo {
+ mirrorRepo := ®istry.RepositoryInfo{
+ RemoteName: repoInfo.RemoteName,
+ LocalName: repoInfo.LocalName,
+ CanonicalName: repoInfo.CanonicalName,
+ Official: false,
+
+ Index: ®istry.IndexInfo{
+ Official: false,
+ Secure: repoInfo.Index.Secure,
+ Name: mirror,
+ Mirrors: []string{},
+ },
+ }
+ return mirrorRepo
+}
+
+func configureV2Mirror(repoInfo *registry.RepositoryInfo, s *registry.Service) (*registry.Endpoint, *registry.RepositoryInfo, error) {
+ mirrors := repoInfo.Index.Mirrors
+
+ if len(mirrors) == 0 {
+ // no mirrors configured
+ return nil, nil, nil
+ }
+
+ v1MirrorCount := 0
+ var v2MirrorEndpoint *registry.Endpoint
+ var v2MirrorRepoInfo *registry.RepositoryInfo
+ var lastErr error
+ for _, mirror := range mirrors {
+ mirrorRepoInfo := makeMirrorRepoInfo(repoInfo, mirror)
+ endpoint, err := registry.NewEndpoint(mirrorRepoInfo.Index, nil)
+ if err != nil {
+ logrus.Errorf("Unable to create endpoint for %s: %s", mirror, err)
+ lastErr = err
+ continue
+ }
+ if endpoint.Version == 2 {
+ if v2MirrorEndpoint == nil {
+ v2MirrorEndpoint = endpoint
+ v2MirrorRepoInfo = mirrorRepoInfo
+ } else {
+ // > 1 v2 mirrors given
+ return nil, nil, fmt.Errorf("multiple v2 mirrors configured")
+ }
+ } else {
+ v1MirrorCount++
+ }
+ }
+
+ if v1MirrorCount == len(mirrors) {
+ // OK, but mirrors are v1
+ return nil, nil, nil
+ }
+ if v2MirrorEndpoint != nil && v1MirrorCount == 0 {
+ // OK, 1 v2 mirror specified
+ return v2MirrorEndpoint, v2MirrorRepoInfo, nil
+ }
+ if v2MirrorEndpoint != nil && v1MirrorCount > 0 {
+ lastErr = fmt.Errorf("v1 and v2 mirrors configured")
+ }
+ return nil, nil, lastErr
+}
+
+func (s *TagStore) pullFromV2Mirror(mirrorEndpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo,
+ imagePullConfig *ImagePullConfig, tag string, sf *streamformatter.StreamFormatter, logName string) error {
+
+ tr := transport.NewTransport(
+ registry.NewTransport(registry.ReceiveTimeout, mirrorEndpoint.IsSecure),
+ registry.DockerHeaders(imagePullConfig.MetaHeaders)...,
+ )
+ client := registry.HTTPClient(tr)
+ mirrorSession, err := registry.NewSession(client, &cliconfig.AuthConfig{}, mirrorEndpoint)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Pulling v2 repository with local name %q from %s", repoInfo.LocalName, mirrorEndpoint.URL)
+ if err := s.pullV2Repository(mirrorSession, imagePullConfig.OutStream, repoInfo, tag, sf); err != nil {
+ return err
+ }
+ s.eventsService.Log("pull", logName, "")
+ return nil
+}
+
+func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *streamformatter.StreamFormatter) error {
out.Write(sf.FormatStatus("", "Pulling repository %s", repoInfo.CanonicalName))
repoData, err := r.GetRepositoryData(repoInfo.RemoteName)
@@ -119,10 +219,10 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
return err
}
- log.Debugf("Retrieving the tag list")
- tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName, repoData.Tokens)
+ logrus.Debugf("Retrieving the tag list")
+ tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName)
if err != nil {
- log.Errorf("unable to get remote tags: %s", err)
+ logrus.Errorf("unable to get remote tags: %s", err)
return err
}
@@ -134,7 +234,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
}
}
- log.Debugf("Registering tags")
+ logrus.Debugf("Registering tags")
// If no tag has been specified, pull them all
if askedTag == "" {
for tag, id := range tagsList {
@@ -151,108 +251,95 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
errors := make(chan error)
- layers_downloaded := false
+ layersDownloaded := false
for _, image := range repoData.ImgList {
downloadImage := func(img *registry.ImgData) {
if askedTag != "" && img.Tag != askedTag {
- if parallel {
- errors <- nil
- }
+ errors <- nil
return
}
if img.Tag == "" {
- log.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
- if parallel {
- errors <- nil
- }
+ logrus.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
+ errors <- nil
return
}
// ensure no two downloads of the same image happen at the same time
if c, err := s.poolAdd("pull", "img:"+img.ID); err != nil {
if c != nil {
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil))
<-c
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil))
} else {
- log.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
- }
- if parallel {
- errors <- nil
+ logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
}
+ errors <- nil
return
}
defer s.poolRemove("pull", "img:"+img.ID)
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, repoInfo.CanonicalName), nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, repoInfo.CanonicalName), nil))
success := false
var lastErr, err error
- var is_downloaded bool
+ var isDownloaded bool
for _, ep := range repoInfo.Index.Mirrors {
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
- if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
+ // Ensure endpoint is v1
+ ep = ep + "v1/"
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
+ if isDownloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
// Don't report errors when pulling from mirrors.
- log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err)
+ logrus.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err)
continue
}
- layers_downloaded = layers_downloaded || is_downloaded
+ layersDownloaded = layersDownloaded || isDownloaded
success = true
break
}
if !success {
for _, ep := range repoData.Endpoints {
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
- if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
+ if isDownloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
// It's not ideal that only the last error is returned, it would be better to concatenate the errors.
// As the error is also given to the output stream the user will see the error.
lastErr = err
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err), nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err), nil))
continue
}
- layers_downloaded = layers_downloaded || is_downloaded
+ layersDownloaded = layersDownloaded || isDownloaded
success = true
break
}
}
if !success {
err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, repoInfo.CanonicalName, lastErr)
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), err.Error(), nil))
- if parallel {
- errors <- err
- return
- }
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), err.Error(), nil))
+ errors <- err
+ return
}
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil))
- if parallel {
- errors <- nil
- }
+ errors <- nil
}
- if parallel {
- go downloadImage(image)
- } else {
- downloadImage(image)
+ go downloadImage(image)
+ }
+
+ var lastError error
+ for i := 0; i < len(repoData.ImgList); i++ {
+ if err := <-errors; err != nil {
+ lastError = err
}
}
- if parallel {
- var lastError error
- for i := 0; i < len(repoData.ImgList); i++ {
- if err := <-errors; err != nil {
- lastError = err
- }
- }
- if lastError != nil {
- return lastError
- }
-
+ if lastError != nil {
+ return lastError
}
+
for tag, id := range tagsList {
if askedTag != "" && tag != askedTag {
continue
}
- if err := s.Set(repoInfo.LocalName, tag, id, true); err != nil {
+ if err := s.Tag(repoInfo.LocalName, tag, id, true); err != nil {
return err
}
}
@@ -261,32 +348,32 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
if len(askedTag) > 0 {
requestedTag = utils.ImageReference(repoInfo.CanonicalName, askedTag)
}
- WriteStatus(requestedTag, out, sf, layers_downloaded)
+ WriteStatus(requestedTag, out, sf, layersDownloaded)
return nil
}
-func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) (bool, error) {
- history, err := r.GetRemoteHistory(imgID, endpoint, token)
+func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *streamformatter.StreamFormatter) (bool, error) {
+ history, err := r.GetRemoteHistory(imgID, endpoint)
if err != nil {
return false, err
}
- out.Write(sf.FormatProgress(common.TruncateID(imgID), "Pulling dependent layers", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(imgID), "Pulling dependent layers", nil))
// FIXME: Try to stream the images?
// FIXME: Launch the getRemoteImage() in goroutines
- layers_downloaded := false
+ layersDownloaded := false
for i := len(history) - 1; i >= 0; i-- {
id := history[i]
// ensure no two downloads of the same layer happen at the same time
if c, err := s.poolAdd("pull", "layer:"+id); err != nil {
- log.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err)
+ logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err)
<-c
}
defer s.poolRemove("pull", "layer:"+id)
if !s.graph.Exists(id) {
- out.Write(sf.FormatProgress(common.TruncateID(id), "Pulling metadata", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Pulling metadata", nil))
var (
imgJSON []byte
imgSize int
@@ -295,19 +382,19 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
)
retries := 5
for j := 1; j <= retries; j++ {
- imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)
+ imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint)
if err != nil && j == retries {
- out.Write(sf.FormatProgress(common.TruncateID(id), "Error pulling dependent layers", nil))
- return layers_downloaded, err
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
+ return layersDownloaded, err
} else if err != nil {
time.Sleep(time.Duration(j) * 500 * time.Millisecond)
continue
}
img, err = image.NewImgJSON(imgJSON)
- layers_downloaded = true
+ layersDownloaded = true
if err != nil && j == retries {
- out.Write(sf.FormatProgress(common.TruncateID(id), "Error pulling dependent layers", nil))
- return layers_downloaded, fmt.Errorf("Failed to parse json: %s", err)
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
+ return layersDownloaded, fmt.Errorf("Failed to parse json: %s", err)
} else if err != nil {
time.Sleep(time.Duration(j) * 500 * time.Millisecond)
continue
@@ -322,8 +409,8 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
if j > 1 {
status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
}
- out.Write(sf.FormatProgress(common.TruncateID(id), status, nil))
- layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize))
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), status, nil))
+ layer, err := r.GetRemoteImageLayer(img.ID, endpoint, int64(imgSize))
if uerr, ok := err.(*url.Error); ok {
err = uerr.Err
}
@@ -331,10 +418,10 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
time.Sleep(time.Duration(j) * 500 * time.Millisecond)
continue
} else if err != nil {
- out.Write(sf.FormatProgress(common.TruncateID(id), "Error pulling dependent layers", nil))
- return layers_downloaded, err
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
+ return layersDownloaded, err
}
- layers_downloaded = true
+ layersDownloaded = true
defer layer.Close()
err = s.graph.Register(img,
@@ -344,27 +431,27 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
Formatter: sf,
Size: imgSize,
NewLines: false,
- ID: common.TruncateID(id),
+ ID: stringid.TruncateID(id),
Action: "Downloading",
}))
if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
time.Sleep(time.Duration(j) * 500 * time.Millisecond)
continue
} else if err != nil {
- out.Write(sf.FormatProgress(common.TruncateID(id), "Error downloading dependent layers", nil))
- return layers_downloaded, err
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error downloading dependent layers", nil))
+ return layersDownloaded, err
} else {
break
}
}
}
- out.Write(sf.FormatProgress(common.TruncateID(id), "Download complete", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Download complete", nil))
}
- return layers_downloaded, nil
+ return layersDownloaded, nil
}
-func WriteStatus(requestedTag string, out io.Writer, sf *utils.StreamFormatter, layers_downloaded bool) {
- if layers_downloaded {
+func WriteStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layersDownloaded bool) {
+ if layersDownloaded {
out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag))
} else {
out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag))
@@ -382,11 +469,11 @@ type downloadInfo struct {
err chan error
}
-func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool) error {
+func (s *TagStore) pullV2Repository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter) error {
endpoint, err := r.V2RegistryEndpoint(repoInfo.Index)
if err != nil {
if repoInfo.Index.Official {
- log.Debugf("Unable to pull from V2 registry, falling back to v1: %s", err)
+ logrus.Debugf("Unable to pull from V2 registry, falling back to v1: %s", err)
return ErrV2RegistryUnavailable
}
return fmt.Errorf("error getting registry endpoint: %s", err)
@@ -397,7 +484,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out
}
var layersDownloaded bool
if tag == "" {
- log.Debugf("Pulling tag list from V2 registry for %s", repoInfo.CanonicalName)
+ logrus.Debugf("Pulling tag list from V2 registry for %s", repoInfo.CanonicalName)
tags, err := r.GetV2RemoteTags(endpoint, repoInfo.RemoteName, auth)
if err != nil {
return err
@@ -406,14 +493,14 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out
return registry.ErrDoesNotExist
}
for _, t := range tags {
- if downloaded, err := s.pullV2Tag(eng, r, out, endpoint, repoInfo, t, sf, parallel, auth); err != nil {
+ if downloaded, err := s.pullV2Tag(r, out, endpoint, repoInfo, t, sf, auth); err != nil {
return err
} else if downloaded {
layersDownloaded = true
}
}
} else {
- if downloaded, err := s.pullV2Tag(eng, r, out, endpoint, repoInfo, tag, sf, parallel, auth); err != nil {
+ if downloaded, err := s.pullV2Tag(r, out, endpoint, repoInfo, tag, sf, auth); err != nil {
return err
} else if downloaded {
layersDownloaded = true
@@ -428,8 +515,8 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out
return nil
}
-func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) {
- log.Debugf("Pulling tag from V2 registry: %q", tag)
+func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, auth *registry.RequestAuthorization) (bool, error) {
+ logrus.Debugf("Pulling tag from V2 registry: %q", tag)
manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth)
if err != nil {
@@ -438,7 +525,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
// loadManifest ensures that the manifest payload has the expected digest
// if the tag is a digest reference.
- manifest, verified, err := s.loadManifest(eng, manifestBytes, manifestDigest, tag)
+ manifest, verified, err := s.loadManifest(manifestBytes, manifestDigest, tag)
if err != nil {
return false, fmt.Errorf("error verifying manifest: %s", err)
}
@@ -448,7 +535,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
}
if verified {
- log.Printf("Image manifest for %s has been verified", utils.ImageReference(repoInfo.CanonicalName, tag))
+ logrus.Printf("Image manifest for %s has been verified", utils.ImageReference(repoInfo.CanonicalName, tag))
}
out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName))
@@ -468,7 +555,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
// Check if exists
if s.graph.Exists(img.ID) {
- log.Debugf("Image already exists: %s", img.ID)
+ logrus.Debugf("Image already exists: %s", img.ID)
continue
}
@@ -478,18 +565,18 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
}
downloads[i].digest = dgst
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Pulling fs layer", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil))
downloadFunc := func(di *downloadInfo) error {
- log.Debugf("pulling blob %q to V1 img %s", sumStr, img.ID)
+ logrus.Debugf("pulling blob %q to V1 img %s", sumStr, img.ID)
if c, err := s.poolAdd("pull", "img:"+img.ID); err != nil {
if c != nil {
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil))
<-c
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil))
} else {
- log.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
+ logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
}
} else {
defer s.poolRemove("pull", "img:"+img.ID)
@@ -498,7 +585,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
return err
}
- r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest.Algorithm(), di.digest.Hex(), auth)
+ r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest, auth)
if err != nil {
return err
}
@@ -515,22 +602,22 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
Formatter: sf,
Size: int(l),
NewLines: false,
- ID: common.TruncateID(img.ID),
+ ID: stringid.TruncateID(img.ID),
Action: "Downloading",
})); err != nil {
return fmt.Errorf("unable to copy v2 image blob data: %s", err)
}
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Verifying Checksum", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Verifying Checksum", nil))
if !verifier.Verified() {
- log.Infof("Image verification failed: checksum mismatch for %q", di.digest.String())
+ logrus.Infof("Image verification failed: checksum mismatch for %q", di.digest.String())
verified = false
}
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil))
- log.Debugf("Downloaded %s to tempfile %s", img.ID, tmpFile.Name())
+ logrus.Debugf("Downloaded %s to tempfile %s", img.ID, tmpFile.Name())
di.tmpFile = tmpFile
di.length = l
di.downloaded = true
@@ -540,25 +627,17 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
return nil
}
- if parallel {
- downloads[i].err = make(chan error)
- go func(di *downloadInfo) {
- di.err <- downloadFunc(di)
- }(&downloads[i])
- } else {
- err := downloadFunc(&downloads[i])
- if err != nil {
- return false, err
- }
- }
+ downloads[i].err = make(chan error)
+ go func(di *downloadInfo) {
+ di.err <- downloadFunc(di)
+ }(&downloads[i])
}
var tagUpdated bool
for i := len(downloads) - 1; i >= 0; i-- {
d := &downloads[i]
if d.err != nil {
- err := <-d.err
- if err != nil {
+ if err := <-d.err; err != nil {
return false, err
}
}
@@ -574,7 +653,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
Out: out,
Formatter: sf,
Size: int(d.length),
- ID: common.TruncateID(d.img.ID),
+ ID: stringid.TruncateID(d.img.ID),
Action: "Extracting",
}))
if err != nil {
@@ -583,10 +662,10 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
// FIXME: Pool release here for parallel tag pull (ensures any downloads block until fully extracted)
}
- out.Write(sf.FormatProgress(common.TruncateID(d.img.ID), "Pull complete", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(d.img.ID), "Pull complete", nil))
tagUpdated = true
} else {
- out.Write(sf.FormatProgress(common.TruncateID(d.img.ID), "Already exists", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(d.img.ID), "Already exists", nil))
}
}
@@ -601,6 +680,8 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
if _, exists := repo[tag]; !exists {
tagUpdated = true
}
+ } else {
+ tagUpdated = true
}
}
@@ -618,7 +699,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
}
} else {
// only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest)
- if err = s.Set(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
+ if err = s.Tag(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
return false, err
}
}
diff --git a/graph/push.go b/graph/push.go
index 08c383b44..817ef707f 100644
--- a/graph/push.go
+++ b/graph/push.go
@@ -7,15 +7,18 @@ import (
"io"
"io/ioutil"
"os"
- "path"
- "strings"
+ "path/filepath"
"sync"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/distribution/digest"
+ "github.com/docker/docker/cliconfig"
"github.com/docker/docker/image"
- "github.com/docker/docker/pkg/common"
+ "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progressreader"
+ "github.com/docker/docker/pkg/streamformatter"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/pkg/transport"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
@@ -24,6 +27,13 @@ import (
var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable")
+type ImagePushConfig struct {
+ MetaHeaders map[string][]string
+ AuthConfig *cliconfig.AuthConfig
+ Tag string
+ OutStream io.Writer
+}
+
// Retrieve the all the images to be uploaded in the correct order
func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) {
var (
@@ -72,14 +82,14 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string
if len(imageList) == 0 {
return nil, nil, fmt.Errorf("No images found for the requested repository / tag")
}
- log.Debugf("Image list: %v", imageList)
- log.Debugf("Tags by image: %v", tagsByImage)
+ logrus.Debugf("Image list: %v", imageList)
+ logrus.Debugf("Tags by image: %v", tagsByImage)
return imageList, tagsByImage, nil
}
func (s *TagStore) getImageTags(localRepo map[string]string, askedTag string) ([]string, error) {
- log.Debugf("Checking %s against %#v", askedTag, localRepo)
+ logrus.Debugf("Checking %s against %#v", askedTag, localRepo)
if len(askedTag) > 0 {
if _, ok := localRepo[askedTag]; !ok || utils.DigestReference(askedTag) {
return nil, fmt.Errorf("Tag does not exist: %s", askedTag)
@@ -128,21 +138,21 @@ type imagePushData struct {
// lookupImageOnEndpoint checks the specified endpoint to see if an image exists
// and if it is absent then it sends the image id to the channel to be pushed.
-func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Writer, sf *utils.StreamFormatter,
+func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Writer, sf *streamformatter.StreamFormatter,
images chan imagePushData, imagesToPush chan string) {
defer wg.Done()
for image := range images {
- if err := r.LookupRemoteImage(image.id, image.endpoint, image.tokens); err != nil {
- log.Errorf("Error in LookupRemoteImage: %s", err)
+ if err := r.LookupRemoteImage(image.id, image.endpoint); err != nil {
+ logrus.Errorf("Error in LookupRemoteImage: %s", err)
imagesToPush <- image.id
continue
}
- out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", common.TruncateID(image.id)))
+ out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", stringid.TruncateID(image.id)))
}
}
func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteName string, imageIDs []string,
- tags map[string][]string, repo *registry.RepositoryData, sf *utils.StreamFormatter, r *registry.Session) error {
+ tags map[string][]string, repo *registry.RepositoryData, sf *streamformatter.StreamFormatter, r *registry.Session) error {
workerCount := len(imageIDs)
// start a maximum of 5 workers to check if images exist on the specified endpoint.
if workerCount > 5 {
@@ -189,8 +199,8 @@ func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteNam
}
}
for _, tag := range tags[id] {
- out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", common.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag))
- if err := r.PushRegistryTag(remoteName, id, tag, endpoint, repo.Tokens); err != nil {
+ out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag))
+ if err := r.PushRegistryTag(remoteName, id, tag, endpoint); err != nil {
return err
}
}
@@ -201,9 +211,9 @@ func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteNam
// pushRepository pushes layers that do not already exist on the registry.
func (s *TagStore) pushRepository(r *registry.Session, out io.Writer,
repoInfo *registry.RepositoryInfo, localRepo map[string]string,
- tag string, sf *utils.StreamFormatter) error {
- log.Debugf("Local repo: %s", localRepo)
- out = utils.NewWriteFlusher(out)
+ tag string, sf *streamformatter.StreamFormatter) error {
+ logrus.Debugf("Local repo: %s", localRepo)
+ out = ioutils.NewWriteFlusher(out)
imgList, tags, err := s.getImageList(localRepo, tag)
if err != nil {
return err
@@ -211,9 +221,9 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer,
out.Write(sf.FormatStatus("", "Sending image list"))
imageIndex := s.createImageIndex(imgList, tags)
- log.Debugf("Preparing to push %s with the following images and tags", localRepo)
+ logrus.Debugf("Preparing to push %s with the following images and tags", localRepo)
for _, data := range imageIndex {
- log.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag)
+ logrus.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag)
}
// Register all the images in a repository with the registry
// If an image is not in this list it will not be associated with the repository
@@ -236,22 +246,22 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer,
return err
}
-func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
- out = utils.NewWriteFlusher(out)
- jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json"))
+func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *streamformatter.StreamFormatter) (checksum string, err error) {
+ out = ioutils.NewWriteFlusher(out)
+ jsonRaw, err := ioutil.ReadFile(filepath.Join(s.graph.Root, imgID, "json"))
if err != nil {
return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
}
- out.Write(sf.FormatProgress(common.TruncateID(imgID), "Pushing", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(imgID), "Pushing", nil))
imgData := ®istry.ImgData{
ID: imgID,
}
// Send the json
- if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
+ if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep); err != nil {
if err == registry.ErrAlreadyExists {
- out.Write(sf.FormatProgress(common.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
return "", nil
}
return "", err
@@ -264,7 +274,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin
defer os.RemoveAll(layerData.Name())
// Send the layer
- log.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
+ logrus.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID,
progressreader.New(progressreader.Config{
@@ -273,28 +283,28 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin
Formatter: sf,
Size: int(layerData.Size),
NewLines: false,
- ID: common.TruncateID(imgData.ID),
+ ID: stringid.TruncateID(imgData.ID),
Action: "Pushing",
- }), ep, token, jsonRaw)
+ }), ep, jsonRaw)
if err != nil {
return "", err
}
imgData.Checksum = checksum
imgData.ChecksumPayload = checksumPayload
// Send the checksum
- if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
+ if err := r.PushImageChecksumRegistry(imgData, ep); err != nil {
return "", err
}
- out.Write(sf.FormatProgress(common.TruncateID(imgData.ID), "Image successfully pushed", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image successfully pushed", nil))
return imgData.Checksum, nil
}
-func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter) error {
+func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter) error {
endpoint, err := r.V2RegistryEndpoint(repoInfo.Index)
if err != nil {
if repoInfo.Index.Official {
- log.Debugf("Unable to push to V2 registry, falling back to v1: %s", err)
+ logrus.Debugf("Unable to push to V2 registry, falling back to v1: %s", err)
return ErrV2RegistryUnavailable
}
return fmt.Errorf("error getting registry endpoint: %s", err)
@@ -314,7 +324,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
}
for _, tag := range tags {
- log.Debugf("Pushing repository: %s:%s", repoInfo.CanonicalName, tag)
+ logrus.Debugf("Pushing repository: %s:%s", repoInfo.CanonicalName, tag)
layerId, exists := localRepo[tag]
if !exists {
@@ -355,11 +365,10 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
// Schema version 1 requires layer ordering from top to root
for i, layer := range layers {
- log.Debugf("Pushing layer: %s", layer.ID)
+ logrus.Debugf("Pushing layer: %s", layer.ID)
if layer.Config != nil && metadata.Image != layer.ID {
- err = runconfig.Merge(&metadata, layer.Config)
- if err != nil {
+ if err := runconfig.Merge(&metadata, layer.Config); err != nil {
return err
}
}
@@ -375,15 +384,15 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
var exists bool
if len(checksum) > 0 {
- sumParts := strings.SplitN(checksum, ":", 2)
- if len(sumParts) < 2 {
- return fmt.Errorf("Invalid checksum: %s", checksum)
+ dgst, err := digest.ParseDigest(checksum)
+ if err != nil {
+ return fmt.Errorf("Invalid checksum %s: %s", checksum, err)
}
// Call mount blob
- exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], sumParts[1], auth)
+ exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, dgst, auth)
if err != nil {
- out.Write(sf.FormatProgress(common.TruncateID(layer.ID), "Image push failed", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil))
return err
}
}
@@ -398,7 +407,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
checksum = cs
}
} else {
- out.Write(sf.FormatProgress(common.TruncateID(layer.ID), "Image already exists", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image already exists", nil))
}
m.FSLayers[i] = ®istry.FSLayer{BlobSum: checksum}
m.History[i] = ®istry.ManifestHistory{V1Compatibility: string(jsonData)}
@@ -408,7 +417,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
return fmt.Errorf("invalid manifest: %s", err)
}
- log.Debugf("Pushing %s:%s to v2 repository", repoInfo.LocalName, tag)
+ logrus.Debugf("Pushing %s:%s to v2 repository", repoInfo.LocalName, tag)
mBytes, err := json.MarshalIndent(m, "", " ")
if err != nil {
return err
@@ -426,7 +435,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
if err != nil {
return err
}
- log.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID())
+ logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID())
// push the manifest
digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, signedBody, mBytes, auth)
@@ -440,8 +449,8 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
}
// PushV2Image pushes the image content to the v2 registry, first buffering the contents to disk
-func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName string, sf *utils.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (string, error) {
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Buffering to Disk", nil))
+func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName string, sf *streamformatter.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (string, error) {
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil))
image, err := s.graph.Get(img.ID)
if err != nil {
@@ -465,87 +474,87 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *
size, dgst, err := bufferToFile(tf, arch)
// Send the layer
- log.Debugf("rendered layer for %s of [%d] size", img.ID, size)
+ logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size)
- if err := r.PutV2ImageBlob(endpoint, imageName, dgst.Algorithm(), dgst.Hex(),
+ if err := r.PutV2ImageBlob(endpoint, imageName, dgst,
progressreader.New(progressreader.Config{
In: tf,
Out: out,
Formatter: sf,
Size: int(size),
NewLines: false,
- ID: common.TruncateID(img.ID),
+ ID: stringid.TruncateID(img.ID),
Action: "Pushing",
}), auth); err != nil {
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Image push failed", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Image push failed", nil))
return "", err
}
- out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Image successfully pushed", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Image successfully pushed", nil))
return dgst.String(), nil
}
// FIXME: Allow to interrupt current push when new push of same image is done.
-func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.Errorf("Usage: %s IMAGE", job.Name)
- }
+func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error {
var (
- localName = job.Args[0]
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
- authConfig = ®istry.AuthConfig{}
- metaHeaders map[string][]string
+ sf = streamformatter.NewJSONStreamFormatter()
)
// Resolve the Repository name from fqn to RepositoryInfo
- repoInfo, err := registry.ResolveRepositoryInfo(job, localName)
+ repoInfo, err := s.registryService.ResolveRepository(localName)
if err != nil {
- return job.Error(err)
+ return err
}
- tag := job.Getenv("tag")
- job.GetenvJson("authConfig", authConfig)
- job.GetenvJson("metaHeaders", &metaHeaders)
-
if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil {
- return job.Error(err)
+ return err
}
defer s.poolRemove("push", repoInfo.LocalName)
- endpoint, err := repoInfo.GetEndpoint()
+ endpoint, err := repoInfo.GetEndpoint(imagePushConfig.MetaHeaders)
if err != nil {
- return job.Error(err)
+ return err
}
-
- r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
+ // TODO(tiborvass): reuse client from endpoint?
+ // Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+ tr := transport.NewTransport(
+ registry.NewTransport(registry.NoTimeout, endpoint.IsSecure),
+ registry.DockerHeaders(imagePushConfig.MetaHeaders)...,
+ )
+ client := registry.HTTPClient(tr)
+ r, err := registry.NewSession(client, imagePushConfig.AuthConfig, endpoint)
if err != nil {
- return job.Error(err)
+ return err
}
reposLen := 1
- if tag == "" {
+ if imagePushConfig.Tag == "" {
reposLen = len(s.Repositories[repoInfo.LocalName])
}
- job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen))
+
+ imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen))
+
// If it fails, try to get the repository
localRepo, exists := s.Repositories[repoInfo.LocalName]
if !exists {
- return job.Errorf("Repository does not exist: %s", repoInfo.LocalName)
+ return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName)
}
if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 {
- err := s.pushV2Repository(r, localRepo, job.Stdout, repoInfo, tag, sf)
+ err := s.pushV2Repository(r, localRepo, imagePushConfig.OutStream, repoInfo, imagePushConfig.Tag, sf)
if err == nil {
- return engine.StatusOK
+ s.eventsService.Log("push", repoInfo.LocalName, "")
+ return nil
}
if err != ErrV2RegistryUnavailable {
- return job.Errorf("Error pushing to registry: %s", err)
+ return fmt.Errorf("Error pushing to registry: %s", err)
}
}
- if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil {
- return job.Error(err)
+ if err := s.pushRepository(r, imagePushConfig.OutStream, repoInfo, localRepo, imagePushConfig.Tag, sf); err != nil {
+ return err
}
- return engine.StatusOK
+ s.eventsService.Log("push", repoInfo.LocalName, "")
+ return nil
}
diff --git a/graph/service.go b/graph/service.go
index 350ed8cf9..52dde1d98 100644
--- a/graph/service.go
+++ b/graph/service.go
@@ -4,178 +4,65 @@ import (
"fmt"
"io"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
- "github.com/docker/docker/image"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/api/types"
)
-func (s *TagStore) Install(eng *engine.Engine) error {
- for name, handler := range map[string]engine.Handler{
- "image_set": s.CmdSet,
- "tag": s.CmdTag,
- "image_get": s.CmdGet,
- "image_inspect": s.CmdLookup,
- "image_tarlayer": s.CmdTarLayer,
- "image_export": s.CmdImageExport,
- "history": s.CmdHistory,
- "images": s.CmdImages,
- "viz": s.CmdViz,
- "load": s.CmdLoad,
- "import": s.CmdImport,
- "pull": s.CmdPull,
- "push": s.CmdPush,
- } {
- if err := eng.Register(name, handler); err != nil {
- return fmt.Errorf("Could not register %q: %v", name, err)
- }
+func (s *TagStore) LookupRaw(name string) ([]byte, error) {
+ image, err := s.LookupImage(name)
+ if err != nil || image == nil {
+ return nil, fmt.Errorf("No such image %s", name)
}
- return nil
-}
-// CmdSet stores a new image in the graph.
-// Images are stored in the graph using 4 elements:
-// - A user-defined ID
-// - A collection of metadata describing the image
-// - A directory tree stored as a tar archive (also called the "layer")
-// - A reference to a "parent" ID on top of which the layer should be applied
-//
-// NOTE: even though the parent ID is only useful in relation to the layer and how
-// to apply it (ie you could represent the full directory tree as 'parent_layer + layer',
-// it is treated as a top-level property of the image. This is an artifact of early
-// design and should probably be cleaned up in the future to simplify the design.
-//
-// Syntax: image_set ID
-// Input:
-// - Layer content must be streamed in tar format on stdin. An empty input is
-// valid and represents a nil layer.
-//
-// - Image metadata must be passed in the command environment.
-// 'json': a json-encoded object with all image metadata.
-// It will be stored as-is, without any encoding/decoding artifacts.
-// That is a requirement of the current registry client implementation,
-// because a re-encoded json might invalidate the image checksum at
-// the next upload, even with functionaly identical content.
-func (s *TagStore) CmdSet(job *engine.Job) engine.Status {
- if len(job.Args) != 1 {
- return job.Errorf("usage: %s NAME", job.Name)
- }
- var (
- imgJSON = []byte(job.Getenv("json"))
- layer = job.Stdin
- )
- if len(imgJSON) == 0 {
- return job.Errorf("mandatory key 'json' is not set")
- }
- // We have to pass an *image.Image object, even though it will be completely
- // ignored in favor of the redundant json data.
- // FIXME: the current prototype of Graph.Register is stupid and redundant.
- img, err := image.NewImgJSON(imgJSON)
+ imageInspectRaw, err := image.RawJson()
if err != nil {
- return job.Error(err)
+ return nil, err
}
- if err := s.graph.Register(img, layer); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
+
+ return imageInspectRaw, nil
}
-// CmdGet returns information about an image.
-// If the image doesn't exist, an empty object is returned, to allow
-// checking for an image's existence.
-func (s *TagStore) CmdGet(job *engine.Job) engine.Status {
- if len(job.Args) != 1 {
- return job.Errorf("usage: %s NAME", job.Name)
+// Lookup return an image encoded in JSON
+func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) {
+ image, err := s.LookupImage(name)
+ if err != nil || image == nil {
+ return nil, fmt.Errorf("No such image: %s", name)
}
- name := job.Args[0]
- res := &engine.Env{}
- img, err := s.LookupImage(name)
- // Note: if the image doesn't exist, LookupImage returns
- // nil, nil.
- if err != nil {
- return job.Error(err)
+
+ imageInspect := &types.ImageInspect{
+ Id: image.ID,
+ Parent: image.Parent,
+ Comment: image.Comment,
+ Created: image.Created,
+ Container: image.Container,
+ ContainerConfig: &image.ContainerConfig,
+ DockerVersion: image.DockerVersion,
+ Author: image.Author,
+ Config: image.Config,
+ Architecture: image.Architecture,
+ Os: image.OS,
+ Size: image.Size,
+ VirtualSize: image.GetParentsSize(0) + image.Size,
}
- if img != nil {
- // We don't directly expose all fields of the Image objects,
- // to maintain a clean public API which we can maintain over
- // time even if the underlying structure changes.
- // We should have done this with the Image object to begin with...
- // but we didn't, so now we're doing it here.
- //
- // Fields that we're probably better off not including:
- // - Config/ContainerConfig. Those structs have the same sprawl problem,
- // so we shouldn't include them wholesale either.
- // - Comment: initially created to fulfill the "every image is a git commit"
- // metaphor, in practice people either ignore it or use it as a
- // generic description field which it isn't. On deprecation shortlist.
- res.SetAuto("Created", img.Created)
- res.SetJson("Author", img.Author)
- res.Set("Os", img.OS)
- res.Set("Architecture", img.Architecture)
- res.Set("DockerVersion", img.DockerVersion)
- res.SetJson("Id", img.ID)
- res.SetJson("Parent", img.Parent)
- }
- res.WriteTo(job.Stdout)
- return engine.StatusOK
+
+ return imageInspect, nil
}
-// CmdLookup return an image encoded in JSON
-func (s *TagStore) CmdLookup(job *engine.Job) engine.Status {
- if len(job.Args) != 1 {
- return job.Errorf("usage: %s NAME", job.Name)
- }
- name := job.Args[0]
- if image, err := s.LookupImage(name); err == nil && image != nil {
- if job.GetenvBool("raw") {
- b, err := image.RawJson()
- if err != nil {
- return job.Error(err)
- }
- job.Stdout.Write(b)
- return engine.StatusOK
- }
-
- out := &engine.Env{}
- out.SetJson("Id", image.ID)
- out.SetJson("Parent", image.Parent)
- out.SetJson("Comment", image.Comment)
- out.SetAuto("Created", image.Created)
- out.SetJson("Container", image.Container)
- out.SetJson("ContainerConfig", image.ContainerConfig)
- out.Set("DockerVersion", image.DockerVersion)
- out.SetJson("Author", image.Author)
- out.SetJson("Config", image.Config)
- out.Set("Architecture", image.Architecture)
- out.Set("Os", image.OS)
- out.SetInt64("Size", image.Size)
- out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
- if _, err = out.WriteTo(job.Stdout); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
- }
- return job.Errorf("No such image: %s", name)
-}
-
-// CmdTarLayer return the tarLayer of the image
-func (s *TagStore) CmdTarLayer(job *engine.Job) engine.Status {
- if len(job.Args) != 1 {
- return job.Errorf("usage: %s NAME", job.Name)
- }
- name := job.Args[0]
+// ImageTarLayer return the tarLayer of the image
+func (s *TagStore) ImageTarLayer(name string, dest io.Writer) error {
if image, err := s.LookupImage(name); err == nil && image != nil {
fs, err := image.TarLayer()
if err != nil {
- return job.Error(err)
+ return err
}
defer fs.Close()
- written, err := io.Copy(job.Stdout, fs)
+ written, err := io.Copy(dest, fs)
if err != nil {
- return job.Error(err)
+ return err
}
- log.Debugf("rendered layer for %s of [%d] size", image.ID, written)
- return engine.StatusOK
+ logrus.Debugf("rendered layer for %s of [%d] size", image.ID, written)
+ return nil
}
- return job.Errorf("No such image: %s", name)
+ return fmt.Errorf("No such image: %s", name)
}
diff --git a/graph/tag.go b/graph/tag.go
deleted file mode 100644
index b33e49d59..000000000
--- a/graph/tag.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package graph
-
-import (
- "github.com/docker/docker/engine"
-)
-
-func (s *TagStore) CmdTag(job *engine.Job) engine.Status {
- if len(job.Args) != 2 && len(job.Args) != 3 {
- return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name)
- }
- var tag string
- if len(job.Args) == 3 {
- tag = job.Args[2]
- }
- if err := s.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
-}
diff --git a/graph/tags.go b/graph/tags.go
index 5d26b8cfb..166a3d733 100644
--- a/graph/tags.go
+++ b/graph/tags.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "io"
"io/ioutil"
"os"
"path/filepath"
@@ -12,10 +13,13 @@ import (
"strings"
"sync"
+ "github.com/docker/docker/daemon/events"
+ "github.com/docker/docker/graph/tags"
"github.com/docker/docker/image"
- "github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/parsers"
+ "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/registry"
+ "github.com/docker/docker/trust"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)
@@ -23,9 +27,8 @@ import (
const DEFAULTTAG = "latest"
var (
- //FIXME these 2 regexes also exist in registry/v2/regexp.go
- validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
- validDigest = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
+ //FIXME this regex also exists in registry/v2/regexp.go
+ validDigest = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
)
type TagStore struct {
@@ -36,8 +39,11 @@ type TagStore struct {
sync.Mutex
// FIXME: move push/pull-related fields
// to a helper type
- pullingPool map[string]chan struct{}
- pushingPool map[string]chan struct{}
+ pullingPool map[string]chan struct{}
+ pushingPool map[string]chan struct{}
+ registryService *registry.Service
+ eventsService *events.Events
+ trustService *trust.TrustStore
}
type Repository map[string]string
@@ -60,19 +66,30 @@ func (r Repository) Contains(u Repository) bool {
return true
}
-func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey) (*TagStore, error) {
+type TagStoreConfig struct {
+ Graph *Graph
+ Key libtrust.PrivateKey
+ Registry *registry.Service
+ Events *events.Events
+ Trust *trust.TrustStore
+}
+
+func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) {
abspath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
store := &TagStore{
- path: abspath,
- graph: graph,
- trustKey: key,
- Repositories: make(map[string]Repository),
- pullingPool: make(map[string]chan struct{}),
- pushingPool: make(map[string]chan struct{}),
+ path: abspath,
+ graph: cfg.Graph,
+ trustKey: cfg.Key,
+ Repositories: make(map[string]Repository),
+ pullingPool: make(map[string]chan struct{}),
+ pushingPool: make(map[string]chan struct{}),
+ registryService: cfg.Registry,
+ eventsService: cfg.Events,
+ trustService: cfg.Trust,
}
// Load the json file if it exists, otherwise create it.
if err := store.reload(); os.IsNotExist(err) {
@@ -98,11 +115,12 @@ func (store *TagStore) save() error {
}
func (store *TagStore) reload() error {
- jsonData, err := ioutil.ReadFile(store.path)
+ f, err := os.Open(store.path)
if err != nil {
return err
}
- if err := json.Unmarshal(jsonData, store); err != nil {
+ defer f.Close()
+ if err := json.NewDecoder(f).Decode(&store); err != nil {
return err
}
return nil
@@ -163,7 +181,7 @@ func (store *TagStore) ImageName(id string) string {
if names, exists := store.ByID()[id]; exists && len(names) > 0 {
return names[0]
}
- return common.TruncateID(id)
+ return stringid.TruncateID(id)
}
func (store *TagStore) DeleteAll(id string) error {
@@ -218,7 +236,11 @@ func (store *TagStore) Delete(repoName, ref string) (bool, error) {
return deleted, store.save()
}
-func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
+func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error {
+ return store.SetLoad(repoName, tag, imageName, force, nil)
+}
+
+func (store *TagStore) SetLoad(repoName, tag, imageName string, force bool, out io.Writer) error {
img, err := store.LookupImage(imageName)
store.Lock()
defer store.Unlock()
@@ -226,12 +248,12 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
return err
}
if tag == "" {
- tag = DEFAULTTAG
+ tag = tags.DEFAULTTAG
}
if err := validateRepoName(repoName); err != nil {
return err
}
- if err := ValidateTagName(tag); err != nil {
+ if err := tags.ValidateTagName(tag); err != nil {
return err
}
if err := store.reload(); err != nil {
@@ -241,8 +263,17 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists {
repo = r
- if old, exists := store.Repositories[repoName][tag]; exists && !force {
- return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old)
+ if old, exists := store.Repositories[repoName][tag]; exists {
+
+ if !force {
+ return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old)
+ }
+
+ if old != img.ID && out != nil {
+
+ fmt.Fprintf(out, "The image %s:%s already exists, renaming the old one with ID %s to empty string\n", repoName, tag, old[:12])
+
+ }
}
} else {
repo = make(map[string]string)
@@ -316,9 +347,12 @@ func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error)
}
// If no matching tag is found, search through images for a matching image id
- for _, revision := range repo {
- if strings.HasPrefix(revision, refOrID) {
- return store.graph.Get(revision)
+ // iff it looks like a short ID or would look like a short ID
+ if stringid.IsShortID(stringid.TruncateID(refOrID)) {
+ for _, revision := range repo {
+ if strings.HasPrefix(revision, refOrID) {
+ return store.graph.Get(revision)
+ }
}
}
@@ -331,7 +365,7 @@ func (store *TagStore) GetRepoRefs() map[string][]string {
for name, repository := range store.Repositories {
for tag, id := range repository {
- shortID := common.TruncateID(id)
+ shortID := stringid.TruncateID(id)
reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag))
}
}
@@ -350,17 +384,6 @@ func validateRepoName(name string) error {
return nil
}
-// ValidateTagName validates the name of a tag
-func ValidateTagName(name string) error {
- if name == "" {
- return fmt.Errorf("tag name can't be empty")
- }
- if !validTagName.MatchString(name) {
- return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name)
- }
- return nil
-}
-
func validateDigest(dgst string) error {
if dgst == "" {
return errors.New("digest can't be empty")
diff --git a/graph/tags/tags.go b/graph/tags/tags.go
new file mode 100644
index 000000000..1abb593db
--- /dev/null
+++ b/graph/tags/tags.go
@@ -0,0 +1,24 @@
+package tags
+
+import (
+ "fmt"
+ "regexp"
+)
+
+const DEFAULTTAG = "latest"
+
+var (
+ //FIXME this regex also exists in registry/v2/regexp.go
+ validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
+)
+
+// ValidateTagName validates the name of a tag
+func ValidateTagName(name string) error {
+ if name == "" {
+ return fmt.Errorf("tag name can't be empty")
+ }
+ if !validTagName.MatchString(name) {
+ return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name)
+ }
+ return nil
+}
diff --git a/graph/tags/tags_unit_test.go b/graph/tags/tags_unit_test.go
new file mode 100644
index 000000000..5114da107
--- /dev/null
+++ b/graph/tags/tags_unit_test.go
@@ -0,0 +1,23 @@
+package tags
+
+import (
+ "testing"
+)
+
+func TestValidTagName(t *testing.T) {
+ validTags := []string{"9", "foo", "foo-test", "bar.baz.boo"}
+ for _, tag := range validTags {
+ if err := ValidateTagName(tag); err != nil {
+ t.Errorf("'%s' should've been a valid tag", tag)
+ }
+ }
+}
+
+func TestInvalidTagName(t *testing.T) {
+ validTags := []string{"-9", ".foo", "-test", ".", "-"}
+ for _, tag := range validTags {
+ if err := ValidateTagName(tag); err == nil {
+ t.Errorf("'%s' shouldn't have been a valid tag", tag)
+ }
+ }
+}
diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go
index c1a686bbc..d1ddc6761 100644
--- a/graph/tags_unit_test.go
+++ b/graph/tags_unit_test.go
@@ -1,17 +1,18 @@
package graph
import (
+ "archive/tar"
"bytes"
"io"
"os"
"path"
"testing"
+ "github.com/docker/docker/daemon/events"
"github.com/docker/docker/daemon/graphdriver"
_ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests
"github.com/docker/docker/image"
"github.com/docker/docker/utils"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
const (
@@ -59,7 +60,11 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err != nil {
t.Fatal(err)
}
- store, err := NewTagStore(path.Join(root, "tags"), graph, nil)
+ tagCfg := &TagStoreConfig{
+ Graph: graph,
+ Events: events.New(),
+ }
+ store, err := NewTagStore(path.Join(root, "tags"), tagCfg)
if err != nil {
t.Fatal(err)
}
@@ -71,7 +76,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err := graph.Register(img, officialArchive); err != nil {
t.Fatal(err)
}
- if err := store.Set(testOfficialImageName, "", testOfficialImageID, false); err != nil {
+ if err := store.Tag(testOfficialImageName, "", testOfficialImageID, false); err != nil {
t.Fatal(err)
}
privateArchive, err := fakeTar()
@@ -82,7 +87,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err := graph.Register(img, privateArchive); err != nil {
t.Fatal(err)
}
- if err := store.Set(testPrivateImageName, "", testPrivateImageID, false); err != nil {
+ if err := store.Tag(testPrivateImageName, "", testPrivateImageID, false); err != nil {
t.Fatal(err)
}
if err := store.SetDigest(testPrivateImageName, testPrivateImageDigest, testPrivateImageID); err != nil {
@@ -176,24 +181,6 @@ func TestLookupImage(t *testing.T) {
}
}
-func TestValidTagName(t *testing.T) {
- validTags := []string{"9", "foo", "foo-test", "bar.baz.boo"}
- for _, tag := range validTags {
- if err := ValidateTagName(tag); err != nil {
- t.Errorf("'%s' should've been a valid tag", tag)
- }
- }
-}
-
-func TestInvalidTagName(t *testing.T) {
- validTags := []string{"-9", ".foo", "-test", ".", "-"}
- for _, tag := range validTags {
- if err := ValidateTagName(tag); err == nil {
- t.Errorf("'%s' shouldn't have been a valid tag", tag)
- }
- }
-}
-
func TestValidateDigest(t *testing.T) {
tests := []struct {
input string
diff --git a/graph/viz.go b/graph/viz.go
deleted file mode 100644
index 924c22b6a..000000000
--- a/graph/viz.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package graph
-
-import (
- "strings"
-
- "github.com/docker/docker/engine"
- "github.com/docker/docker/image"
-)
-
-func (s *TagStore) CmdViz(job *engine.Job) engine.Status {
- images, _ := s.graph.Map()
- if images == nil {
- return engine.StatusOK
- }
- job.Stdout.Write([]byte("digraph docker {\n"))
-
- var (
- parentImage *image.Image
- err error
- )
- for _, image := range images {
- parentImage, err = image.GetParent()
- if err != nil {
- return job.Errorf("Error while getting parent image: %v", err)
- }
- if parentImage != nil {
- job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n"))
- } else {
- job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n"))
- }
- }
-
- for id, repos := range s.GetRepoRefs() {
- job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n"))
- }
- job.Stdout.Write([]byte(" base [style=invisible]\n}\n"))
- return engine.StatusOK
-}
diff --git a/hack/dind b/hack/dind
index f8fae6379..9289ba655 100755
--- a/hack/dind
+++ b/hack/dind
@@ -3,7 +3,7 @@ set -e
# DinD: a wrapper script which allows docker to be run inside a docker container.
# Original version by Jerome Petazzoni
-# See the blog post: http://blog.docker.com/2013/09/docker-can-now-run-within-docker/
+# See the blog post: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
#
# This script should be executed inside a docker container in privilieged mode
# ('docker run --privileged', introduced in docker 0.6).
@@ -33,28 +33,35 @@ if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then
fi
# Mount the cgroup hierarchies exactly as they are in the parent system.
-for SUBSYS in $(cut -d: -f2 /proc/1/cgroup); do
- mkdir -p "$CGROUP/$SUBSYS"
- if ! mountpoint -q $CGROUP/$SUBSYS; then
- mount -n -t cgroup -o "$SUBSYS" cgroup "$CGROUP/$SUBSYS"
- fi
+for HIER in $(cut -d: -f2 /proc/1/cgroup); do
- # The two following sections address a bug which manifests itself
+ # The following sections address a bug which manifests itself
# by a cryptic "lxc-start: no ns_cgroup option specified" when
- # trying to start containers withina container.
+ # trying to start containers within a container.
# The bug seems to appear when the cgroup hierarchies are not
# mounted on the exact same directories in the host, and in the
# container.
+ SUBSYSTEMS="${HIER%name=*}"
+
+ # If cgroup hierarchy is named(mounted with "-o name=foo") we
+ # need to mount it in $CGROUP/foo to create exect same
+ # directoryes as on host. Else we need to mount it as is e.g.
+ # "subsys1,subsys2" if it has two subsystems
+
# Named, control-less cgroups are mounted with "-o name=foo"
# (and appear as such under /proc//cgroup) but are usually
# mounted on a directory named "foo" (without the "name=" prefix).
# Systemd and OpenRC (and possibly others) both create such a
- # cgroup. To avoid the aforementioned bug, we symlink "foo" to
- # "name=foo". This shouldn't have any adverse effect.
- name="${SUBSYS#name=}"
- if [ "$name" != "$SUBSYS" ]; then
- ln -s "$SUBSYS" "$CGROUP/$name"
+ # cgroup. So just mount them on directory $CGROUP/foo.
+
+ OHIER=$HIER
+ HIER="${HIER#*name=}"
+
+ mkdir -p "$CGROUP/$HIER"
+
+ if ! mountpoint -q "$CGROUP/$HIER"; then
+ mount -n -t cgroup -o "$OHIER" cgroup "$CGROUP/$HIER"
fi
# Likewise, on at least one system, it has been reported that
@@ -62,8 +69,25 @@ for SUBSYS in $(cut -d: -f2 /proc/1/cgroup); do
# (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
# but on a directory called "cpu,cpuacct" (note the inversion
# in the order of the groups). This tries to work around it.
- if [ "$SUBSYS" = 'cpuacct,cpu' ]; then
- ln -s "$SUBSYS" "$CGROUP/cpu,cpuacct"
+
+ if [ "$HIER" = 'cpuacct,cpu' ]; then
+ ln -s "$HIER" "$CGROUP/cpu,cpuacct"
+ fi
+
+ # If hierarchy has multiple subsystems, in /proc//cgroup
+ # we will see ":subsys1,subsys2,subsys3,name=foo:" substring,
+ # we need to mount it to "$CGROUP/foo" and if there were no
+ # name to "$CGROUP/subsys1,subsys2,subsys3", so we must create
+ # symlinks for docker daemon to find these subsystems:
+ # ln -s $CGROUP/foo $CGROUP/subsys1
+ # ln -s $CGROUP/subsys1,subsys2,subsys3 $CGROUP/subsys1
+
+ if [ "$SUBSYSTEMS" != "${SUBSYSTEMS//,/ }" ]; then
+ SUBSYSTEMS="${SUBSYSTEMS//,/ }"
+ for SUBSYS in $SUBSYSTEMS
+ do
+ ln -s "$CGROUP/$HIER" "$CGROUP/$SUBSYS"
+ done
fi
done
diff --git a/hack/install.sh b/hack/install.sh
index b0177e670..1984d5a5f 100755
--- a/hack/install.sh
+++ b/hack/install.sh
@@ -49,11 +49,18 @@ do_install() {
;;
esac
- if command_exists docker || command_exists lxc-docker; then
+ if command_exists docker; then
cat >&2 <<-'EOF'
- Warning: "docker" or "lxc-docker" command appears to already exist.
- Please ensure that you do not already have docker installed.
- You may press Ctrl+C now to abort this process and rectify this situation.
+ Warning: the "docker" command appears to already exist on this system.
+
+ If you already have Docker installed, this script can cause trouble, which is
+ why we're displaying this warning and provide the opportunity to cancel the
+ installation.
+
+ If you installed the current Docker package using this script and are using it
+ again to update Docker, you can safely ignore this message.
+
+ You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
@@ -126,7 +133,7 @@ do_install() {
exit 0
;;
- ubuntu|debian|linuxmint)
+ ubuntu|debian|linuxmint|'elementary os'|kali)
export DEBIAN_FRONTEND=noninteractive
did_apt_get_update=
diff --git a/hack/make.sh b/hack/make.sh
index 118d4327f..60aafbd66 100755
--- a/hack/make.sh
+++ b/hack/make.sh
@@ -6,7 +6,7 @@ set -e
#
# Requirements:
# - The current directory should be a checkout of the docker source code
-# (http://github.com/docker/docker). Whatever version is checked out
+# (https://github.com/docker/docker). Whatever version is checked out
# will be built.
# - The VERSION file, at the root of the repository, should exist, and
# will be used as Docker binary version and package version.
@@ -24,10 +24,12 @@ set -e
set -o pipefail
export DOCKER_PKG='github.com/docker/docker'
+export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+export MAKEDIR="$SCRIPTDIR/make"
# We're a nice, sexy, little shell script, and people might try to run us;
# but really, they shouldn't. We want to be in a container!
-if [ "$(pwd)" != "/go/src/$DOCKER_PKG" ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then
+if [ "$PWD" != "/go/src/$DOCKER_PKG" ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then
{
echo "# WARNING! I don't seem to be running in the Docker container."
echo "# The result of this command might be an incorrect build, and will not be"
@@ -44,7 +46,9 @@ echo
DEFAULT_BUNDLES=(
validate-dco
validate-gofmt
+ validate-test
validate-toml
+ validate-vet
binary
@@ -53,7 +57,6 @@ DEFAULT_BUNDLES=(
test-docker-py
dynbinary
- test-integration
cover
cross
@@ -61,7 +64,7 @@ DEFAULT_BUNDLES=(
ubuntu
)
-VERSION=$(cat ./VERSION)
+VERSION=$(< ./VERSION)
if command -v git &> /dev/null && git rev-parse &> /dev/null; then
GITCOMMIT=$(git rev-parse --short HEAD)
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
@@ -81,15 +84,22 @@ if [ "$AUTO_GOPATH" ]; then
rm -rf .gopath
mkdir -p .gopath/src/"$(dirname "${DOCKER_PKG}")"
ln -sf ../../../.. .gopath/src/"${DOCKER_PKG}"
- export GOPATH="$(pwd)/.gopath:$(pwd)/vendor"
+ export GOPATH="${PWD}/.gopath:${PWD}/vendor"
fi
if [ ! "$GOPATH" ]; then
- echo >&2 'error: missing GOPATH; please see http://golang.org/doc/code.html#GOPATH'
+ echo >&2 'error: missing GOPATH; please see https://golang.org/doc/code.html#GOPATH'
echo >&2 ' alternatively, set AUTO_GOPATH=1'
exit 1
fi
+if [ "$DOCKER_EXPERIMENTAL" ]; then
+ echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features'
+ echo >&2
+ VERSION+="-experimental"
+ DOCKER_BUILDTAGS+=" experimental"
+fi
+
if [ -z "$DOCKER_CLIENTONLY" ]; then
DOCKER_BUILDTAGS+=" daemon"
fi
@@ -98,10 +108,27 @@ if [ "$DOCKER_EXECDRIVER" = 'lxc' ]; then
DOCKER_BUILDTAGS+=' test_no_exec'
fi
+# test whether "btrfs/version.h" exists and apply btrfs_noversion appropriately
+if \
+ command -v gcc &> /dev/null \
+ && ! gcc -E - &> /dev/null <<<'#include ' \
+; then
+ DOCKER_BUILDTAGS+=' btrfs_noversion'
+fi
+
+# test whether "libdevmapper.h" is new enough to support deferred remove
+# functionality.
+if \
+ command -v gcc &> /dev/null \
+ && ! ( echo -e '#include \nint main() { dm_task_deferred_remove(NULL); }'| gcc -ldevmapper -xc - &> /dev/null ) \
+; then
+ DOCKER_BUILDTAGS+=' libdm_no_deferred_remove'
+fi
+
# Use these flags when compiling the tests and final binary
IAMSTATIC='true'
-source "$(dirname "$BASH_SOURCE")/make/.go-autogen"
+source "$SCRIPTDIR/make/.go-autogen"
LDFLAGS='-w'
LDFLAGS_STATIC='-linkmode external'
@@ -156,7 +183,12 @@ fi
# If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'.
# You can use this to select certain tests to run, eg.
#
-# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test
+# TESTFLAGS='-test.run ^TestBuild$' ./hack/make.sh test-unit
+#
+# For integration-cli test, we use [gocheck](https://labix.org/gocheck), if you want
+# to run certain tests on your local host, you should run with command:
+#
+# TESTFLAGS='-check.f DockerSuite.TestBuild*' ./hack/make.sh binary test-integration-cli
#
go_test_dir() {
dir=$1
@@ -182,6 +214,7 @@ test_env() {
DEST="$DEST" \
DOCKER_EXECDRIVER="$DOCKER_EXECDRIVER" \
DOCKER_GRAPHDRIVER="$DOCKER_GRAPHDRIVER" \
+ DOCKER_USERLANDPROXY="$DOCKER_USERLANDPROXY" \
DOCKER_HOST="$DOCKER_HOST" \
GOPATH="$GOPATH" \
HOME="$DEST/fake-HOME" \
@@ -204,7 +237,6 @@ find_dirs() {
find . -not \( \
\( \
-path './vendor/*' \
- -o -path './integration/*' \
-o -path './integration-cli/*' \
-o -path './contrib/*' \
-o -path './pkg/mflag/example/*' \
@@ -242,7 +274,7 @@ bundle() {
bundlescript=$1
bundle=$(basename $bundlescript)
echo "---> Making bundle: $bundle (in bundles/$VERSION/$bundle)"
- mkdir -p bundles/$VERSION/$bundle
+ mkdir -p "bundles/$VERSION/$bundle"
source "$bundlescript" "$(pwd)/bundles/$VERSION/$bundle"
}
@@ -252,17 +284,24 @@ main() {
mkdir -p bundles
if [ -e "bundles/$VERSION" ]; then
echo "bundles/$VERSION already exists. Removing."
- rm -fr bundles/$VERSION && mkdir bundles/$VERSION || exit 1
+ rm -fr "bundles/$VERSION" && mkdir "bundles/$VERSION" || exit 1
echo
fi
- SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+ if [ "$(go env GOHOSTOS)" != 'windows' ]; then
+ # Windows and symlinks don't get along well
+
+ rm -f bundles/latest
+ ln -s "$VERSION" bundles/latest
+ fi
+
if [ $# -lt 1 ]; then
bundles=(${DEFAULT_BUNDLES[@]})
else
bundles=($@)
fi
for bundle in ${bundles[@]}; do
- bundle $SCRIPTDIR/make/$bundle
+ bundle "$SCRIPTDIR/make/$bundle"
echo
done
}
diff --git a/hack/make/.build-deb/compat b/hack/make/.build-deb/compat
new file mode 100644
index 000000000..ec635144f
--- /dev/null
+++ b/hack/make/.build-deb/compat
@@ -0,0 +1 @@
+9
diff --git a/hack/make/.build-deb/control b/hack/make/.build-deb/control
new file mode 100644
index 000000000..ac6541a73
--- /dev/null
+++ b/hack/make/.build-deb/control
@@ -0,0 +1,27 @@
+Source: docker-engine
+Maintainer: Docker
+Homepage: https://dockerproject.com
+Vcs-Browser: https://github.com/docker/docker
+Vcs-Git: git://github.com/docker/docker.git
+
+Package: docker-engine
+Architecture: linux-any
+Depends: iptables, ${misc:Depends}, ${perl:Depends}, ${shlibs:Depends}
+Recommends: aufs-tools,
+ ca-certificates,
+ cgroupfs-mount | cgroup-lite,
+ git,
+ xz-utils,
+ ${apparmor:Recommends}
+Conflicts: docker (<< 1.5~), docker.io, lxc-docker, lxc-docker-virtual-package
+Description: Docker: the open-source application container engine
+ Docker is an open source project to pack, ship and run any application as a
+ lightweight container
+ .
+ Docker containers are both hardware-agnostic and platform-agnostic. This means
+ they can run anywhere, from your laptop to the largest EC2 compute instance and
+ they can run anywhere, from your laptop to the largest EC2 compute instance and
+ everything in between - and they don't require you to use a particular
+ language, framework or packaging system. That makes them great building blocks
+ for deploying and scaling web apps, databases, and backend services without
+ depending on a particular stack or provider.
diff --git a/hack/make/.build-deb/docker-engine.bash-completion b/hack/make/.build-deb/docker-engine.bash-completion
new file mode 100644
index 000000000..6ea111930
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.bash-completion
@@ -0,0 +1 @@
+contrib/completion/bash/docker
diff --git a/hack/make/.build-deb/docker-engine.docker.default b/hack/make/.build-deb/docker-engine.docker.default
new file mode 120000
index 000000000..4278533d6
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.docker.default
@@ -0,0 +1 @@
+../../../contrib/init/sysvinit-debian/docker.default
\ No newline at end of file
diff --git a/hack/make/.build-deb/docker-engine.docker.init b/hack/make/.build-deb/docker-engine.docker.init
new file mode 120000
index 000000000..8cb89d30d
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.docker.init
@@ -0,0 +1 @@
+../../../contrib/init/sysvinit-debian/docker
\ No newline at end of file
diff --git a/hack/make/.build-deb/docker-engine.docker.upstart b/hack/make/.build-deb/docker-engine.docker.upstart
new file mode 120000
index 000000000..7e1b64a3e
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.docker.upstart
@@ -0,0 +1 @@
+../../../contrib/init/upstart/docker.conf
\ No newline at end of file
diff --git a/hack/make/.build-deb/docker-engine.install b/hack/make/.build-deb/docker-engine.install
new file mode 100644
index 000000000..a8857a96d
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.install
@@ -0,0 +1,11 @@
+#contrib/syntax/vim/doc/* /usr/share/vim/vimfiles/doc/
+#contrib/syntax/vim/ftdetect/* /usr/share/vim/vimfiles/ftdetect/
+#contrib/syntax/vim/syntax/* /usr/share/vim/vimfiles/syntax/
+contrib/*-integration usr/share/docker-engine/contrib/
+contrib/check-config.sh usr/share/docker-engine/contrib/
+contrib/completion/zsh/_docker usr/share/zsh/vendor-completions/
+contrib/init/systemd/docker.service lib/systemd/system/
+contrib/init/systemd/docker.socket lib/systemd/system/
+contrib/mk* usr/share/docker-engine/contrib/
+contrib/nuke-graph-directory.sh usr/share/docker-engine/contrib/
+contrib/syntax/nano/Dockerfile.nanorc usr/share/nano/
diff --git a/hack/make/.build-deb/docker-engine.manpages b/hack/make/.build-deb/docker-engine.manpages
new file mode 100644
index 000000000..d5cff8a47
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.manpages
@@ -0,0 +1 @@
+docs/man/man*/*
diff --git a/hack/make/.build-deb/docker-engine.postinst b/hack/make/.build-deb/docker-engine.postinst
new file mode 100644
index 000000000..eeef6ca80
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.postinst
@@ -0,0 +1,20 @@
+#!/bin/sh
+set -e
+
+case "$1" in
+ configure)
+ if [ -z "$2" ]; then
+ if ! getent group docker > /dev/null; then
+ groupadd --system docker
+ fi
+ fi
+ ;;
+ abort-*)
+ # How'd we get here??
+ exit 1
+ ;;
+ *)
+ ;;
+esac
+
+#DEBHELPER#
diff --git a/hack/make/.build-deb/docker-engine.udev b/hack/make/.build-deb/docker-engine.udev
new file mode 120000
index 000000000..914a36195
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.udev
@@ -0,0 +1 @@
+../../../contrib/udev/80-docker.rules
\ No newline at end of file
diff --git a/hack/make/.build-deb/docs b/hack/make/.build-deb/docs
new file mode 100644
index 000000000..b43bf86b5
--- /dev/null
+++ b/hack/make/.build-deb/docs
@@ -0,0 +1 @@
+README.md
diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules
new file mode 100755
index 000000000..fe19b729e
--- /dev/null
+++ b/hack/make/.build-deb/rules
@@ -0,0 +1,36 @@
+#!/usr/bin/make -f
+
+VERSION = $(shell cat VERSION)
+
+override_dh_gencontrol:
+ # if we're on Ubuntu, we need to Recommends: apparmor
+ echo 'apparmor:Recommends=$(shell dpkg-vendor --is Ubuntu && echo apparmor)' >> debian/docker-engine.substvars
+ dh_gencontrol
+
+override_dh_auto_build:
+ ./hack/make.sh dynbinary
+ # ./docs/man/md2man-all.sh runs outside the build container (if at all), since we don't have go-md2man here
+
+override_dh_auto_test:
+ ./bundles/$(VERSION)/dynbinary/docker -v
+
+override_dh_strip:
+ # the SHA1 of dockerinit is important: don't strip it
+ # also, Go has lots of problems with stripping, so just don't
+
+override_dh_auto_install:
+ mkdir -p debian/docker-engine/usr/bin
+ cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/docker)" debian/docker-engine/usr/bin/docker
+ mkdir -p debian/docker-engine/usr/lib/docker
+ cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/dockerinit)" debian/docker-engine/usr/lib/docker/dockerinit
+
+override_dh_installinit:
+ # use "docker" as our service name, not "docker-engine"
+ dh_installinit --name=docker
+
+override_dh_installudev:
+ # match our existing priority
+ dh_installudev --priority=z80
+
+%:
+ dh $@ --with=systemd,bash-completion
diff --git a/hack/make/.build-rpm/docker-engine.spec b/hack/make/.build-rpm/docker-engine.spec
new file mode 100644
index 000000000..8cb7c1118
--- /dev/null
+++ b/hack/make/.build-rpm/docker-engine.spec
@@ -0,0 +1,184 @@
+Name: docker-engine
+Version: %{_version}
+Release: %{_release}%{?dist}
+Summary: The open-source application container engine
+
+License: ASL 2.0
+Source: %{name}.tar.gz
+
+URL: https://dockerproject.com
+Vendor: Docker
+Packager: Docker
+
+# docker builds in a checksum of dockerinit into docker,
+# # so stripping the binaries breaks docker
+%global __os_install_post %{_rpmconfigdir}/brp-compress
+%global debug_package %{nil}
+
+# is_systemd conditional
+%if 0%{?fedora} >= 21 || 0%{?centos} >= 7 || 0%{?rhel} >= 7
+%global is_systemd 1
+%endif
+
+# required packages for build
+# most are already in the container (see contrib/builder/rpm/generate.sh)
+# only require systemd on those systems
+%if 0%{?is_systemd}
+BuildRequires: pkgconfig(systemd)
+Requires: systemd-units
+%else
+Requires(post): chkconfig
+Requires(preun): chkconfig
+# This is for /sbin/service
+Requires(preun): initscripts
+%endif
+
+# required packages on install
+Requires: /bin/sh
+Requires: iptables
+Requires: libc.so.6
+Requires: libcgroup
+Requires: libpthread.so.0
+Requires: libsqlite3.so.0
+Requires: tar
+Requires: xz
+%if 0%{?fedora} >= 21
+# Resolves: rhbz#1165615
+Requires: device-mapper-libs >= 1.02.90-1
+%endif
+
+# conflicting packages
+Conflicts: docker
+Conflicts: docker-io
+
+%description
+Docker is an open source project to pack, ship and run any application as a
+lightweight container
+
+Docker containers are both hardware-agnostic and platform-agnostic. This means
+they can run anywhere, from your laptop to the largest EC2 compute instance and
+everything in between - and they don't require you to use a particular
+language, framework or packaging system. That makes them great building blocks
+for deploying and scaling web apps, databases, and backend services without
+depending on a particular stack or provider.
+
+%prep
+%if 0%{?centos} <= 6
+%setup -n %{name}
+%else
+%autosetup -n %{name}
+%endif
+
+%build
+./hack/make.sh dynbinary
+# ./docs/man/md2man-all.sh runs outside the build container (if at all), since we don't have go-md2man here
+
+%check
+./bundles/%{_origversion}/dynbinary/docker -v
+
+%install
+# install binary
+install -d $RPM_BUILD_ROOT/%{_bindir}
+install -p -m 755 bundles/%{_origversion}/dynbinary/docker-%{_origversion} $RPM_BUILD_ROOT/%{_bindir}/docker
+
+# install dockerinit
+install -d $RPM_BUILD_ROOT/%{_libexecdir}/docker
+install -p -m 755 bundles/%{_origversion}/dynbinary/dockerinit-%{_origversion} $RPM_BUILD_ROOT/%{_libexecdir}/docker/dockerinit
+
+# install udev rules
+install -d $RPM_BUILD_ROOT/%{_sysconfdir}/udev/rules.d
+install -p -m 755 contrib/udev/80-docker.rules $RPM_BUILD_ROOT/%{_sysconfdir}/udev/rules.d/80-docker.rules
+
+# add init scripts
+install -d $RPM_BUILD_ROOT/etc/sysconfig
+install -d $RPM_BUILD_ROOT/%{_initddir}
+
+
+%if 0%{?is_systemd}
+install -d $RPM_BUILD_ROOT/%{_unitdir}
+install -p -m 644 contrib/init/systemd/docker.service $RPM_BUILD_ROOT/%{_unitdir}/docker.service
+install -p -m 644 contrib/init/systemd/docker.socket $RPM_BUILD_ROOT/%{_unitdir}/docker.socket
+%endif
+
+install -p -m 644 contrib/init/sysvinit-redhat/docker.sysconfig $RPM_BUILD_ROOT/etc/sysconfig/docker
+install -p -m 755 contrib/init/sysvinit-redhat/docker $RPM_BUILD_ROOT/%{_initddir}/docker
+
+# add bash completions
+install -d $RPM_BUILD_ROOT/usr/share/bash-completion/completions
+install -d $RPM_BUILD_ROOT/usr/share/zsh/vendor-completions
+install -d $RPM_BUILD_ROOT/usr/share/fish/completions
+install -p -m 644 contrib/completion/bash/docker $RPM_BUILD_ROOT/usr/share/bash-completion/completions/docker
+install -p -m 644 contrib/completion/zsh/_docker $RPM_BUILD_ROOT/usr/share/zsh/vendor-completions/_docker
+install -p -m 644 contrib/completion/fish/docker.fish $RPM_BUILD_ROOT/usr/share/fish/completions/docker.fish
+
+# install manpages
+install -d %{buildroot}%{_mandir}/man1
+install -p -m 644 docs/man/man1/*.1 $RPM_BUILD_ROOT/%{_mandir}/man1
+install -d %{buildroot}%{_mandir}/man5
+install -p -m 644 docs/man/man5/*.5 $RPM_BUILD_ROOT/%{_mandir}/man5
+
+# add vimfiles
+install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/doc
+install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/ftdetect
+install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/syntax
+install -p -m 644 contrib/syntax/vim/doc/dockerfile.txt $RPM_BUILD_ROOT/usr/share/vim/vimfiles/doc/dockerfile.txt
+install -p -m 644 contrib/syntax/vim/ftdetect/dockerfile.vim $RPM_BUILD_ROOT/usr/share/vim/vimfiles/ftdetect/dockerfile.vim
+install -p -m 644 contrib/syntax/vim/syntax/dockerfile.vim $RPM_BUILD_ROOT/usr/share/vim/vimfiles/syntax/dockerfile.vim
+
+# add nano
+install -d $RPM_BUILD_ROOT/usr/share/nano
+install -p -m 644 contrib/syntax/nano/Dockerfile.nanorc $RPM_BUILD_ROOT/usr/share/nano/Dockerfile.nanorc
+
+# list files owned by the package here
+%files
+/%{_bindir}/docker
+/%{_libexecdir}/docker/dockerinit
+/%{_sysconfdir}/udev/rules.d/80-docker.rules
+%if 0%{?is_systemd}
+/%{_unitdir}/docker.service
+/%{_unitdir}/docker.socket
+%endif
+/etc/sysconfig/docker
+/%{_initddir}/docker
+/usr/share/bash-completion/completions/docker
+/usr/share/zsh/vendor-completions/_docker
+/usr/share/fish/completions/docker.fish
+%doc
+/%{_mandir}/man1/*
+/%{_mandir}/man5/*
+/usr/share/vim/vimfiles/doc/dockerfile.txt
+/usr/share/vim/vimfiles/ftdetect/dockerfile.vim
+/usr/share/vim/vimfiles/syntax/dockerfile.vim
+/usr/share/nano/Dockerfile.nanorc
+
+%post
+%if 0%{?is_systemd}
+%systemd_post docker
+%else
+# This adds the proper /etc/rc*.d links for the script
+/sbin/chkconfig --add docker
+%endif
+if ! getent group docker > /dev/null; then
+ groupadd --system docker
+fi
+
+%preun
+%if 0%{?is_systemd}
+%systemd_preun docker
+%else
+if [ $1 -eq 0 ] ; then
+ /sbin/service docker stop >/dev/null 2>&1
+ /sbin/chkconfig --del docker
+fi
+%endif
+
+%postun
+%if 0%{?is_systemd}
+%systemd_postun_with_restart docker
+%else
+if [ "$1" -ge "1" ] ; then
+ /sbin/service docker condrestart >/dev/null 2>&1 || :
+fi
+%endif
+
+%changelog
diff --git a/hack/make/.dockerinit b/hack/make/.dockerinit
index fceba7db9..4a62ee1ad 100644
--- a/hack/make/.dockerinit
+++ b/hack/make/.dockerinit
@@ -2,7 +2,7 @@
set -e
IAMSTATIC="true"
-source "$(dirname "$BASH_SOURCE")/.go-autogen"
+source "${MAKEDIR}/.go-autogen"
# dockerinit still needs to be a static binary, even if docker is dynamic
go build \
@@ -30,4 +30,4 @@ else
fi
# sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another
-export DOCKER_INITSHA1="$($sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)"
+export DOCKER_INITSHA1=$($sha1sum "$DEST/dockerinit-$VERSION" | cut -d' ' -f1)
diff --git a/hack/make/.dockerinit-gccgo b/hack/make/.dockerinit-gccgo
index 592a4152c..989086384 100644
--- a/hack/make/.dockerinit-gccgo
+++ b/hack/make/.dockerinit-gccgo
@@ -2,7 +2,7 @@
set -e
IAMSTATIC="true"
-source "$(dirname "$BASH_SOURCE")/.go-autogen"
+source "${MAKEDIR}/.go-autogen"
# dockerinit still needs to be a static binary, even if docker is dynamic
go build --compiler=gccgo \
@@ -12,6 +12,7 @@ go build --compiler=gccgo \
-g
-Wl,--no-export-dynamic
$EXTLDFLAGS_STATIC_DOCKER
+ -lnetgo
" \
./dockerinit
@@ -27,4 +28,4 @@ else
fi
# sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another
-export DOCKER_INITSHA1="$($sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)"
+export DOCKER_INITSHA1=$($sha1sum "$DEST/dockerinit-$VERSION" | cut -d' ' -f1)
diff --git a/hack/make/.ensure-frozen-images b/hack/make/.ensure-frozen-images
index 379f73849..deded80e7 100644
--- a/hack/make/.ensure-frozen-images
+++ b/hack/make/.ensure-frozen-images
@@ -5,6 +5,7 @@ set -e
images=(
busybox:latest
hello-world:frozen
+ jess/unshare:latest
)
if ! docker inspect "${images[@]}" &> /dev/null; then
diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start
index 570c6c7a9..eecf682ef 100644
--- a/hack/make/.integration-daemon-start
+++ b/hack/make/.integration-daemon-start
@@ -14,8 +14,30 @@ exec 41>&1 42>&2
export DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs}
export DOCKER_EXECDRIVER=${DOCKER_EXECDRIVER:-native}
+export DOCKER_USERLANDPROXY=${DOCKER_USERLANDPROXY:-true}
+
+# example usage: DOCKER_STORAGE_OPTS="dm.basesize=20G,dm.loopdatasize=200G"
+storage_params=""
+if [ -n "$DOCKER_STORAGE_OPTS" ]; then
+ IFS=','
+ for i in ${DOCKER_STORAGE_OPTS}; do
+ storage_params="--storage-opt $i $storage_params"
+ done
+ unset IFS
+fi
if [ -z "$DOCKER_TEST_HOST" ]; then
+ # Start apparmor if it is enabled
+ if [ "$(cat /sys/module/apparmor/parameters/enabled)" == "Y" ]; then
+ # reset container variable so apparmor profile is applied to process
+ # see https://github.com/docker/libcontainer/blob/master/apparmor/apparmor.go#L16
+ export container=""
+ (
+ set -x
+ /etc/init.d/apparmor start
+ )
+ fi
+
export DOCKER_HOST="unix://$(cd "$DEST" && pwd)/docker.sock" # "pwd" tricks to make sure $DEST is an absolute path, not a relative one
( set -x; exec \
docker --daemon --debug \
@@ -23,8 +45,11 @@ if [ -z "$DOCKER_TEST_HOST" ]; then
--storage-driver "$DOCKER_GRAPHDRIVER" \
--exec-driver "$DOCKER_EXECDRIVER" \
--pidfile "$DEST/docker.pid" \
+ --userland-proxy="$DOCKER_USERLANDPROXY" \
+ $storage_params \
&> "$DEST/docker.log"
) &
+ trap "source '${MAKEDIR}/.integration-daemon-stop'" EXIT # make sure that if the script exits unexpectedly, we stop this daemon we just started
else
export DOCKER_HOST="$DOCKER_TEST_HOST"
fi
diff --git a/hack/make/.integration-daemon-stop b/hack/make/.integration-daemon-stop
index 319aaa4a1..364490bde 100644
--- a/hack/make/.integration-daemon-stop
+++ b/hack/make/.integration-daemon-stop
@@ -1,9 +1,21 @@
#!/bin/bash
+trap - EXIT # reset EXIT trap applied in .integration-daemon-start
+
for pidFile in $(find "$DEST" -name docker.pid); do
pid=$(set -x; cat "$pidFile")
- ( set -x; kill $pid )
- if ! wait $pid; then
+ ( set -x; kill "$pid" )
+ if ! wait "$pid"; then
echo >&2 "warning: PID $pid from $pidFile had a nonzero exit code"
fi
done
+
+if [ -z "$DOCKER_TEST_HOST" ]; then
+ # Stop apparmor if it is enabled
+ if [ "$(cat /sys/module/apparmor/parameters/enabled)" == "Y" ]; then
+ (
+ set -x
+ /etc/init.d/apparmor stop
+ )
+ fi
+fi
diff --git a/hack/make/.validate b/hack/make/.validate
index 022809154..7397d0fa1 100644
--- a/hack/make/.validate
+++ b/hack/make/.validate
@@ -3,23 +3,23 @@
if [ -z "$VALIDATE_UPSTREAM" ]; then
# this is kind of an expensive check, so let's not do this twice if we
# are running more than one validate bundlescript
-
+
VALIDATE_REPO='https://github.com/docker/docker.git'
VALIDATE_BRANCH='master'
-
+
if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then
VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git"
VALIDATE_BRANCH="${TRAVIS_BRANCH}"
fi
-
+
VALIDATE_HEAD="$(git rev-parse --verify HEAD)"
-
+
git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH"
VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)"
-
+
VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD"
VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD"
-
+
validate_diff() {
if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then
git diff "$VALIDATE_COMMIT_DIFF" "$@"
diff --git a/hack/make/binary b/hack/make/binary
index 0f57ea0d6..7b2af5c84 100644
--- a/hack/make/binary
+++ b/hack/make/binary
@@ -11,8 +11,9 @@ if [[ "$(uname -s)" == CYGWIN* ]]; then
DEST=$(cygpath -mw $DEST)
fi
-source "$(dirname "$BASH_SOURCE")/.go-autogen"
+source "${MAKEDIR}/.go-autogen"
+echo "Building: $DEST/$BINARY_FULLNAME"
go build \
-o "$DEST/$BINARY_FULLNAME" \
"${BUILDFLAGS[@]}" \
diff --git a/hack/make/build-deb b/hack/make/build-deb
new file mode 100644
index 000000000..36e11777c
--- /dev/null
+++ b/hack/make/build-deb
@@ -0,0 +1,67 @@
+#!/bin/bash
+set -e
+
+DEST=$1
+
+# subshell so that we can export PATH without breaking other things
+(
+ source "${MAKEDIR}/.integration-daemon-start"
+
+ # TODO consider using frozen images for the dockercore/builder-deb tags
+
+ tilde='~' # ouch Bash 4.2 vs 4.3, you keel me
+ debVersion="${VERSION//-/$tilde}" # using \~ or '~' here works in 4.3, but not 4.2; just ~ causes $HOME to be inserted, hence the $tilde
+ # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better
+ if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then
+ gitUnix="$(git log -1 --pretty='%at')"
+ gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')"
+ gitCommit="$(git log -1 --pretty='%h')"
+ gitVersion="git${gitDate}.0.${gitCommit}"
+ # gitVersion is now something like 'git20150128.112847.0.17e840a'
+ debVersion="$debVersion~$gitVersion"
+
+ # $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false
+ # true
+ # $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false
+ # true
+ # $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false
+ # true
+
+ # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a
+ fi
+
+ debSource="$(awk -F ': ' '$1 == "Source" { print $2; exit }' hack/make/.build-deb/control)"
+ debMaintainer="$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' hack/make/.build-deb/control)"
+ debDate="$(date --rfc-2822)"
+
+ # if go-md2man is available, pre-generate the man pages
+ ./docs/man/md2man-all.sh -q || true
+ # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this
+
+ # TODO add a configurable knob for _which_ debs to build so we don't have to modify the file or build all of them every time we need to test
+ for dir in contrib/builder/deb/*/; do
+ version="$(basename "$dir")"
+ suite="${version##*-}"
+
+ image="dockercore/builder-deb:$version"
+ if ! docker inspect "$image" &> /dev/null; then
+ ( set -x && docker build -t "$image" "$dir" )
+ fi
+
+ mkdir -p "$DEST/$version"
+ cat > "$DEST/$version/Dockerfile.build" <<-EOF
+ FROM $image
+ WORKDIR /usr/src/docker
+ COPY . /usr/src/docker
+ RUN ln -sfv hack/make/.build-deb debian
+ RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog
+ RUN dpkg-buildpackage -uc -us
+ EOF
+ tempImage="docker-temp/build-deb:$version"
+ ( set -x && docker build -t "$tempImage" -f "$DEST/$version/Dockerfile.build" . )
+ docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version"
+ docker rmi "$tempImage"
+ done
+
+ source "${MAKEDIR}/.integration-daemon-stop"
+) 2>&1 | tee -a "$DEST/test.log"
diff --git a/hack/make/build-rpm b/hack/make/build-rpm
new file mode 100644
index 000000000..0f3ff6d00
--- /dev/null
+++ b/hack/make/build-rpm
@@ -0,0 +1,73 @@
+#!/bin/bash
+set -e
+
+DEST=$1
+
+# subshell so that we can export PATH without breaking other things
+(
+ source "$(dirname "$BASH_SOURCE")/.integration-daemon-start"
+
+ # TODO consider using frozen images for the dockercore/builder-rpm tags
+
+ rpmName=docker-engine
+ rpmVersion="${VERSION%%-*}"
+ rpmRelease=1
+
+ # rpmRelease versioning is as follows
+ # Docker 1.7.0: version=1.7.0, release=1
+ # Docker 1.7.0-rc1: version=1.7.0, release=0.1.rc1
+ # Docker 1.7.0-dev nightly: version=1.7.0, release=0.0.YYYYMMDD.HHMMSS.gitHASH
+
+ # if we have a "-rc*" suffix, set appropriate release
+ if [[ "$VERSION" == *-rc* ]]; then
+ rcVersion=${VERSION#*-rc}
+ rpmRelease="0.${rcVersion}.rc${rcVersion}"
+ fi
+
+ # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better
+ if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then
+ gitUnix="$(git log -1 --pretty='%at')"
+ gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')"
+ gitCommit="$(git log -1 --pretty='%h')"
+ gitVersion="${gitDate}.git${gitCommit}"
+ # gitVersion is now something like '20150128.112847.17e840a'
+ rpmRelease="0.0.$gitVersion"
+ fi
+
+ rpmPackager="$(awk -F ': ' '$1 == "Packager" { print $2; exit }' hack/make/.build-rpm/${rpmName}.spec)"
+ rpmDate="$(date +'%a %b %d %Y')"
+
+ # if go-md2man is available, pre-generate the man pages
+ ./docs/man/md2man-all.sh -q || true
+ # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this
+
+ # TODO add a configurable knob for _which_ rpms to build so we don't have to modify the file or build all of them every time we need to test
+ for dir in contrib/builder/rpm/*/; do
+ version="$(basename "$dir")"
+ suite="${version##*-}"
+
+ image="dockercore/builder-rpm:$version"
+ if ! docker inspect "$image" &> /dev/null; then
+ ( set -x && docker build -t "$image" "$dir" )
+ fi
+
+ mkdir -p "$DEST/$version"
+ cat > "$DEST/$version/Dockerfile.build" <<-EOF
+ FROM $image
+ COPY . /usr/src/${rpmName}
+ RUN mkdir -p /root/rpmbuild/SOURCES
+ WORKDIR /root/rpmbuild
+ RUN ln -sfv /usr/src/${rpmName}/hack/make/.build-rpm SPECS
+ RUN tar -cz -C /usr/src -f /root/rpmbuild/SOURCES/${rpmName}.tar.gz ${rpmName}
+ WORKDIR /root/rpmbuild/SPECS
+ RUN { echo '* $rpmDate $rpmPackager $rpmVersion-$rpmRelease'; echo '* Version: $VERSION'; } >> ${rpmName}.spec && tail >&2 ${rpmName}.spec
+ RUN rpmbuild -ba --define '_release $rpmRelease' --define '_version $rpmVersion' --define '_origversion $VERSION' ${rpmName}.spec
+ EOF
+ tempImage="docker-temp/build-rpm:$version"
+ ( set -x && docker build -t "$tempImage" -f $DEST/$version/Dockerfile.build . )
+ docker run --rm "$tempImage" bash -c 'cd /root/rpmbuild && tar -c *RPMS' | tar -xvC "$DEST/$version"
+ docker rmi "$tempImage"
+ done
+
+ source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop"
+) 2>&1 | tee -a $DEST/test.log
diff --git a/hack/make/cross b/hack/make/cross
index 3c5cb0401..368ebc5ab 100644
--- a/hack/make/cross
+++ b/hack/make/cross
@@ -28,6 +28,6 @@ for platform in $DOCKER_CROSSPLATFORMS; do
export LDFLAGS_STATIC_DOCKER="" # we just need a simple client for these platforms
export BUILDFLAGS=( "${ORIG_BUILDFLAGS[@]/ daemon/}" ) # remove the "daemon" build tag from platforms that aren't supported
fi
- source "$(dirname "$BASH_SOURCE")/binary" "$DEST/$platform"
+ source "${MAKEDIR}/binary" "$DEST/$platform"
)
done
diff --git a/hack/make/dynbinary b/hack/make/dynbinary
index 861a19214..e1b65b48e 100644
--- a/hack/make/dynbinary
+++ b/hack/make/dynbinary
@@ -4,8 +4,8 @@ set -e
DEST=$1
if [ -z "$DOCKER_CLIENTONLY" ]; then
- source "$(dirname "$BASH_SOURCE")/.dockerinit"
-
+ source "${MAKEDIR}/.dockerinit"
+
hash_files "$DEST/dockerinit-$VERSION"
else
# DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :)
@@ -18,5 +18,5 @@ fi
export LDFLAGS_STATIC_DOCKER=''
export BUILDFLAGS=( "${BUILDFLAGS[@]/netgo /}" ) # disable netgo, since we don't need it for a dynamic binary
export BUILDFLAGS=( "${BUILDFLAGS[@]/static_build /}" ) # we're not building a "static" binary here
- source "$(dirname "$BASH_SOURCE")/binary"
+ source "${MAKEDIR}/binary"
)
diff --git a/hack/make/dyngccgo b/hack/make/dyngccgo
index a76e9c5b5..7bdd404f1 100644
--- a/hack/make/dyngccgo
+++ b/hack/make/dyngccgo
@@ -4,8 +4,8 @@ set -e
DEST=$1
if [ -z "$DOCKER_CLIENTONLY" ]; then
- source "$(dirname "$BASH_SOURCE")/.dockerinit-gccgo"
-
+ source "${MAKEDIR}/.dockerinit-gccgo"
+
hash_files "$DEST/dockerinit-$VERSION"
else
# DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :)
@@ -19,5 +19,5 @@ fi
export LDFLAGS_STATIC_DOCKER=''
export BUILDFLAGS=( "${BUILDFLAGS[@]/netgo /}" ) # disable netgo, since we don't need it for a dynamic binary
export BUILDFLAGS=( "${BUILDFLAGS[@]/static_build /}" ) # we're not building a "static" binary here
- source "$(dirname "$BASH_SOURCE")/gccgo"
+ source "${MAKEDIR}/gccgo"
)
diff --git a/hack/make/gccgo b/hack/make/gccgo
index c85d2fbda..896c2d46c 100644
--- a/hack/make/gccgo
+++ b/hack/make/gccgo
@@ -6,8 +6,11 @@ BINARY_NAME="docker-$VERSION"
BINARY_EXTENSION="$(binary_extension)"
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
-source "$(dirname "$BASH_SOURCE")/.go-autogen"
+source "${MAKEDIR}/.go-autogen"
+if [[ "${BUILDFLAGS[@]}" =~ 'netgo ' ]]; then
+ EXTLDFLAGS_STATIC_DOCKER+=' -lnetgo'
+fi
go build -compiler=gccgo \
-o "$DEST/$BINARY_FULLNAME" \
"${BUILDFLAGS[@]}" \
diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py
index b95cf40af..ac5ef3583 100644
--- a/hack/make/test-docker-py
+++ b/hack/make/test-docker-py
@@ -5,26 +5,16 @@ DEST=$1
# subshell so that we can export PATH without breaking other things
(
- source "$(dirname "$BASH_SOURCE")/.integration-daemon-start"
+ source "${MAKEDIR}/.integration-daemon-start"
- # we need to wrap up everything in between integration-daemon-start and
- # integration-daemon-stop to make sure we kill the daemon and don't hang,
- # even and especially on test failures
- didFail=
- if ! {
- dockerPy='/docker-py'
- [ -d "$dockerPy" ] || {
- dockerPy="$DEST/docker-py"
- git clone https://github.com/docker/docker-py.git "$dockerPy"
- }
+ dockerPy='/docker-py'
+ [ -d "$dockerPy" ] || {
+ dockerPy="$DEST/docker-py"
+ git clone https://github.com/docker/docker-py.git "$dockerPy"
+ }
- # exporting PYTHONPATH to import "docker" from our local docker-py
- test_env PYTHONPATH="$dockerPy" python "$dockerPy/tests/integration_test.py"
- }; then
- didFail=1
- fi
+ # exporting PYTHONPATH to import "docker" from our local docker-py
+ test_env PYTHONPATH="$dockerPy" python "$dockerPy/tests/integration_test.py"
- source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop"
-
- [ -z "$didFail" ] # "set -e" ftw
-) 2>&1 | tee -a $DEST/test.log
+ source "${MAKEDIR}/.integration-daemon-stop"
+) 2>&1 | tee -a "$DEST/test.log"
diff --git a/hack/make/test-integration b/hack/make/test-integration
deleted file mode 100644
index 5cb7102bc..000000000
--- a/hack/make/test-integration
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-set -e
-
-DEST=$1
-
-INIT=$DEST/../dynbinary/dockerinit-$VERSION
-[ -x "$INIT" ] || {
- source "$(dirname "$BASH_SOURCE")/.dockerinit"
- INIT="$DEST/dockerinit"
-}
-export TEST_DOCKERINIT_PATH="$INIT"
-
-bundle_test_integration() {
- LDFLAGS="
- $LDFLAGS
- -X $DOCKER_PKG/dockerversion.INITSHA1 \"$DOCKER_INITSHA1\"
- " go_test_dir ./integration \
- "-coverpkg $(find_dirs '*.go' | sed 's,^\.,'$DOCKER_PKG',g' | paste -d, -s)"
-}
-
-# this "grep" hides some really irritating warnings that "go test -coverpkg"
-# spews when it is given packages that aren't used
-bundle_test_integration 2>&1 \
- | grep --line-buffered -v '^warning: no packages being tested depend on ' \
- | tee -a $DEST/test.log
diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli
index 3ef41d919..db1cb298f 100644
--- a/hack/make/test-integration-cli
+++ b/hack/make/test-integration-cli
@@ -9,23 +9,13 @@ bundle_test_integration_cli() {
# subshell so that we can export PATH without breaking other things
(
- source "$(dirname "$BASH_SOURCE")/.integration-daemon-start"
+ source "${MAKEDIR}/.integration-daemon-start"
- # we need to wrap up everything in between integration-daemon-start and
- # integration-daemon-stop to make sure we kill the daemon and don't hang,
- # even and especially on test failures
- didFail=
- if ! {
- source "$(dirname "$BASH_SOURCE")/.ensure-frozen-images"
- source "$(dirname "$BASH_SOURCE")/.ensure-httpserver"
- source "$(dirname "$BASH_SOURCE")/.ensure-emptyfs"
+ source "${MAKEDIR}/.ensure-frozen-images"
+ source "${MAKEDIR}/.ensure-httpserver"
+ source "${MAKEDIR}/.ensure-emptyfs"
- bundle_test_integration_cli
- }; then
- didFail=1
- fi
+ bundle_test_integration_cli
- source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop"
-
- [ -z "$didFail" ] # "set -e" ftw
-) 2>&1 | tee -a $DEST/test.log
+ source "${MAKEDIR}/.integration-daemon-stop"
+) 2>&1 | tee -a "$DEST/test.log"
diff --git a/hack/make/test-unit b/hack/make/test-unit
index 540ba41f3..7b6ce089e 100644
--- a/hack/make/test-unit
+++ b/hack/make/test-unit
@@ -12,7 +12,7 @@ TEXTRESET=$'\033[0m' # reset the foreground colour
# If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'.
# You can use this to select certain tests to run, eg.
#
-# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test-unit
+# TESTFLAGS='-test.run ^TestBuild$' ./hack/make.sh test-unit
#
bundle_test_unit() {
{
@@ -39,12 +39,12 @@ bundle_test_unit() {
mkdir -p "$HOME/.parallel"
touch "$HOME/.parallel/ignored_vars"
- echo "$TESTDIRS" | parallel --jobs "$PARALLEL_JOBS" --env _ "$(dirname "$BASH_SOURCE")/.go-compile-test-dir"
+ echo "$TESTDIRS" | parallel --jobs "$PARALLEL_JOBS" --env _ "${MAKEDIR}/.go-compile-test-dir"
rm -rf "$HOME"
else
# aww, no "parallel" available - fall back to boring
for test_dir in $TESTDIRS; do
- "$(dirname "$BASH_SOURCE")/.go-compile-test-dir" "$test_dir" || true
+ "${MAKEDIR}/.go-compile-test-dir" "$test_dir" || true
# don't let one directory that fails to build tank _all_ our tests!
done
fi
@@ -85,4 +85,4 @@ go_run_test_dir() {
fi
}
-bundle_test_unit 2>&1 | tee -a $DEST/test.log
+bundle_test_unit 2>&1 | tee -a "$DEST/test.log"
diff --git a/hack/make/tgz b/hack/make/tgz
index 7234218da..fa297e1f5 100644
--- a/hack/make/tgz
+++ b/hack/make/tgz
@@ -18,17 +18,17 @@ for d in "$CROSS/"*/*; do
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
mkdir -p "$DEST/$GOOS/$GOARCH"
TGZ="$DEST/$GOOS/$GOARCH/$BINARY_NAME.tgz"
-
+
mkdir -p "$DEST/build"
-
+
mkdir -p "$DEST/build/usr/local/bin"
cp -L "$d/$BINARY_FULLNAME" "$DEST/build/usr/local/bin/docker$BINARY_EXTENSION"
-
+
tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr
-
+
hash_files "$TGZ"
-
+
rm -rf "$DEST/build"
-
+
echo "Created tgz: $TGZ"
done
diff --git a/hack/make/ubuntu b/hack/make/ubuntu
index e34369eb1..7543789a1 100644
--- a/hack/make/ubuntu
+++ b/hack/make/ubuntu
@@ -23,7 +23,7 @@ fi
# ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a
PACKAGE_ARCHITECTURE="$(dpkg-architecture -qDEB_HOST_ARCH)"
-PACKAGE_URL="http://www.docker.com/"
+PACKAGE_URL="https://www.docker.com/"
PACKAGE_MAINTAINER="support@docker.com"
PACKAGE_DESCRIPTION="Linux container runtime
Docker complements LXC with a high-level API which operates at the process
@@ -40,26 +40,26 @@ bundle_ubuntu() {
DIR=$DEST/build
# Include our udev rules
- mkdir -p $DIR/etc/udev/rules.d
- cp contrib/udev/80-docker.rules $DIR/etc/udev/rules.d/
+ mkdir -p "$DIR/etc/udev/rules.d"
+ cp contrib/udev/80-docker.rules "$DIR/etc/udev/rules.d/"
# Include our init scripts
- mkdir -p $DIR/etc/init
- cp contrib/init/upstart/docker.conf $DIR/etc/init/
- mkdir -p $DIR/etc/init.d
- cp contrib/init/sysvinit-debian/docker $DIR/etc/init.d/
- mkdir -p $DIR/etc/default
- cp contrib/init/sysvinit-debian/docker.default $DIR/etc/default/docker
- mkdir -p $DIR/lib/systemd/system
- cp contrib/init/systemd/docker.{service,socket} $DIR/lib/systemd/system/
+ mkdir -p "$DIR/etc/init"
+ cp contrib/init/upstart/docker.conf "$DIR/etc/init/"
+ mkdir -p "$DIR/etc/init.d"
+ cp contrib/init/sysvinit-debian/docker "$DIR/etc/init.d/"
+ mkdir -p "$DIR/etc/default"
+ cp contrib/init/sysvinit-debian/docker.default "$DIR/etc/default/docker"
+ mkdir -p "$DIR/lib/systemd/system"
+ cp contrib/init/systemd/docker.{service,socket} "$DIR/lib/systemd/system/"
# Include contributed completions
- mkdir -p $DIR/etc/bash_completion.d
- cp contrib/completion/bash/docker $DIR/etc/bash_completion.d/
- mkdir -p $DIR/usr/share/zsh/vendor-completions
- cp contrib/completion/zsh/_docker $DIR/usr/share/zsh/vendor-completions/
- mkdir -p $DIR/etc/fish/completions
- cp contrib/completion/fish/docker.fish $DIR/etc/fish/completions/
+ mkdir -p "$DIR/etc/bash_completion.d"
+ cp contrib/completion/bash/docker "$DIR/etc/bash_completion.d/"
+ mkdir -p "$DIR/usr/share/zsh/vendor-completions"
+ cp contrib/completion/zsh/_docker "$DIR/usr/share/zsh/vendor-completions/"
+ mkdir -p "$DIR/etc/fish/completions"
+ cp contrib/completion/fish/docker.fish "$DIR/etc/fish/completions/"
# Include contributed man pages
docs/man/md2man-all.sh -q
@@ -76,11 +76,11 @@ bundle_ubuntu() {
# Copy the binary
# This will fail if the binary bundle hasn't been built
- mkdir -p $DIR/usr/bin
- cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker
+ mkdir -p "$DIR/usr/bin"
+ cp "$DEST/../binary/docker-$VERSION" "$DIR/usr/bin/docker"
# Generate postinst/prerm/postrm scripts
- cat > $DEST/postinst <<'EOF'
+ cat > "$DEST/postinst" <<'EOF'
#!/bin/sh
set -e
set -u
@@ -104,7 +104,7 @@ service docker $_dh_action 2>/dev/null || true
#DEBHELPER#
EOF
- cat > $DEST/prerm <<'EOF'
+ cat > "$DEST/prerm" <<'EOF'
#!/bin/sh
set -e
set -u
@@ -113,7 +113,7 @@ service docker stop 2>/dev/null || true
#DEBHELPER#
EOF
- cat > $DEST/postrm <<'EOF'
+ cat > "$DEST/postrm" <<'EOF'
#!/bin/sh
set -e
set -u
@@ -131,18 +131,18 @@ fi
#DEBHELPER#
EOF
# TODO swaths of these were borrowed from debhelper's auto-inserted stuff, because we're still using fpm - we need to use debhelper instead, and somehow reconcile Ubuntu that way
- chmod +x $DEST/postinst $DEST/prerm $DEST/postrm
+ chmod +x "$DEST/postinst" "$DEST/prerm" "$DEST/postrm"
(
# switch directories so we create *.deb in the right folder
- cd $DEST
+ cd "$DEST"
# create lxc-docker-VERSION package
- fpm -s dir -C $DIR \
- --name lxc-docker-$VERSION --version "$PKGVERSION" \
- --after-install $DEST/postinst \
- --before-remove $DEST/prerm \
- --after-remove $DEST/postrm \
+ fpm -s dir -C "$DIR" \
+ --name "lxc-docker-$VERSION" --version "$PKGVERSION" \
+ --after-install "$DEST/postinst" \
+ --before-remove "$DEST/prerm" \
+ --after-remove "$DEST/postrm" \
--architecture "$PACKAGE_ARCHITECTURE" \
--prefix / \
--depends iptables \
@@ -184,8 +184,8 @@ EOF
)
# clean up after ourselves so we have a clean output directory
- rm $DEST/postinst $DEST/prerm $DEST/postrm
- rm -r $DIR
+ rm "$DEST/postinst" "$DEST/prerm" "$DEST/postrm"
+ rm -r "$DIR"
}
bundle_ubuntu
diff --git a/hack/make/validate-dco b/hack/make/validate-dco
index 84c47f526..5ac98728f 100644
--- a/hack/make/validate-dco
+++ b/hack/make/validate-dco
@@ -1,6 +1,6 @@
#!/bin/bash
-source "$(dirname "$BASH_SOURCE")/.validate"
+source "${MAKEDIR}/.validate"
adds=$(validate_diff --numstat | awk '{ s += $1 } END { print s }')
dels=$(validate_diff --numstat | awk '{ s += $2 } END { print s }')
diff --git a/hack/make/validate-gofmt b/hack/make/validate-gofmt
index 8fc88cc55..7ad9e8557 100644
--- a/hack/make/validate-gofmt
+++ b/hack/make/validate-gofmt
@@ -1,6 +1,6 @@
#!/bin/bash
-source "$(dirname "$BASH_SOURCE")/.validate"
+source "${MAKEDIR}/.validate"
IFS=$'\n'
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
diff --git a/hack/make/validate-test b/hack/make/validate-test
new file mode 100644
index 000000000..d9d05f3be
--- /dev/null
+++ b/hack/make/validate-test
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+# Make sure we're not using gos' Testing package any more in integration-cli
+
+source "${MAKEDIR}/.validate"
+
+IFS=$'\n'
+files=( $(validate_diff --diff-filter=ACMR --name-only -- 'integration-cli/*.go' || true) )
+unset IFS
+
+badFiles=()
+for f in "${files[@]}"; do
+ # skip check_test.go since it *does* use the testing package
+ if [ "$f" = "integration-cli/check_test.go" ]; then
+ continue
+ fi
+
+ # we use "git show" here to validate that what's committed is formatted
+ if git show "$VALIDATE_HEAD:$f" | grep -q testing.T; then
+ badFiles+=( "$f" )
+ fi
+done
+
+if [ ${#badFiles[@]} -eq 0 ]; then
+ echo 'Congratulations! No testing.T found.'
+else
+ {
+ echo "These files use the wrong testing infrastructure:"
+ for f in "${badFiles[@]}"; do
+ echo " - $f"
+ done
+ echo
+ } >&2
+ false
+fi
diff --git a/hack/make/validate-toml b/hack/make/validate-toml
index 16c228d14..18f26ee75 100644
--- a/hack/make/validate-toml
+++ b/hack/make/validate-toml
@@ -1,6 +1,6 @@
#!/bin/bash
-source "$(dirname "$BASH_SOURCE")/.validate"
+source "${MAKEDIR}/.validate"
IFS=$'\n'
files=( $(validate_diff --diff-filter=ACMR --name-only -- 'MAINTAINERS' || true) )
diff --git a/hack/make/validate-vet b/hack/make/validate-vet
new file mode 100644
index 000000000..febe93e5c
--- /dev/null
+++ b/hack/make/validate-vet
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+source "${MAKEDIR}/.validate"
+
+IFS=$'\n'
+files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
+unset IFS
+
+errors=()
+for f in "${files[@]}"; do
+ # we use "git show" here to validate that what's committed passes go vet
+ failedVet=$(go vet "$f")
+ if [ "$failedVet" ]; then
+ errors+=( "$failedVet" )
+ fi
+done
+
+
+if [ ${#errors[@]} -eq 0 ]; then
+ echo 'Congratulations! All Go source files have been vetted.'
+else
+ {
+ echo "Errors from go vet:"
+ for err in "${errors[@]}"; do
+ echo " - $err"
+ done
+ echo
+ echo 'Please fix the above errors. You can test via "go vet" and commit the result.'
+ echo
+ } >&2
+ false
+fi
diff --git a/hack/release.sh b/hack/release.sh
index da95808c5..1d3c9c372 100755
--- a/hack/release.sh
+++ b/hack/release.sh
@@ -54,13 +54,13 @@ RELEASE_BUNDLES=(
if [ "$1" != '--release-regardless-of-test-failure' ]; then
RELEASE_BUNDLES=(
- test-unit test-integration
+ test-unit
"${RELEASE_BUNDLES[@]}"
test-integration-cli
)
fi
-VERSION=$(cat VERSION)
+VERSION=$(< VERSION)
BUCKET=$AWS_S3_BUCKET
# These are the 2 keys we've used to sign the deb's
@@ -71,23 +71,23 @@ BUCKET=$AWS_S3_BUCKET
setup_s3() {
# Try creating the bucket. Ignore errors (it might already exist).
- s3cmd mb s3://$BUCKET 2>/dev/null || true
+ s3cmd mb "s3://$BUCKET" 2>/dev/null || true
# Check access to the bucket.
# s3cmd has no useful exit status, so we cannot check that.
# Instead, we check if it outputs anything on standard output.
# (When there are problems, it uses standard error instead.)
- s3cmd info s3://$BUCKET | grep -q .
+ s3cmd info "s3://$BUCKET" | grep -q .
# Make the bucket accessible through website endpoints.
- s3cmd ws-create --ws-index index --ws-error error s3://$BUCKET
+ s3cmd ws-create --ws-index index --ws-error error "s3://$BUCKET"
}
# write_to_s3 uploads the contents of standard input to the specified S3 url.
write_to_s3() {
DEST=$1
F=`mktemp`
- cat > $F
- s3cmd --acl-public --mime-type='text/plain' put $F $DEST
- rm -f $F
+ cat > "$F"
+ s3cmd --acl-public --mime-type='text/plain' put "$F" "$DEST"
+ rm -f "$F"
}
s3_url() {
@@ -246,20 +246,20 @@ release_build() {
# 1. A full APT repository is published at $BUCKET/ubuntu/
# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index
release_ubuntu() {
- [ -e bundles/$VERSION/ubuntu ] || {
+ [ -e "bundles/$VERSION/ubuntu" ] || {
echo >&2 './hack/make.sh must be run before release_ubuntu'
exit 1
}
# Sign our packages
dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \
- --sign builder bundles/$VERSION/ubuntu/*.deb
+ --sign builder "bundles/$VERSION/ubuntu/"*.deb
# Setup the APT repo
APTDIR=bundles/$VERSION/ubuntu/apt
- mkdir -p $APTDIR/conf $APTDIR/db
- s3cmd sync s3://$BUCKET/ubuntu/db/ $APTDIR/db/ || true
- cat > $APTDIR/conf/distributions < "$APTDIR/conf/distributions" < bundles/$VERSION/ubuntu/gpg
- s3cmd --acl-public put bundles/$VERSION/ubuntu/gpg s3://$BUCKET/gpg
+ s3cmd sync "$HOME/.gnupg/" "s3://$BUCKET/ubuntu/.gnupg/"
+ gpg --armor --export releasedocker > "bundles/$VERSION/ubuntu/gpg"
+ s3cmd --acl-public put "bundles/$VERSION/ubuntu/gpg" "s3://$BUCKET/gpg"
local gpgFingerprint=36A1D7869245C8950F966E92D8576A8BA88D21E9
if [[ $BUCKET == test* ]]; then
@@ -287,7 +287,7 @@ EOF
fi
# Upload repo
- s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/
+ s3cmd --acl-public sync "$APTDIR/" "s3://$BUCKET/ubuntu/"
cat <&2 './hack/make.sh must be run before release_binaries'
exit 1
}
@@ -341,29 +341,29 @@ EOF
# Add redirect at /builds/info for URL-backwards-compatibility
rm -rf /tmp/emptyfile && touch /tmp/emptyfile
- s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/builds/info
+ s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile "s3://$BUCKET/builds/info"
if [ -z "$NOLATEST" ]; then
echo "Advertising $VERSION on $BUCKET as most recent version"
- echo $VERSION | write_to_s3 s3://$BUCKET/latest
+ echo "$VERSION" | write_to_s3 "s3://$BUCKET/latest"
fi
}
# Upload the index script
release_index() {
- sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 s3://$BUCKET/index
+ sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 "s3://$BUCKET/index"
}
release_test() {
if [ -e "bundles/$VERSION/test" ]; then
- s3cmd --acl-public sync bundles/$VERSION/test/ s3://$BUCKET/test/
+ s3cmd --acl-public sync "bundles/$VERSION/test/" "s3://$BUCKET/test/"
fi
}
setup_gpg() {
# Make sure that we have our keys
- mkdir -p $HOME/.gnupg/
- s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ $HOME/.gnupg/ || true
+ mkdir -p "$HOME/.gnupg/"
+ s3cmd sync "s3://$BUCKET/ubuntu/.gnupg/" "$HOME/.gnupg/" || true
gpg --list-keys releasedocker >/dev/null || {
gpg --gen-key --batch <
- Each layer has an associated A JSON structure which describes some
+ Each layer has an associated JSON structure which describes some
basic information about the image such as date created, author, and the
ID of its parent image as well as execution/runtime configuration like
its entry point, default arguments, CPU/memory shares, networking, and
@@ -81,7 +81,7 @@ This specification uses the following terms:
times of any entries differ. For this reason, image checksums are
generated using the TarSum algorithm which produces a cryptographic
hash of file contents and selected headers only. Details of this
- algorithm are described in the separate [TarSum specification](https://github.com/docker/docker/blob/master/pkg/tarsum/tarsum_spec.md).
+ algorithm are described in the separate TarSum specification.
Tag
@@ -492,9 +492,9 @@ Changeset tar archives.
There is also a format for a single archive which contains complete information
about an image, including:
- - repository names/tags
- - all image layer JSON files
- - all tar archives of each layer filesystem changesets
+ - repository names/tags
+ - all image layer JSON files
+ - all tar archives of each layer filesystem changesets
For example, here's what the full archive of `library/busybox` is (displayed in
`tree` format):
@@ -523,10 +523,10 @@ For example, here's what the full archive of `library/busybox` is (displayed in
There are one or more directories named with the ID for each layer in a full
image. Each of these directories contains 3 files:
- * `VERSION` - The schema version of the `json` file
- * `json` - The JSON metadata for an image layer
- * `layer.tar` - The Tar archive of the filesystem changeset for an image
- layer.
+ * `VERSION` - The schema version of the `json` file
+ * `json` - The JSON metadata for an image layer
+ * `layer.tar` - The Tar archive of the filesystem changeset for an image
+ layer.
The content of the `VERSION` files is simply the semantic version of the JSON
metadata schema:
diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go
new file mode 100644
index 000000000..202799cbf
--- /dev/null
+++ b/integration-cli/check_test.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/go-check/check"
+)
+
+func Test(t *testing.T) {
+ check.TestingT(t)
+}
+
+type TimerSuite struct {
+ start time.Time
+}
+
+func (s *TimerSuite) SetUpTest(c *check.C) {
+ s.start = time.Now()
+}
+
+func (s *TimerSuite) TearDownTest(c *check.C) {
+ fmt.Printf("%-60s%.2f\n", c.TestName(), time.Since(s.start).Seconds())
+}
+
+func init() {
+ check.Suite(&DockerSuite{})
+}
+
+type DockerSuite struct {
+ TimerSuite
+}
+
+func (s *DockerSuite) TearDownTest(c *check.C) {
+ deleteAllContainers()
+ deleteAllImages()
+ s.TimerSuite.TearDownTest(c)
+}
+
+func init() {
+ check.Suite(&DockerRegistrySuite{
+ ds: &DockerSuite{},
+ })
+}
+
+type DockerRegistrySuite struct {
+ ds *DockerSuite
+ reg *testRegistryV2
+}
+
+func (s *DockerRegistrySuite) SetUpTest(c *check.C) {
+ s.reg = setupRegistry(c)
+ s.ds.SetUpTest(c)
+}
+
+func (s *DockerRegistrySuite) TearDownTest(c *check.C) {
+ s.reg.Close()
+ s.ds.TearDownTest(c)
+}
+
+func init() {
+ check.Suite(&DockerDaemonSuite{
+ ds: &DockerSuite{},
+ })
+}
+
+type DockerDaemonSuite struct {
+ ds *DockerSuite
+ d *Daemon
+}
+
+func (s *DockerDaemonSuite) SetUpTest(c *check.C) {
+ s.d = NewDaemon(c)
+ s.ds.SetUpTest(c)
+}
+
+func (s *DockerDaemonSuite) TearDownTest(c *check.C) {
+ s.d.Stop()
+ s.ds.TearDownTest(c)
+}
diff --git a/integration-cli/docker_api_attach_test.go b/integration-cli/docker_api_attach_test.go
index b16a7bb2f..c784d5c36 100644
--- a/integration-cli/docker_api_attach_test.go
+++ b/integration-cli/docker_api_attach_test.go
@@ -3,64 +3,77 @@ package main
import (
"bytes"
"os/exec"
- "testing"
+ "strings"
"time"
+ "github.com/go-check/check"
+
"code.google.com/p/go.net/websocket"
)
-func TestGetContainersAttachWebsocket(t *testing.T) {
+func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-dit", "busybox", "cat")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- defer deleteAllContainers()
rwc, err := sockConn(time.Duration(10 * time.Second))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
config, err := websocket.NewConfig(
"/containers/"+cleanedContainerID+"/attach/ws?stream=1&stdin=1&stdout=1&stderr=1",
"http://localhost",
)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
ws, err := websocket.NewClient(config, rwc)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ws.Close()
expected := []byte("hello")
actual := make([]byte, len(expected))
- outChan := make(chan string)
+
+ outChan := make(chan error)
go func() {
- if _, err := ws.Read(actual); err != nil {
- t.Fatal(err)
- }
- outChan <- "done"
+ _, err := ws.Read(actual)
+ outChan <- err
+ close(outChan)
}()
- inChan := make(chan string)
+ inChan := make(chan error)
go func() {
- if _, err := ws.Write(expected); err != nil {
- t.Fatal(err)
- }
- inChan <- "done"
+ _, err := ws.Write(expected)
+ inChan <- err
+ close(inChan)
}()
- <-inChan
- <-outChan
-
- if !bytes.Equal(expected, actual) {
- t.Fatal("Expected output on websocket to match input")
+ select {
+ case err := <-inChan:
+ if err != nil {
+ c.Fatal(err)
+ }
+ case <-time.After(5 * time.Second):
+ c.Fatal("Timeout writing to ws")
}
- logDone("container attach websocket - can echo input via cat")
+ select {
+ case err := <-outChan:
+ if err != nil {
+ c.Fatal(err)
+ }
+ case <-time.After(5 * time.Second):
+ c.Fatal("Timeout reading from ws")
+ }
+
+ if !bytes.Equal(expected, actual) {
+ c.Fatal("Expected output on websocket to match input")
+ }
}
diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go
index ea2f2450a..daec87c49 100644
--- a/integration-cli/docker_api_containers_test.go
+++ b/integration-cli/docker_api_containers_test.go
@@ -1,70 +1,67 @@
package main
import (
+ "archive/tar"
"bytes"
"encoding/json"
"io"
+ "net/http"
+ "net/http/httputil"
+ "os"
"os/exec"
"strings"
- "testing"
"time"
"github.com/docker/docker/api/types"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/runconfig"
+ "github.com/go-check/check"
)
-func TestContainerApiGetAll(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestContainerApiGetAll(c *check.C) {
startCount, err := getContainerCount()
if err != nil {
- t.Fatalf("Cannot query container count: %v", err)
+ c.Fatalf("Cannot query container count: %v", err)
}
name := "getall"
runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("Error on container creation: %v, output: %q", err, out)
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
}
- body, err := sockRequest("GET", "/containers/json?all=1", nil)
- if err != nil {
- t.Fatalf("GET all containers sockRequest failed: %v", err)
- }
+ status, body, err := sockRequest("GET", "/containers/json?all=1", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
var inspectJSON []struct {
Names []string
}
if err = json.Unmarshal(body, &inspectJSON); err != nil {
- t.Fatalf("unable to unmarshal response body: %v", err)
+ c.Fatalf("unable to unmarshal response body: %v", err)
}
if len(inspectJSON) != startCount+1 {
- t.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount)
+ c.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount)
}
if actual := inspectJSON[0].Names[0]; actual != "/"+name {
- t.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual)
+ c.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual)
}
-
- logDone("container REST API - check GET json/all=1")
}
-func TestContainerApiGetExport(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestContainerApiGetExport(c *check.C) {
name := "exportcontainer"
runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("Error on container creation: %v, output: %q", err, out)
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
}
- body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
- if err != nil {
- t.Fatalf("GET containers/export sockRequest failed: %v", err)
- }
+ status, body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
found := false
for tarReader := tar.NewReader(bytes.NewReader(body)); ; {
@@ -73,7 +70,7 @@ func TestContainerApiGetExport(t *testing.T) {
if err == io.EOF {
break
}
- t.Fatal(err)
+ c.Fatal(err)
}
if h.Name == "test" {
found = true
@@ -82,33 +79,28 @@ func TestContainerApiGetExport(t *testing.T) {
}
if !found {
- t.Fatalf("The created test file has not been found in the exported image")
+ c.Fatalf("The created test file has not been found in the exported image")
}
-
- logDone("container REST API - check GET containers/export")
}
-func TestContainerApiGetChanges(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestContainerApiGetChanges(c *check.C) {
name := "changescontainer"
runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "rm", "/etc/passwd")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("Error on container creation: %v, output: %q", err, out)
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
}
- body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
- if err != nil {
- t.Fatalf("GET containers/changes sockRequest failed: %v", err)
- }
+ status, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
changes := []struct {
Kind int
Path string
}{}
if err = json.Unmarshal(body, &changes); err != nil {
- t.Fatalf("unable to unmarshal response body: %v", err)
+ c.Fatalf("unable to unmarshal response body: %v", err)
}
// Check the changelog for removal of /etc/passwd
@@ -119,56 +111,50 @@ func TestContainerApiGetChanges(t *testing.T) {
}
}
if !success {
- t.Fatalf("/etc/passwd has been removed but is not present in the diff")
+ c.Fatalf("/etc/passwd has been removed but is not present in the diff")
}
-
- logDone("container REST API - check GET containers/changes")
}
-func TestContainerApiStartVolumeBinds(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestContainerApiStartVolumeBinds(c *check.C) {
name := "testing"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{"/tmp": {}},
}
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.Fatal(err)
- }
+ status, _, err := sockRequest("POST", "/containers/create?name="+name, config)
+ c.Assert(status, check.Equals, http.StatusCreated)
+ c.Assert(err, check.IsNil)
bindPath := randomUnixTmpDirPath("test")
config = map[string]interface{}{
"Binds": []string{bindPath + ":/tmp"},
}
- if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.Fatal(err)
- }
+ status, _, err = sockRequest("POST", "/containers/"+name+"/start", config)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
pth, err := inspectFieldMap(name, "Volumes", "/tmp")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if pth != bindPath {
- t.Fatalf("expected volume host path to be %s, got %s", bindPath, pth)
+ c.Fatalf("expected volume host path to be %s, got %s", bindPath, pth)
}
-
- logDone("container REST API - check volume binds on start")
}
// Test for GH#10618
-func TestContainerApiStartDupVolumeBinds(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) {
name := "testdups"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{"/tmp": {}},
}
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.Fatal(err)
- }
+ status, _, err := sockRequest("POST", "/containers/create?name="+name, config)
+ c.Assert(status, check.Equals, http.StatusCreated)
+ c.Assert(err, check.IsNil)
bindPath1 := randomUnixTmpDirPath("test1")
bindPath2 := randomUnixTmpDirPath("test2")
@@ -176,177 +162,158 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) {
config = map[string]interface{}{
"Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"},
}
- if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
- t.Fatal("expected container start to fail when duplicate volume binds to same container path")
- } else {
- if !strings.Contains(string(body), "Duplicate volume") {
- t.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err)
- }
- }
+ status, body, err := sockRequest("POST", "/containers/"+name+"/start", config)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
- logDone("container REST API - check for duplicate volume binds error on start")
+ if !strings.Contains(string(body), "Duplicate bind") {
+ c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err)
+ }
}
-func TestContainerApiStartVolumesFrom(t *testing.T) {
- defer deleteAllContainers()
+
+func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
volName := "voltst"
volPath := "/tmp"
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- name := "testing"
+ name := "TestContainerApiStartDupVolumeBinds"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{volPath: {}},
}
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.Fatal(err)
- }
+ status, _, err := sockRequest("POST", "/containers/create?name="+name, config)
+ c.Assert(status, check.Equals, http.StatusCreated)
+ c.Assert(err, check.IsNil)
config = map[string]interface{}{
"VolumesFrom": []string{volName},
}
- if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.Fatal(err)
- }
+ status, _, err = sockRequest("POST", "/containers/"+name+"/start", config)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
pth, err := inspectFieldMap(name, "Volumes", volPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
pth2, err := inspectFieldMap(volName, "Volumes", volPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if pth != pth2 {
- t.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
+ c.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
}
-
- logDone("container REST API - check VolumesFrom on start")
}
-// Ensure that volumes-from has priority over binds/anything else
-// This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start
-func TestVolumesFromHasPriority(t *testing.T) {
- defer deleteAllContainers()
- volName := "voltst"
- volPath := "/tmp"
-
- if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
- t.Fatal(out, err)
- }
-
- name := "testing"
- config := map[string]interface{}{
- "Image": "busybox",
- "Volumes": map[string]struct{}{volPath: {}},
- }
-
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.Fatal(err)
- }
-
- bindPath := randomUnixTmpDirPath("test")
- config = map[string]interface{}{
- "VolumesFrom": []string{volName},
- "Binds": []string{bindPath + ":/tmp"},
- }
- if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.Fatal(err)
- }
-
- pth, err := inspectFieldMap(name, "Volumes", volPath)
- if err != nil {
- t.Fatal(err)
- }
- pth2, err := inspectFieldMap(volName, "Volumes", volPath)
- if err != nil {
- t.Fatal(err)
- }
-
- if pth != pth2 {
- t.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
- }
-
- logDone("container REST API - check VolumesFrom has priority")
-}
-
-func TestGetContainerStats(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestGetContainerStats(c *check.C) {
var (
name = "statscontainer"
runCmd = exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top")
)
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("Error on container creation: %v, output: %q", err, out)
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
}
type b struct {
- body []byte
- err error
+ status int
+ body []byte
+ err error
}
bc := make(chan b, 1)
go func() {
- body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
- bc <- b{body, err}
+ status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
+ bc <- b{status, body, err}
}()
// allow some time to stream the stats from the container
time.Sleep(4 * time.Second)
if _, err := runCommand(exec.Command(dockerBinary, "rm", "-f", name)); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// collect the results from the stats stream or timeout and fail
// if the stream was not disconnected.
select {
case <-time.After(2 * time.Second):
- t.Fatal("stream was not closed after container was removed")
+ c.Fatal("stream was not closed after container was removed")
case sr := <-bc:
- if sr.err != nil {
- t.Fatal(sr.err)
- }
+ c.Assert(sr.err, check.IsNil)
+ c.Assert(sr.status, check.Equals, http.StatusOK)
dec := json.NewDecoder(bytes.NewBuffer(sr.body))
var s *types.Stats
// decode only one object from the stream
if err := dec.Decode(&s); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}
- logDone("container REST API - check GET containers/stats")
}
-func TestGetStoppedContainerStats(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestContainerStatsRmRunning(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
+ id := strings.TrimSpace(out)
+
+ buf := &channelBuffer{make(chan []byte, 1)}
+ defer buf.Close()
+ chErr := make(chan error)
+ go func() {
+ _, body, err := sockRequestRaw("GET", "/containers/"+id+"/stats?stream=1", nil, "application/json")
+ if err != nil {
+ chErr <- err
+ }
+ defer body.Close()
+ _, err = io.Copy(buf, body)
+ chErr <- err
+ }()
+ defer func() {
+ c.Assert(<-chErr, check.IsNil)
+ }()
+
+ b := make([]byte, 32)
+ // make sure we've got some stats
+ _, err := buf.ReadTimeout(b, 2*time.Second)
+ c.Assert(err, check.IsNil)
+
+ // Now remove without `-f` and make sure we are still pulling stats
+ _, err = runCommand(exec.Command(dockerBinary, "rm", id))
+ c.Assert(err, check.Not(check.IsNil), check.Commentf("rm should have failed but didn't"))
+ _, err = buf.ReadTimeout(b, 2*time.Second)
+ c.Assert(err, check.IsNil)
+ dockerCmd(c, "rm", "-f", id)
+
+ _, err = buf.ReadTimeout(b, 2*time.Second)
+ c.Assert(err, check.Not(check.IsNil))
+}
+
+func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) {
+ // TODO: this test does nothing because we are c.Assert'ing in goroutine
var (
name = "statscontainer"
runCmd = exec.Command(dockerBinary, "create", "--name", name, "busybox", "top")
)
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("Error on container creation: %v, output: %q", err, out)
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
}
go func() {
// We'll never get return for GET stats from sockRequest as of now,
// just send request and see if panic or error would happen on daemon side.
- _, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
- if err != nil {
- t.Fatal(err)
- }
+ status, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
}()
// allow some time to send request and let daemon deal with it
time.Sleep(1 * time.Second)
-
- logDone("container REST API - check GET stopped containers/stats")
}
-func TestBuildApiDockerfilePath(t *testing.T) {
+func (s *DockerSuite) TestBuildApiDockerfilePath(c *check.C) {
// Test to make sure we stop people from trying to leave the
// build context when specifying the path to the dockerfile
buffer := new(bytes.Buffer)
@@ -358,28 +325,30 @@ func TestBuildApiDockerfilePath(t *testing.T) {
Name: "Dockerfile",
Size: int64(len(dockerfile)),
}); err != nil {
- t.Fatalf("failed to write tar file header: %v", err)
+ c.Fatalf("failed to write tar file header: %v", err)
}
if _, err := tw.Write(dockerfile); err != nil {
- t.Fatalf("failed to write tar file content: %v", err)
+ c.Fatalf("failed to write tar file content: %v", err)
}
if err := tw.Close(); err != nil {
- t.Fatalf("failed to close tar archive: %v", err)
+ c.Fatalf("failed to close tar archive: %v", err)
}
- out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
- if err == nil {
- t.Fatalf("Build was supposed to fail: %s", out)
+ res, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
+
+ out, err := readBody(body)
+ if err != nil {
+ c.Fatal(err)
}
if !strings.Contains(string(out), "must be within the build context") {
- t.Fatalf("Didn't complain about leaving build context: %s", out)
+ c.Fatalf("Didn't complain about leaving build context: %s", out)
}
-
- logDone("container REST API - check build w/bad Dockerfile path")
}
-func TestBuildApiDockerFileRemote(t *testing.T) {
+func (s *DockerSuite) TestBuildApiDockerFileRemote(c *check.C) {
server, err := fakeStorage(map[string]string{
"testD": `FROM busybox
COPY * /tmp/
@@ -387,13 +356,17 @@ RUN find / -name ba*
RUN find /tmp/`,
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
- buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
+ res, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
+ c.Assert(res.StatusCode, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ buf, err := readBody(body)
if err != nil {
- t.Fatalf("Build failed: %s", err)
+ c.Fatal(err)
}
// Make sure Dockerfile exists.
@@ -401,36 +374,36 @@ RUN find /tmp/`,
out := string(buf)
if !strings.Contains(out, "/tmp/Dockerfile") ||
strings.Contains(out, "baz") {
- t.Fatalf("Incorrect output: %s", out)
+ c.Fatalf("Incorrect output: %s", out)
}
-
- logDone("container REST API - check build with -f from remote")
}
-func TestBuildApiLowerDockerfile(t *testing.T) {
+func (s *DockerSuite) TestBuildApiLowerDockerfile(c *check.C) {
git, err := fakeGIT("repo", map[string]string{
"dockerfile": `FROM busybox
RUN echo from dockerfile`,
}, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer git.Close()
- buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+ res, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+ c.Assert(res.StatusCode, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ buf, err := readBody(body)
if err != nil {
- t.Fatalf("Build failed: %s\n%q", err, buf)
+ c.Fatal(err)
}
out := string(buf)
if !strings.Contains(out, "from dockerfile") {
- t.Fatalf("Incorrect output: %s", out)
+ c.Fatalf("Incorrect output: %s", out)
}
-
- logDone("container REST API - check build with lower dockerfile")
}
-func TestBuildApiBuildGitWithF(t *testing.T) {
+func (s *DockerSuite) TestBuildApiBuildGitWithF(c *check.C) {
git, err := fakeGIT("repo", map[string]string{
"baz": `FROM busybox
RUN echo from baz`,
@@ -438,26 +411,28 @@ RUN echo from baz`,
RUN echo from Dockerfile`,
}, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer git.Close()
// Make sure it tries to 'dockerfile' query param value
- buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
+ res, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
+ c.Assert(res.StatusCode, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ buf, err := readBody(body)
if err != nil {
- t.Fatalf("Build failed: %s\n%q", err, buf)
+ c.Fatal(err)
}
out := string(buf)
if !strings.Contains(out, "from baz") {
- t.Fatalf("Incorrect output: %s", out)
+ c.Fatalf("Incorrect output: %s", out)
}
-
- logDone("container REST API - check build from git w/F")
}
-func TestBuildApiDoubleDockerfile(t *testing.T) {
- testRequires(t, UnixCli) // dockerfile overwrites Dockerfile on Windows
+func (s *DockerSuite) TestBuildApiDoubleDockerfile(c *check.C) {
+ testRequires(c, UnixCli) // dockerfile overwrites Dockerfile on Windows
git, err := fakeGIT("repo", map[string]string{
"Dockerfile": `FROM busybox
RUN echo from Dockerfile`,
@@ -465,25 +440,27 @@ RUN echo from Dockerfile`,
RUN echo from dockerfile`,
}, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer git.Close()
// Make sure it tries to 'dockerfile' query param value
- buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+ res, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+ c.Assert(res.StatusCode, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ buf, err := readBody(body)
if err != nil {
- t.Fatalf("Build failed: %s", err)
+ c.Fatal(err)
}
out := string(buf)
if !strings.Contains(out, "from Dockerfile") {
- t.Fatalf("Incorrect output: %s", out)
+ c.Fatalf("Incorrect output: %s", out)
}
-
- logDone("container REST API - check build with two dockerfiles")
}
-func TestBuildApiDockerfileSymlink(t *testing.T) {
+func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) {
// Test to make sure we stop people from trying to leave the
// build context when specifying a symlink as the path to the dockerfile
buffer := new(bytes.Buffer)
@@ -495,15 +472,19 @@ func TestBuildApiDockerfileSymlink(t *testing.T) {
Typeflag: tar.TypeSymlink,
Linkname: "/etc/passwd",
}); err != nil {
- t.Fatalf("failed to write tar file header: %v", err)
+ c.Fatalf("failed to write tar file header: %v", err)
}
if err := tw.Close(); err != nil {
- t.Fatalf("failed to close tar archive: %v", err)
+ c.Fatalf("failed to close tar archive: %v", err)
}
- out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
- if err == nil {
- t.Fatalf("Build was supposed to fail: %s", out)
+ res, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
+
+ out, err := readBody(body)
+ if err != nil {
+ c.Fatal(err)
}
// The reason the error is "Cannot locate specified Dockerfile" is because
@@ -511,45 +492,843 @@ func TestBuildApiDockerfileSymlink(t *testing.T) {
// Dockerfile -> /etc/passwd becomes etc/passwd from the context which is
// a nonexistent file.
if !strings.Contains(string(out), "Cannot locate specified Dockerfile: Dockerfile") {
- t.Fatalf("Didn't complain about leaving build context: %s", out)
+ c.Fatalf("Didn't complain about leaving build context: %s", out)
}
-
- logDone("container REST API - check build w/bad Dockerfile symlink path")
}
// #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume
-func TestPostContainerBindNormalVolume(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) {
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox"))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
fooDir, err := inspectFieldMap("one", "Volumes", "/foo")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox"))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
- _, err = sockRequest("POST", "/containers/two/start", bindSpec)
- if err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.Fatal(err)
- }
+ status, _, err := sockRequest("POST", "/containers/two/start", bindSpec)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
fooDir2, err := inspectFieldMap("two", "Volumes", "/foo")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if fooDir2 != fooDir {
- t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2)
+ c.Fatalf("expected volume path to be %s, got: %s", fooDir, fooDir2)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiPause(c *check.C) {
+ defer unpauseAllContainers()
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "30")
+ out, _, err := runCommandWithOutput(runCmd)
+
+ if err != nil {
+ c.Fatalf("failed to create a container: %s, %v", out, err)
+ }
+ ContainerID := strings.TrimSpace(out)
+
+ status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ pausedContainers, err := getSliceOfPausedContainers()
+
+ if err != nil {
+ c.Fatalf("error thrown while checking if containers were paused: %v", err)
}
- logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume")
+ if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] {
+ c.Fatalf("there should be one paused container and not %d", len(pausedContainers))
+ }
+
+ status, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ pausedContainers, err = getSliceOfPausedContainers()
+
+ if err != nil {
+ c.Fatalf("error thrown while checking if containers were paused: %v", err)
+ }
+
+ if pausedContainers != nil {
+ c.Fatalf("There should be no paused container.")
+ }
+}
+
+func (s *DockerSuite) TestContainerApiTop(c *check.C) {
+ out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "top").CombinedOutput()
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ id := strings.TrimSpace(string(out))
+ if err := waitRun(id); err != nil {
+ c.Fatal(err)
+ }
+
+ type topResp struct {
+ Titles []string
+ Processes [][]string
+ }
+ var top topResp
+ status, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ if err := json.Unmarshal(b, &top); err != nil {
+ c.Fatal(err)
+ }
+
+ if len(top.Titles) != 11 {
+ c.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles)
+ }
+
+ if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" {
+ c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles)
+ }
+ if len(top.Processes) != 2 {
+ c.Fatalf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes)
+ }
+ if top.Processes[0][10] != "/bin/sh -c top" {
+ c.Fatalf("expected `/bin/sh -c top`, found: %s", top.Processes[0][10])
+ }
+ if top.Processes[1][10] != "top" {
+ c.Fatalf("expected `top`, found: %s", top.Processes[1][10])
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCommit(c *check.C) {
+ cName := "testapicommit"
+ out, err := exec.Command(dockerBinary, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput()
+ if err != nil {
+ c.Fatal(err, out)
+ }
+
+ name := "TestContainerApiCommit"
+ status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+cName, nil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+ c.Assert(err, check.IsNil)
+
+ type resp struct {
+ Id string
+ }
+ var img resp
+ if err := json.Unmarshal(b, &img); err != nil {
+ c.Fatal(err)
+ }
+
+ cmd, err := inspectField(img.Id, "Config.Cmd")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if cmd != "{[/bin/sh -c touch /test]}" {
+ c.Fatalf("got wrong Cmd from commit: %q", cmd)
+ }
+ // sanity check, make sure the image is what we think it is
+ out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput()
+ if err != nil {
+ c.Fatalf("error checking committed image: %v - %q", err, string(out))
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreate(c *check.C) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"},
+ }
+
+ status, b, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(status, check.Equals, http.StatusCreated)
+ c.Assert(err, check.IsNil)
+
+ type createResp struct {
+ Id string
+ }
+ var container createResp
+ if err := json.Unmarshal(b, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := exec.Command(dockerBinary, "start", "-a", container.Id).CombinedOutput()
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ if strings.TrimSpace(string(out)) != "/test" {
+ c.Fatalf("expected output `/test`, got %q", out)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateWithHostName(c *check.C) {
+ hostName := "test-host"
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "Hostname": hostName,
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJSON types.ContainerJSON
+ if err := json.Unmarshal(body, &containerJSON); err != nil {
+ c.Fatal(err)
+ }
+
+ if containerJSON.Config.Hostname != hostName {
+ c.Fatalf("Mismatched Hostname, Expected %s, Actual: %s ", hostName, containerJSON.Config.Hostname)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateWithDomainName(c *check.C) {
+ domainName := "test-domain"
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "Domainname": domainName,
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJSON types.ContainerJSON
+ if err := json.Unmarshal(body, &containerJSON); err != nil {
+ c.Fatal(err)
+ }
+
+ if containerJSON.Config.Domainname != domainName {
+ c.Fatalf("Mismatched Domainname, Expected %s, Actual: %s ", domainName, containerJSON.Config.Domainname)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateNetworkMode(c *check.C) {
+ UtilCreateNetworkMode(c, "host")
+ UtilCreateNetworkMode(c, "bridge")
+ UtilCreateNetworkMode(c, "container:web1")
+}
+
+func UtilCreateNetworkMode(c *check.C, networkMode string) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "HostConfig": map[string]interface{}{"NetworkMode": networkMode},
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJSON types.ContainerJSON
+ if err := json.Unmarshal(body, &containerJSON); err != nil {
+ c.Fatal(err)
+ }
+
+ if containerJSON.HostConfig.NetworkMode != runconfig.NetworkMode(networkMode) {
+ c.Fatalf("Mismatched NetworkMode, Expected %s, Actual: %s ", networkMode, containerJSON.HostConfig.NetworkMode)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateWithCpuSharesCpuset(c *check.C) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "CpuShares": 512,
+ "CpusetCpus": "0,1",
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJson types.ContainerJSON
+
+ c.Assert(json.Unmarshal(body, &containerJson), check.IsNil)
+
+ out, err := inspectField(containerJson.Id, "HostConfig.CpuShares")
+ c.Assert(err, check.IsNil)
+ c.Assert(out, check.Equals, "512")
+
+ outCpuset, errCpuset := inspectField(containerJson.Id, "HostConfig.CpusetCpus")
+ c.Assert(errCpuset, check.IsNil, check.Commentf("Output: %s", outCpuset))
+ c.Assert(outCpuset, check.Equals, "0,1")
+}
+
+func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ }
+
+ create := func(ct string) (*http.Response, io.ReadCloser, error) {
+ jsonData := bytes.NewBuffer(nil)
+ if err := json.NewEncoder(jsonData).Encode(config); err != nil {
+ c.Fatal(err)
+ }
+ return sockRequestRaw("POST", "/containers/create", jsonData, ct)
+ }
+
+ // Try with no content-type
+ res, body, err := create("")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ body.Close()
+
+ // Try with wrong content-type
+ res, body, err = create("application/xml")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ body.Close()
+
+ // now application/json
+ res, body, err = create("application/json")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusCreated)
+ body.Close()
+}
+
+// Issue 7941 - test to make sure a "null" in JSON is just ignored.
+// W/o this fix a null in JSON would be parsed into a string var as "null"
+func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) {
+ config := `{
+ "Hostname":"",
+ "Domainname":"",
+ "Memory":0,
+ "MemorySwap":0,
+ "CpuShares":0,
+ "Cpuset":null,
+ "AttachStdin":true,
+ "AttachStdout":true,
+ "AttachStderr":true,
+ "PortSpecs":null,
+ "ExposedPorts":{},
+ "Tty":true,
+ "OpenStdin":true,
+ "StdinOnce":true,
+ "Env":[],
+ "Cmd":"ls",
+ "Image":"busybox",
+ "Volumes":{},
+ "WorkingDir":"",
+ "Entrypoint":null,
+ "NetworkDisabled":false,
+ "OnBuild":null}`
+
+ res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json")
+ c.Assert(res.StatusCode, check.Equals, http.StatusCreated)
+ c.Assert(err, check.IsNil)
+
+ b, err := readBody(body)
+ if err != nil {
+ c.Fatal(err)
+ }
+ type createResp struct {
+ Id string
+ }
+ var container createResp
+ if err := json.Unmarshal(b, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := inspectField(container.Id, "HostConfig.CpusetCpus")
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ if out != "" {
+ c.Fatalf("expected empty string, got %q", out)
+ }
+
+ outMemory, errMemory := inspectField(container.Id, "HostConfig.Memory")
+ c.Assert(outMemory, check.Equals, "0")
+ if errMemory != nil {
+ c.Fatal(errMemory, outMemory)
+ }
+ outMemorySwap, errMemorySwap := inspectField(container.Id, "HostConfig.MemorySwap")
+ c.Assert(outMemorySwap, check.Equals, "0")
+ if errMemorySwap != nil {
+ c.Fatal(errMemorySwap, outMemorySwap)
+ }
+}
+
+func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) {
+ config := `{
+ "Image": "busybox",
+ "Cmd": "ls",
+ "OpenStdin": true,
+ "CpuShares": 100,
+ "Memory": 524287
+ }`
+
+ res, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json")
+ b, err2 := readBody(body)
+ if err2 != nil {
+ c.Fatal(err2)
+ }
+
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true)
+}
+
+func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox"))
+ if err != nil {
+ c.Fatal(err, out)
+ }
+
+ containerID := strings.TrimSpace(out)
+
+ config := `{
+ "CpuShares": 100,
+ "Memory": 524287
+ }`
+
+ res, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json")
+ b, err2 := readBody(body)
+ if err2 != nil {
+ c.Fatal(err2)
+ }
+
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true)
+}
+
+func (s *DockerSuite) TestContainerApiRename(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "--name", "TestContainerApiRename", "-d", "busybox", "sh")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ containerID := strings.TrimSpace(out)
+ newName := "TestContainerApiRenameNew"
+ statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/rename?name="+newName, nil)
+
+ // 204 No Content is expected, not 200
+ c.Assert(statusCode, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ name, err := inspectField(containerID, "Name")
+ if name != "/"+newName {
+ c.Fatalf("Failed to rename container, expected %v, got %v. Container rename API failed", newName, name)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiKill(c *check.C) {
+ name := "test-api-kill"
+ runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
+ }
+
+ status, _, err := sockRequest("POST", "/containers/"+name+"/kill", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ state, err := inspectField(name, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if state != "false" {
+ c.Fatalf("got wrong State from container %s: %q", name, state)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiRestart(c *check.C) {
+ name := "test-api-restart"
+ runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
+ }
+
+ status, _, err := sockRequest("POST", "/containers/"+name+"/restart?t=1", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ if err := waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 5); err != nil {
+ c.Fatal(err)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiRestartNotimeoutParam(c *check.C) {
+ name := "test-api-restart-no-timeout-param"
+ runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
+ }
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ status, _, err := sockRequest("POST", "/containers/"+name+"/restart", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ if err := waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 5); err != nil {
+ c.Fatal(err)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiStart(c *check.C) {
+ name := "testing-start"
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "Cmd": []string{"/bin/sh", "-c", "/bin/top"},
+ "OpenStdin": true,
+ }
+
+ status, _, err := sockRequest("POST", "/containers/create?name="+name, config)
+ c.Assert(status, check.Equals, http.StatusCreated)
+ c.Assert(err, check.IsNil)
+
+ conf := make(map[string]interface{})
+ status, _, err = sockRequest("POST", "/containers/"+name+"/start", conf)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ // second call to start should give 304
+ status, _, err = sockRequest("POST", "/containers/"+name+"/start", conf)
+ c.Assert(status, check.Equals, http.StatusNotModified)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerSuite) TestContainerApiStop(c *check.C) {
+ name := "test-api-stop"
+ runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
+ }
+
+ status, _, err := sockRequest("POST", "/containers/"+name+"/stop?t=1", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ if err := waitInspect(name, "{{ .State.Running }}", "false", 5); err != nil {
+ c.Fatal(err)
+ }
+
+ // second call to start should give 304
+ status, _, err = sockRequest("POST", "/containers/"+name+"/stop?t=1", nil)
+ c.Assert(status, check.Equals, http.StatusNotModified)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerSuite) TestContainerApiWait(c *check.C) {
+ name := "test-api-wait"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sleep", "5")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("Error on container creation: %v, output: %q", err, out)
+ }
+
+ status, body, err := sockRequest("POST", "/containers/"+name+"/wait", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ if err := waitInspect(name, "{{ .State.Running }}", "false", 5); err != nil {
+ c.Fatal(err)
+ }
+
+ var waitres types.ContainerWaitResponse
+ if err := json.Unmarshal(body, &waitres); err != nil {
+ c.Fatalf("unable to unmarshal response body: %v", err)
+ }
+
+ if waitres.StatusCode != 0 {
+ c.Fatalf("Expected wait response StatusCode to be 0, got %d", waitres.StatusCode)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCopy(c *check.C) {
+ name := "test-container-api-copy"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test.txt")
+ _, err := runCommand(runCmd)
+ c.Assert(err, check.IsNil)
+
+ postData := types.CopyConfig{
+ Resource: "/test.txt",
+ }
+
+ status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ found := false
+ for tarReader := tar.NewReader(bytes.NewReader(body)); ; {
+ h, err := tarReader.Next()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ c.Fatal(err)
+ }
+ if h.Name == "test.txt" {
+ found = true
+ break
+ }
+ }
+ c.Assert(found, check.Equals, true)
+}
+
+func (s *DockerSuite) TestContainerApiCopyResourcePathEmpty(c *check.C) {
+ name := "test-container-api-copy-resource-empty"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test.txt")
+ _, err := runCommand(runCmd)
+ c.Assert(err, check.IsNil)
+
+ postData := types.CopyConfig{
+ Resource: "",
+ }
+
+ status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(string(body), check.Matches, "Path cannot be empty\n")
+}
+
+func (s *DockerSuite) TestContainerApiCopyResourcePathNotFound(c *check.C) {
+ name := "test-container-api-copy-resource-not-found"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox")
+ _, err := runCommand(runCmd)
+ c.Assert(err, check.IsNil)
+
+ postData := types.CopyConfig{
+ Resource: "/notexist",
+ }
+
+ status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(string(body), check.Matches, "Could not find the file /notexist in container "+name+"\n")
+}
+
+func (s *DockerSuite) TestContainerApiCopyContainerNotFound(c *check.C) {
+ postData := types.CopyConfig{
+ Resource: "/something",
+ }
+
+ status, _, err := sockRequest("POST", "/containers/notexists/copy", postData)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusNotFound)
+}
+
+func (s *DockerSuite) TestContainerApiDelete(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ stopCmd := exec.Command(dockerBinary, "stop", id)
+ _, err = runCommand(stopCmd)
+ c.Assert(err, check.IsNil)
+
+ status, _, err := sockRequest("DELETE", "/containers/"+id, nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+}
+
+func (s *DockerSuite) TestContainerApiDeleteNotExist(c *check.C) {
+ status, body, err := sockRequest("DELETE", "/containers/doesnotexist", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusNotFound)
+ c.Assert(string(body), check.Matches, "no such id: doesnotexist\n")
+}
+
+func (s *DockerSuite) TestContainerApiDeleteForce(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ status, _, err := sockRequest("DELETE", "/containers/"+id+"?force=1", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+}
+
+func (s *DockerSuite) TestContainerApiDeleteRemoveLinks(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "tlink1", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ runCmd = exec.Command(dockerBinary, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ id2 := strings.TrimSpace(out)
+ c.Assert(waitRun(id2), check.IsNil)
+
+ links, err := inspectFieldJSON(id2, "HostConfig.Links")
+ c.Assert(err, check.IsNil)
+
+ if links != "[\"/tlink1:/tlink2/tlink1\"]" {
+ c.Fatal("expected to have links between containers")
+ }
+
+ status, _, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+
+ linksPostRm, err := inspectFieldJSON(id2, "HostConfig.Links")
+ c.Assert(err, check.IsNil)
+
+ if linksPostRm != "null" {
+ c.Fatal("call to api deleteContainer links should have removed the specified links")
+ }
+}
+
+func (s *DockerSuite) TestContainerApiDeleteConflict(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ status, _, err := sockRequest("DELETE", "/containers/"+id, nil)
+ c.Assert(status, check.Equals, http.StatusConflict)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerSuite) TestContainerApiDeleteRemoveVolume(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-v", "/testvolume", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ vol, err := inspectFieldMap(id, "Volumes", "/testvolume")
+ c.Assert(err, check.IsNil)
+
+ _, err = os.Stat(vol)
+ c.Assert(err, check.IsNil)
+
+ status, _, err := sockRequest("DELETE", "/containers/"+id+"?v=1&force=1", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ if _, err := os.Stat(vol); !os.IsNotExist(err) {
+ c.Fatalf("expected to get ErrNotExist error, got %v", err)
+ }
+}
+
+// Regression test for https://github.com/docker/docker/issues/6231
+func (s *DockerSuite) TestContainersApiChunkedEncoding(c *check.C) {
+ out, _ := dockerCmd(c, "create", "-v", "/foo", "busybox", "true")
+ id := strings.TrimSpace(out)
+
+ conn, err := sockConn(time.Duration(10 * time.Second))
+ if err != nil {
+ c.Fatal(err)
+ }
+ client := httputil.NewClientConn(conn, nil)
+ defer client.Close()
+
+ bindCfg := strings.NewReader(`{"Binds": ["/tmp:/foo"]}`)
+ req, err := http.NewRequest("POST", "/containers/"+id+"/start", bindCfg)
+ if err != nil {
+ c.Fatal(err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+ // This is a cheat to make the http request do chunked encoding
+ // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
+ // https://golang.org/src/pkg/net/http/request.go?s=11980:12172
+ req.ContentLength = -1
+
+ resp, err := client.Do(req)
+ if err != nil {
+ c.Fatalf("error starting container with chunked encoding: %v", err)
+ }
+ resp.Body.Close()
+ if resp.StatusCode != 204 {
+ c.Fatalf("expected status code 204, got %d", resp.StatusCode)
+ }
+
+ out, err = inspectFieldJSON(id, "HostConfig.Binds")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ var binds []string
+ if err := json.NewDecoder(strings.NewReader(out)).Decode(&binds); err != nil {
+ c.Fatal(err)
+ }
+ if len(binds) != 1 {
+ c.Fatalf("got unexpected binds: %v", binds)
+ }
+
+ expected := "/tmp:/foo"
+ if binds[0] != expected {
+ c.Fatalf("got incorrect bind spec, wanted %s, got: %s", expected, binds[0])
+ }
+}
+
+func (s *DockerSuite) TestPostContainerStop(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ containerID := strings.TrimSpace(out)
+ c.Assert(waitRun(containerID), check.IsNil)
+
+ statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/stop", nil)
+
+ // 204 No Content is expected, not 200
+ c.Assert(statusCode, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ if err := waitInspect(containerID, "{{ .State.Running }}", "false", 5); err != nil {
+ c.Fatal(err)
+ }
}
diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go
new file mode 100644
index 000000000..ab753d8ec
--- /dev/null
+++ b/integration-cli/docker_api_exec_resize_test.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "net/http"
+ "os/exec"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestExecResizeApiHeightWidthNoInt(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf(out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+
+ endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar"
+ status, _, err := sockRequest("POST", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
+}
diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go
index 1ed99a256..b7957480f 100644
--- a/integration-cli/docker_api_exec_test.go
+++ b/integration-cli/docker_api_exec_test.go
@@ -5,23 +5,25 @@ package main
import (
"bytes"
"fmt"
+ "net/http"
"os/exec"
- "testing"
+
+ "github.com/go-check/check"
)
// Regression test for #9414
-func TestExecApiCreateNoCmd(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecApiCreateNoCmd(c *check.C) {
name := "exec_test"
runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
- if err == nil || !bytes.Contains(body, []byte("No exec command specified")) {
- t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err)
- }
+ status, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
- logDone("exec create API - returns error when missing Cmd")
+ if !bytes.Contains(body, []byte("No exec command specified")) {
+ c.Fatalf("Expected message when creating exec command with no Cmd specified")
+ }
}
diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go
new file mode 100644
index 000000000..573a89046
--- /dev/null
+++ b/integration-cli/docker_api_images_test.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/url"
+ "os/exec"
+ "strings"
+
+ "github.com/docker/docker/api/types"
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestApiImagesFilter(c *check.C) {
+ name := "utest:tag1"
+ name2 := "utest/docker:tag2"
+ name3 := "utest:5000/docker:tag3"
+ for _, n := range []string{name, name2, name3} {
+ if out, err := exec.Command(dockerBinary, "tag", "busybox", n).CombinedOutput(); err != nil {
+ c.Fatal(err, out)
+ }
+ }
+ type image types.Image
+ getImages := func(filter string) []image {
+ v := url.Values{}
+ v.Set("filter", filter)
+ status, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ var images []image
+ if err := json.Unmarshal(b, &images); err != nil {
+ c.Fatal(err)
+ }
+
+ return images
+ }
+
+ errMsg := "incorrect number of matches returned"
+ if images := getImages("utest*/*"); len(images[0].RepoTags) != 2 {
+ c.Fatal(errMsg)
+ }
+ if images := getImages("utest"); len(images[0].RepoTags) != 1 {
+ c.Fatal(errMsg)
+ }
+ if images := getImages("utest*"); len(images[0].RepoTags) != 1 {
+ c.Fatal(errMsg)
+ }
+ if images := getImages("*5000*/*"); len(images[0].RepoTags) != 1 {
+ c.Fatal(errMsg)
+ }
+}
+
+func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) {
+ testRequires(c, Network)
+ out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false)
+ if err != nil {
+ c.Fatal(err)
+ }
+ id := strings.TrimSpace(out)
+
+ res, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusOK)
+
+ defer body.Close()
+
+ if out, err := exec.Command(dockerBinary, "rmi", id).CombinedOutput(); err != nil {
+ c.Fatal(err, out)
+ }
+
+ res, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusOK)
+
+ defer loadBody.Close()
+
+ inspectOut, err := exec.Command(dockerBinary, "inspect", "--format='{{ .Id }}'", id).CombinedOutput()
+ if err != nil {
+ c.Fatal(err, inspectOut)
+ }
+ if strings.TrimSpace(string(inspectOut)) != id {
+ c.Fatal("load did not work properly")
+ }
+}
+
+func (s *DockerSuite) TestApiImagesDelete(c *check.C) {
+ testRequires(c, Network)
+ name := "test-api-images-delete"
+ out, err := buildImage(name, "FROM hello-world\nENV FOO bar", false)
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer deleteImages(name)
+ id := strings.TrimSpace(out)
+
+ if out, err := exec.Command(dockerBinary, "tag", name, "test:tag1").CombinedOutput(); err != nil {
+ c.Fatal(err, out)
+ }
+
+ status, _, err := sockRequest("DELETE", "/images/"+id, nil)
+ c.Assert(status, check.Equals, http.StatusConflict)
+ c.Assert(err, check.IsNil)
+
+ status, _, err = sockRequest("DELETE", "/images/test:noexist", nil)
+ c.Assert(status, check.Equals, http.StatusNotFound) //Status Codes:404 – no such image
+ c.Assert(err, check.IsNil)
+
+ status, _, err = sockRequest("DELETE", "/images/test:tag1", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerSuite) TestApiImagesHistory(c *check.C) {
+ testRequires(c, Network)
+ name := "test-api-images-history"
+ out, err := buildImage(name, "FROM hello-world\nENV FOO bar", false)
+ c.Assert(err, check.IsNil)
+
+ defer deleteImages(name)
+ id := strings.TrimSpace(out)
+
+ status, body, err := sockRequest("GET", "/images/"+id+"/history", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var historydata []types.ImageHistory
+ if err = json.Unmarshal(body, &historydata); err != nil {
+ c.Fatalf("Error on unmarshal: %s", err)
+ }
+
+ c.Assert(len(historydata), check.Not(check.Equals), 0)
+ c.Assert(historydata[0].Tags[0], check.Equals, "test-api-images-history:latest")
+}
diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go
new file mode 100644
index 000000000..408428910
--- /dev/null
+++ b/integration-cli/docker_api_info_test.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestInfoApi(c *check.C) {
+ endpoint := "/info"
+
+ status, body, err := sockRequest("GET", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ // always shown fields
+ stringsToCheck := []string{
+ "ID",
+ "Containers",
+ "Images",
+ "ExecutionDriver",
+ "LoggingDriver",
+ "OperatingSystem",
+ "NCPU",
+ "MemTotal",
+ "KernelVersion",
+ "Driver"}
+
+ out := string(body)
+ for _, linePrefix := range stringsToCheck {
+ if !strings.Contains(out, linePrefix) {
+ c.Errorf("couldn't find string %v in output", linePrefix)
+ }
+ }
+}
diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go
index ed6f596b2..62f32357c 100644
--- a/integration-cli/docker_api_inspect_test.go
+++ b/integration-cli/docker_api_inspect_test.go
@@ -2,58 +2,43 @@ package main
import (
"encoding/json"
+ "net/http"
"os/exec"
- "testing"
+ "strings"
+
+ "github.com/go-check/check"
)
-func TestInspectApiContainerResponse(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to create a container: %s, %v", out, err)
+ c.Fatalf("failed to create a container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
- // test on json marshal version
- // and latest version
- testVersions := []string{"v1.11", "latest"}
+ endpoint := "/containers/" + cleanedContainerID + "/json"
+ status, body, err := sockRequest("GET", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
- for _, testVersion := range testVersions {
- endpoint := "/containers/" + cleanedContainerID + "/json"
- if testVersion != "latest" {
- endpoint = "/" + testVersion + endpoint
- }
- body, err := sockRequest("GET", endpoint, nil)
- if err != nil {
- t.Fatalf("sockRequest failed for %s version: %v", testVersion, err)
- }
-
- var inspectJSON map[string]interface{}
- if err = json.Unmarshal(body, &inspectJSON); err != nil {
- t.Fatalf("unable to unmarshal body for %s version: %v", testVersion, err)
- }
-
- keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW"}
-
- if testVersion == "v1.11" {
- keys = append(keys, "ID")
- } else {
- keys = append(keys, "Id")
- }
-
- for _, key := range keys {
- if _, ok := inspectJSON[key]; !ok {
- t.Fatalf("%s does not exist in reponse for %s version", key, testVersion)
- }
- }
- //Issue #6830: type not properly converted to JSON/back
- if _, ok := inspectJSON["Path"].(bool); ok {
- t.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling")
- }
+ var inspectJSON map[string]interface{}
+ if err = json.Unmarshal(body, &inspectJSON); err != nil {
+ c.Fatalf("unable to unmarshal body for latest version: %v", err)
}
- logDone("container json - check keys in container json response")
+ keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW"}
+
+ keys = append(keys, "Id")
+
+ for _, key := range keys {
+ if _, ok := inspectJSON[key]; !ok {
+ c.Fatalf("%s does not exist in response for latest version", key)
+ }
+ }
+ //Issue #6830: type not properly converted to JSON/back
+ if _, ok := inspectJSON["Path"].(bool); ok {
+ c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling")
+ }
}
diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go
new file mode 100644
index 000000000..caef7255d
--- /dev/null
+++ b/integration-cli/docker_api_logs_test.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "net/http"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "-t", "busybox", "/bin/sh", "-c", "while true; do echo hello; sleep 1; done")
+ id := strings.TrimSpace(out)
+ if err := waitRun(id); err != nil {
+ c.Fatal(err)
+ }
+
+ type logOut struct {
+ out string
+ res *http.Response
+ err error
+ }
+ chLog := make(chan logOut)
+
+ go func() {
+ res, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", id), nil, "")
+ out, _ := bufio.NewReader(body).ReadString('\n')
+ chLog <- logOut{strings.TrimSpace(out), res, err}
+ }()
+
+ select {
+ case l := <-chLog:
+ c.Assert(l.err, check.IsNil)
+ c.Assert(l.res.StatusCode, check.Equals, http.StatusOK)
+ if !strings.HasSuffix(l.out, "hello") {
+ c.Fatalf("expected log output to container 'hello', but it does not")
+ }
+ case <-time.After(2 * time.Second):
+ c.Fatal("timeout waiting for logs to exit")
+ }
+}
+
+func (s *DockerSuite) TestLogsApiNoStdoutNorStderr(c *check.C) {
+ name := "logs_test"
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
+ if out, _, err := runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ status, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil)
+ c.Assert(status, check.Equals, http.StatusBadRequest)
+ c.Assert(err, check.IsNil)
+
+ expected := "Bad parameters: you must choose at least one stream"
+ if !bytes.Contains(body, []byte(expected)) {
+ c.Fatalf("Expected %s, got %s", expected, string(body[:]))
+ }
+}
+
+// Regression test for #12704
+func (s *DockerSuite) TestLogsApiFollowEmptyOutput(c *check.C) {
+ name := "logs_test"
+ t0 := time.Now()
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "sleep", "10")
+ if out, _, err := runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ _, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&stderr=1&tail=all", name), bytes.NewBuffer(nil), "")
+ t1 := time.Now()
+ body.Close()
+ if err != nil {
+ c.Fatal(err)
+ }
+ elapsed := t1.Sub(t0).Seconds()
+ if elapsed > 5.0 {
+ c.Fatalf("HTTP response was not immediate (elapsed %.1fs)", elapsed)
+ }
+}
diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go
index 6ba95c305..6d5528069 100644
--- a/integration-cli/docker_api_resize_test.go
+++ b/integration-cli/docker_api_resize_test.go
@@ -1,53 +1,62 @@
package main
import (
+ "net/http"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestResizeApiResponse(t *testing.T) {
+func (s *DockerSuite) TestResizeApiResponse(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- defer deleteAllContainers()
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
- _, err = sockRequest("POST", endpoint, nil)
- if err != nil {
- t.Fatalf("resize Request failed %v", err)
- }
-
- logDone("container resize - when started")
+ status, _, err := sockRequest("POST", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
}
-func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) {
+func (s *DockerSuite) TestResizeApiHeightWidthNoInt(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf(out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+
+ endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar"
+ status, _, err := sockRequest("POST", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- defer deleteAllContainers()
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
- // make sure the exited cintainer is not running
+ // make sure the exited container is not running
runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
- body, err := sockRequest("POST", endpoint, nil)
- if err == nil {
- t.Fatalf("resize should fail when container is not started")
- }
- if !strings.Contains(string(body), "Cannot resize container") && !strings.Contains(string(body), cleanedContainerID) {
- t.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body))
- }
+ status, body, err := sockRequest("POST", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
- logDone("container resize - when not started should not resize")
+ if !strings.Contains(string(body), "Cannot resize container") && !strings.Contains(string(body), cleanedContainerID) {
+ c.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body))
+ }
}
diff --git a/integration-cli/docker_api_test.go b/integration-cli/docker_api_test.go
new file mode 100644
index 000000000..0bd48880a
--- /dev/null
+++ b/integration-cli/docker_api_test.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "net/http"
+ "net/http/httputil"
+ "time"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestApiOptionsRoute(c *check.C) {
+ status, _, err := sockRequest("OPTIONS", "/", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerSuite) TestApiGetEnabledCors(c *check.C) {
+ res, body, err := sockRequestRaw("GET", "/version", nil, "")
+ body.Close()
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusOK)
+ // TODO: @runcom incomplete tests, why old integration tests had this headers
+ // and here none of the headers below are in the response?
+ //c.Log(res.Header)
+ //c.Assert(res.Header.Get("Access-Control-Allow-Origin"), check.Equals, "*")
+ //c.Assert(res.Header.Get("Access-Control-Allow-Headers"), check.Equals, "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
+}
+
+func (s *DockerSuite) TestVersionStatusCode(c *check.C) {
+ conn, err := sockConn(time.Duration(10 * time.Second))
+ c.Assert(err, check.IsNil)
+
+ client := httputil.NewClientConn(conn, nil)
+ defer client.Close()
+
+ req, err := http.NewRequest("GET", "/v999.0/version", nil)
+ c.Assert(err, check.IsNil)
+ req.Header.Set("User-Agent", "Docker-Client/999.0")
+
+ res, err := client.Do(req)
+ c.Assert(res.StatusCode, check.Equals, http.StatusBadRequest)
+}
diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go
new file mode 100644
index 000000000..b756794c2
--- /dev/null
+++ b/integration-cli/docker_api_version_test.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/autogen/dockerversion"
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestGetVersion(c *check.C) {
+ status, body, err := sockRequest("GET", "/version", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ var v types.Version
+ if err := json.Unmarshal(body, &v); err != nil {
+ c.Fatal(err)
+ }
+
+ if v.Version != dockerversion.VERSION {
+ c.Fatal("Version mismatch")
+ }
+}
diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go
index cf21cda58..fc2ea1a1d 100644
--- a/integration-cli/docker_cli_attach_test.go
+++ b/integration-cli/docker_cli_attach_test.go
@@ -1,18 +1,20 @@
package main
import (
+ "bufio"
+ "fmt"
"io"
"os/exec"
"strings"
"sync"
- "testing"
"time"
+
+ "github.com/go-check/check"
)
const attachWait = 5 * time.Second
-func TestAttachMultipleAndRestart(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestAttachMultipleAndRestart(c *check.C) {
endGroup := &sync.WaitGroup{}
startGroup := &sync.WaitGroup{}
@@ -20,7 +22,7 @@ func TestAttachMultipleAndRestart(t *testing.T) {
startGroup.Add(3)
if err := waitForContainer("attacher", "-d", "busybox", "/bin/sh", "-c", "while true; do sleep 1; echo hello; done"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
startDone := make(chan struct{})
@@ -38,32 +40,32 @@ func TestAttachMultipleAndRestart(t *testing.T) {
for i := 0; i < 3; i++ {
go func() {
- c := exec.Command(dockerBinary, "attach", "attacher")
+ cmd := exec.Command(dockerBinary, "attach", "attacher")
defer func() {
- c.Wait()
+ cmd.Wait()
endGroup.Done()
}()
- out, err := c.StdoutPipe()
+ out, err := cmd.StdoutPipe()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if err := c.Start(); err != nil {
- t.Fatal(err)
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
}
buf := make([]byte, 1024)
if _, err := out.Read(buf); err != nil && err != io.EOF {
- t.Fatal(err)
+ c.Fatal(err)
}
startGroup.Done()
if !strings.Contains(string(buf), "hello") {
- t.Fatalf("unexpected output %s expected hello\n", string(buf))
+ c.Fatalf("unexpected output %s expected hello\n", string(buf))
}
}()
}
@@ -71,66 +73,111 @@ func TestAttachMultipleAndRestart(t *testing.T) {
select {
case <-startDone:
case <-time.After(attachWait):
- t.Fatalf("Attaches did not initialize properly")
+ c.Fatalf("Attaches did not initialize properly")
}
cmd := exec.Command(dockerBinary, "kill", "attacher")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
select {
case <-endDone:
case <-time.After(attachWait):
- t.Fatalf("Attaches did not finish properly")
+ c.Fatalf("Attaches did not finish properly")
}
- logDone("attach - multiple attach")
}
-func TestAttachTtyWithoutStdin(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestAttachTtyWithoutStdin(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to start container: %v (%v)", out, err)
+ c.Fatalf("failed to start container: %v (%v)", out, err)
}
id := strings.TrimSpace(out)
if err := waitRun(id); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer func() {
cmd := exec.Command(dockerBinary, "kill", id)
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatalf("failed to kill container: %v (%v)", out, err)
+ c.Fatalf("failed to kill container: %v (%v)", out, err)
}
}()
- done := make(chan struct{})
+ done := make(chan error)
go func() {
defer close(done)
cmd := exec.Command(dockerBinary, "attach", id)
if _, err := cmd.StdinPipe(); err != nil {
- t.Fatal(err)
+ done <- err
+ return
}
expected := "cannot enable tty mode"
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal("attach should have failed")
+ done <- fmt.Errorf("attach should have failed")
+ return
} else if !strings.Contains(out, expected) {
- t.Fatalf("attach failed with error %q: expected %q", out, expected)
+ done <- fmt.Errorf("attach failed with error %q: expected %q", out, expected)
+ return
}
}()
select {
- case <-done:
+ case err := <-done:
+ c.Assert(err, check.IsNil)
case <-time.After(attachWait):
- t.Fatal("attach is running but should have failed")
+ c.Fatal("attach is running but should have failed")
+ }
+}
+
+func (s *DockerSuite) TestAttachDisconnect(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-di", "busybox", "/bin/cat")
+ id := strings.TrimSpace(out)
+
+ cmd := exec.Command(dockerBinary, "attach", id)
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer stdin.Close()
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ defer cmd.Process.Kill()
+
+ if _, err := stdin.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+ out, err = bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ if err := stdin.Close(); err != nil {
+ c.Fatal(err)
+ }
+
+ // Expect container to still be running after stdin is closed
+ running, err := inspectField(id, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
}
- logDone("attach - forbid piped stdin to tty enabled container")
}
diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go
index a3bfa5b1c..82808a5b0 100644
--- a/integration-cli/docker_cli_attach_unix_test.go
+++ b/integration-cli/docker_cli_attach_unix_test.go
@@ -3,37 +3,38 @@
package main
import (
+ "bufio"
"os/exec"
"strings"
- "testing"
"time"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/go-check/check"
"github.com/kr/pty"
)
// #9860
-func TestAttachClosedOnContainerStop(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-dti", "busybox", "sleep", "2")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to start container: %v (%v)", out, err)
+ c.Fatalf("failed to start container: %v (%v)", out, err)
}
- id := stripTrailingCharacters(out)
+ id := strings.TrimSpace(out)
if err := waitRun(id); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- done := make(chan struct{})
-
+ errChan := make(chan error)
go func() {
- defer close(done)
+ defer close(errChan)
_, tty, err := pty.Open()
if err != nil {
- t.Fatalf("could not open pty: %v", err)
+ errChan <- err
+ return
}
attachCmd := exec.Command(dockerBinary, "attach", id)
attachCmd.Stdin = tty
@@ -41,58 +42,61 @@ func TestAttachClosedOnContainerStop(t *testing.T) {
attachCmd.Stderr = tty
if err := attachCmd.Run(); err != nil {
- t.Fatalf("attach returned error %s", err)
+ errChan <- err
+ return
}
}()
waitCmd := exec.Command(dockerBinary, "wait", id)
if out, _, err = runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("error thrown while waiting for container: %s, %v", out, err)
+ c.Fatalf("error thrown while waiting for container: %s, %v", out, err)
}
select {
- case <-done:
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
case <-time.After(attachWait):
- t.Fatal("timed out without attach returning")
+ c.Fatal("timed out without attach returning")
}
- logDone("attach - return after container finished")
}
-func TestAttachAfterDetach(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestAttachAfterDetach(c *check.C) {
name := "detachtest"
cpty, tty, err := pty.Open()
if err != nil {
- t.Fatalf("Could not open pty: %v", err)
+ c.Fatalf("Could not open pty: %v", err)
}
cmd := exec.Command(dockerBinary, "run", "-ti", "--name", name, "busybox")
cmd.Stdin = tty
cmd.Stdout = tty
cmd.Stderr = tty
- detached := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if err := cmd.Run(); err != nil {
- t.Fatalf("attach returned error %s", err)
- }
- close(detached)
+ errChan <- cmd.Run()
+ close(errChan)
}()
time.Sleep(500 * time.Millisecond)
if err := waitRun(name); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cpty.Write([]byte{16})
time.Sleep(100 * time.Millisecond)
cpty.Write([]byte{17})
- <-detached
+ select {
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
+ case <-time.After(5 * time.Second):
+ c.Fatal("timeout while detaching")
+ }
cpty, tty, err = pty.Open()
if err != nil {
- t.Fatalf("Could not open pty: %v", err)
+ c.Fatalf("Could not open pty: %v", err)
}
cmd = exec.Command(dockerBinary, "attach", name)
@@ -101,7 +105,7 @@ func TestAttachAfterDetach(t *testing.T) {
cmd.Stderr = tty
if err := cmd.Start(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
bytes := make([]byte, 10)
@@ -120,20 +124,162 @@ func TestAttachAfterDetach(t *testing.T) {
select {
case err := <-readErr:
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
case <-time.After(2 * time.Second):
- t.Fatal("timeout waiting for attach read")
+ c.Fatal("timeout waiting for attach read")
}
if err := cmd.Wait(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(string(bytes[:nBytes]), "/ #") {
- t.Fatalf("failed to get a new prompt. got %s", string(bytes[:nBytes]))
+ c.Fatalf("failed to get a new prompt. got %s", string(bytes[:nBytes]))
+ }
+
+}
+
+// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
+func (s *DockerSuite) TestAttachDetach(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat")
+ id := strings.TrimSpace(out)
+ if err := waitRun(id); err != nil {
+ c.Fatal(err)
+ }
+
+ cpty, tty, err := pty.Open()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer cpty.Close()
+
+ cmd := exec.Command(dockerBinary, "attach", id)
+ cmd.Stdin = tty
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ if err := waitRun(id); err != nil {
+ c.Fatalf("error waiting for container to start: %v", err)
+ }
+
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+ out, err = bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ // escape sequence
+ if _, err := cpty.Write([]byte{16}); err != nil {
+ c.Fatal(err)
+ }
+ time.Sleep(100 * time.Millisecond)
+ if _, err := cpty.Write([]byte{17}); err != nil {
+ c.Fatal(err)
+ }
+
+ ch := make(chan struct{})
+ go func() {
+ cmd.Wait()
+ ch <- struct{}{}
+ }()
+
+ running, err := inspectField(id, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
+ }
+
+ go func() {
+ dockerCmd(c, "kill", id)
+ }()
+
+ select {
+ case <-ch:
+ case <-time.After(10 * time.Millisecond):
+ c.Fatal("timed out waiting for container to exit")
+ }
+
+}
+
+// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
+func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat")
+ id := stringid.TruncateID(strings.TrimSpace(out))
+ if err := waitRun(id); err != nil {
+ c.Fatal(err)
+ }
+
+ cpty, tty, err := pty.Open()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer cpty.Close()
+
+ cmd := exec.Command(dockerBinary, "attach", id)
+ cmd.Stdin = tty
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+ out, err = bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ // escape sequence
+ if _, err := cpty.Write([]byte{16}); err != nil {
+ c.Fatal(err)
+ }
+ time.Sleep(100 * time.Millisecond)
+ if _, err := cpty.Write([]byte{17}); err != nil {
+ c.Fatal(err)
+ }
+
+ ch := make(chan struct{})
+ go func() {
+ cmd.Wait()
+ ch <- struct{}{}
+ }()
+
+ running, err := inspectField(id, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
+ }
+
+ go func() {
+ dockerCmd(c, "kill", id)
+ }()
+
+ select {
+ case <-ch:
+ case <-time.After(10 * time.Millisecond):
+ c.Fatal("timed out waiting for container to exit")
}
- logDone("attach - reconnect after detaching")
}
diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go
index 6c96e2aa9..5e35d6e36 100644
--- a/integration-cli/docker_cli_build_test.go
+++ b/integration-cli/docker_cli_build_test.go
@@ -15,18 +15,17 @@ import (
"runtime"
"strconv"
"strings"
- "sync"
- "testing"
"text/template"
"time"
"github.com/docker/docker/builder/command"
"github.com/docker/docker/pkg/archive"
+ "github.com/docker/docker/pkg/stringutils"
+ "github.com/go-check/check"
)
-func TestBuildJSONEmptyRun(t *testing.T) {
+func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) {
name := "testbuildjsonemptyrun"
- defer deleteImages(name)
_, err := buildImage(
name,
@@ -37,15 +36,13 @@ func TestBuildJSONEmptyRun(t *testing.T) {
true)
if err != nil {
- t.Fatal("error when dealing with a RUN statement with empty JSON array")
+ c.Fatal("error when dealing with a RUN statement with empty JSON array")
}
- logDone("build - RUN with an empty array should not panic")
}
-func TestBuildEmptyWhitespace(t *testing.T) {
+func (s *DockerSuite) TestBuildEmptyWhitespace(c *check.C) {
name := "testbuildemptywhitespace"
- defer deleteImages(name)
_, err := buildImage(
name,
@@ -58,15 +55,13 @@ func TestBuildEmptyWhitespace(t *testing.T) {
true)
if err == nil {
- t.Fatal("no error when dealing with a COPY statement with no content on the same line")
+ c.Fatal("no error when dealing with a COPY statement with no content on the same line")
}
- logDone("build - statements with whitespace and no content should generate a parse error")
}
-func TestBuildShCmdJSONEntrypoint(t *testing.T) {
+func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) {
name := "testbuildshcmdjsonentrypoint"
- defer deleteImages(name)
_, err := buildImage(
name,
@@ -78,7 +73,7 @@ func TestBuildShCmdJSONEntrypoint(t *testing.T) {
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(
@@ -89,19 +84,17 @@ func TestBuildShCmdJSONEntrypoint(t *testing.T) {
name))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.TrimSpace(out) != "/bin/sh -c echo test" {
- t.Fatal("CMD did not contain /bin/sh -c")
+ c.Fatal("CMD did not contain /bin/sh -c")
}
- logDone("build - CMD should always contain /bin/sh -c when specified without JSON")
}
-func TestBuildEnvironmentReplacementUser(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) {
name := "testbuildenvironmentreplacement"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM scratch
@@ -109,24 +102,22 @@ func TestBuildEnvironmentReplacementUser(t *testing.T) {
USER ${user}
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.User")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != `"foo"` {
- t.Fatal("User foo from environment not in Config.User on image")
+ c.Fatal("User foo from environment not in Config.User on image")
}
- logDone("build - user environment replacement")
}
-func TestBuildEnvironmentReplacementVolume(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) {
name := "testbuildenvironmentreplacement"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM scratch
@@ -134,30 +125,28 @@ func TestBuildEnvironmentReplacementVolume(t *testing.T) {
VOLUME ${volume}
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Volumes")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var volumes map[string]interface{}
if err := json.Unmarshal([]byte(res), &volumes); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, ok := volumes["/quux"]; !ok {
- t.Fatal("Volume /quux from environment not in Config.Volumes on image")
+ c.Fatal("Volume /quux from environment not in Config.Volumes on image")
}
- logDone("build - volume environment replacement")
}
-func TestBuildEnvironmentReplacementExpose(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) {
name := "testbuildenvironmentreplacement"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM scratch
@@ -165,30 +154,28 @@ func TestBuildEnvironmentReplacementExpose(t *testing.T) {
EXPOSE ${port}
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.ExposedPorts")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var exposedPorts map[string]interface{}
if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, ok := exposedPorts["80/tcp"]; !ok {
- t.Fatal("Exposed port 80 from environment not in Config.ExposedPorts on image")
+ c.Fatal("Exposed port 80 from environment not in Config.ExposedPorts on image")
}
- logDone("build - expose environment replacement")
}
-func TestBuildEnvironmentReplacementWorkdir(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) {
name := "testbuildenvironmentreplacement"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM busybox
@@ -198,47 +185,48 @@ func TestBuildEnvironmentReplacementWorkdir(t *testing.T) {
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - workdir environment replacement")
}
-func TestBuildEnvironmentReplacementAddCopy(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) {
name := "testbuildenvironmentreplacement"
- defer deleteImages(name)
ctx, err := fakeContext(`
FROM scratch
ENV baz foo
ENV quux bar
ENV dot .
+ ENV fee fff
+ ENV gee ggg
ADD ${baz} ${dot}
COPY ${quux} ${dot}
+ ADD ${zzz:-${fee}} ${dot}
+ COPY ${zzz:-${gee}} ${dot}
`,
map[string]string{
"foo": "test1",
"bar": "test2",
+ "fff": "test3",
+ "ggg": "test4",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add/copy environment replacement")
}
-func TestBuildEnvironmentReplacementEnv(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) {
name := "testbuildenvironmentreplacement"
- defer deleteImages(name)
-
_, err := buildImage(name,
`
FROM busybox
@@ -256,18 +244,18 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Env")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
envResult := []string{}
if err = unmarshalJSON([]byte(res), &envResult); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
found := false
@@ -278,32 +266,34 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
if parts[0] == "bar" {
found = true
if parts[1] != "zzz" {
- t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1])
+ c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1])
}
} else if strings.HasPrefix(parts[0], "env") {
envCount++
if parts[1] != "zzz" {
- t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1])
+ c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1])
+ }
+ } else if strings.HasPrefix(parts[0], "env") {
+ envCount++
+ if parts[1] != "foo" {
+ c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1])
}
}
}
if !found {
- t.Fatal("Never found the `bar` env variable")
+ c.Fatal("Never found the `bar` env variable")
}
if envCount != 4 {
- t.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult)
+ c.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult)
}
- logDone("build - env environment replacement")
}
-func TestBuildHandleEscapes(t *testing.T) {
+func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) {
name := "testbuildhandleescapes"
- defer deleteImages(name)
-
_, err := buildImage(name,
`
FROM scratch
@@ -312,22 +302,22 @@ func TestBuildHandleEscapes(t *testing.T) {
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var result map[string]map[string]struct{}
res, err := inspectFieldJSON(name, "Config.Volumes")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err = unmarshalJSON([]byte(res), &result); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, ok := result["bar"]; !ok {
- t.Fatal("Could not find volume bar set from env foo in volumes table")
+ c.Fatal("Could not find volume bar set from env foo in volumes table")
}
deleteImages(name)
@@ -340,20 +330,20 @@ func TestBuildHandleEscapes(t *testing.T) {
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err = inspectFieldJSON(name, "Config.Volumes")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err = unmarshalJSON([]byte(res), &result); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, ok := result["${FOO}"]; !ok {
- t.Fatal("Could not find volume ${FOO} set from env foo in volumes table")
+ c.Fatal("Could not find volume ${FOO} set from env foo in volumes table")
}
deleteImages(name)
@@ -370,31 +360,28 @@ func TestBuildHandleEscapes(t *testing.T) {
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err = inspectFieldJSON(name, "Config.Volumes")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err = unmarshalJSON([]byte(res), &result); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, ok := result[`\\\${FOO}`]; !ok {
- t.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result)
+ c.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result)
}
- logDone("build - handle escapes")
}
-func TestBuildOnBuildLowercase(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) {
name := "testbuildonbuildlowercase"
name2 := "testbuildonbuildlowercase2"
- defer deleteImages(name, name2)
-
_, err := buildImage(name,
`
FROM busybox
@@ -402,7 +389,7 @@ func TestBuildOnBuildLowercase(t *testing.T) {
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, out, err := buildImageWithOut(name2, fmt.Sprintf(`
@@ -410,24 +397,21 @@ func TestBuildOnBuildLowercase(t *testing.T) {
`, name), true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(out, "quux") {
- t.Fatalf("Did not receive the expected echo text, got %s", out)
+ c.Fatalf("Did not receive the expected echo text, got %s", out)
}
if strings.Contains(out, "ONBUILD ONBUILD") {
- t.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", out)
+ c.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", out)
}
- logDone("build - handle case-insensitive onbuild statement")
}
-func TestBuildEnvEscapes(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) {
name := "testbuildenvescapes"
- defer deleteImages(name)
- defer deleteAllContainers()
_, err := buildImage(name,
`
FROM busybox
@@ -439,20 +423,17 @@ func TestBuildEnvEscapes(t *testing.T) {
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.TrimSpace(out) != "$" {
- t.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out))
+ c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out))
}
- logDone("build - env should handle \\$ properly")
}
-func TestBuildEnvOverwrite(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) {
name := "testbuildenvoverwrite"
- defer deleteImages(name)
- defer deleteAllContainers()
_, err := buildImage(name,
`
@@ -463,40 +444,36 @@ func TestBuildEnvOverwrite(t *testing.T) {
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-e", "TEST=bar", "-t", name))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.TrimSpace(out) != "bar" {
- t.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out))
+ c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out))
}
- logDone("build - env should overwrite builder ENV during run")
}
-func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check.C) {
name := "testbuildonbuildforbiddenmaintainerinsourceimage"
- defer deleteImages("onbuild")
- defer deleteImages(name)
- defer deleteAllContainers()
createCmd := exec.Command(dockerBinary, "create", "busybox", "true")
out, _, _, err := runCommandWithStdoutStderr(createCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"MAINTAINER docker.io\"]}", cleanedContainerID, "onbuild")
if _, err := runCommand(commitCmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImage(name,
@@ -504,33 +481,29 @@ func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) {
true)
if err != nil {
if !strings.Contains(err.Error(), "maintainer isn't allowed as an ONBUILD trigger") {
- t.Fatalf("Wrong error %v, must be about MAINTAINER and ONBUILD in source image", err)
+ c.Fatalf("Wrong error %v, must be about MAINTAINER and ONBUILD in source image", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - onbuild forbidden maintainer in source image")
}
-func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) {
name := "testbuildonbuildforbiddenfrominsourceimage"
- defer deleteImages("onbuild")
- defer deleteImages(name)
- defer deleteAllContainers()
createCmd := exec.Command(dockerBinary, "create", "busybox", "true")
out, _, _, err := runCommandWithStdoutStderr(createCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"FROM busybox\"]}", cleanedContainerID, "onbuild")
if _, err := runCommand(commitCmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImage(name,
@@ -538,33 +511,29 @@ func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) {
true)
if err != nil {
if !strings.Contains(err.Error(), "from isn't allowed as an ONBUILD trigger") {
- t.Fatalf("Wrong error %v, must be about FROM and ONBUILD in source image", err)
+ c.Fatalf("Wrong error %v, must be about FROM and ONBUILD in source image", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - onbuild forbidden from in source image")
}
-func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildForbiddenChainedInSourceImage(c *check.C) {
name := "testbuildonbuildforbiddenchainedinsourceimage"
- defer deleteImages("onbuild")
- defer deleteImages(name)
- defer deleteAllContainers()
createCmd := exec.Command(dockerBinary, "create", "busybox", "true")
out, _, _, err := runCommandWithStdoutStderr(createCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"ONBUILD RUN ls\"]}", cleanedContainerID, "onbuild")
if _, err := runCommand(commitCmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImage(name,
@@ -572,23 +541,18 @@ func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) {
true)
if err != nil {
if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") {
- t.Fatalf("Wrong error %v, must be about chaining ONBUILD in source image", err)
+ c.Fatalf("Wrong error %v, must be about chaining ONBUILD in source image", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - onbuild forbidden chained in source image")
}
-func TestBuildOnBuildCmdEntrypointJSON(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildCmdEntrypointJSON(c *check.C) {
name1 := "onbuildcmd"
name2 := "onbuildgenerated"
- defer deleteImages(name2)
- defer deleteImages(name1)
- defer deleteAllContainers()
-
_, err := buildImage(name1, `
FROM busybox
ONBUILD CMD ["hello world"]
@@ -597,71 +561,64 @@ ONBUILD RUN ["true"]`,
false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImage(name2, fmt.Sprintf(`FROM %s`, name1), false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) {
- t.Fatal("did not get echo output from onbuild", out)
+ c.Fatal("did not get echo output from onbuild", out)
}
- logDone("build - onbuild with json entrypoint/cmd")
}
-func TestBuildOnBuildEntrypointJSON(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildEntrypointJSON(c *check.C) {
name1 := "onbuildcmd"
name2 := "onbuildgenerated"
- defer deleteImages(name2)
- defer deleteImages(name1)
- defer deleteAllContainers()
-
_, err := buildImage(name1, `
FROM busybox
ONBUILD ENTRYPOINT ["echo"]`,
false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImage(name2, fmt.Sprintf("FROM %s\nCMD [\"hello world\"]\n", name1), false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) {
- t.Fatal("got malformed output from onbuild", out)
+ c.Fatal("got malformed output from onbuild", out)
}
- logDone("build - onbuild with json entrypoint")
}
-func TestBuildCacheADD(t *testing.T) {
+func (s *DockerSuite) TestBuildCacheAdd(c *check.C) {
name := "testbuildtwoimageswithadd"
- defer deleteImages(name)
server, err := fakeStorage(map[string]string{
"robots.txt": "hello",
"index.html": "world",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -669,10 +626,10 @@ func TestBuildCacheADD(t *testing.T) {
fmt.Sprintf(`FROM scratch
ADD %s/robots.txt /`, server.URL()),
true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
deleteImages(name)
_, out, err := buildImageWithOut(name,
@@ -680,24 +637,22 @@ func TestBuildCacheADD(t *testing.T) {
ADD %s/index.html /`, server.URL()),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.Contains(out, "Using cache") {
- t.Fatal("2nd build used cache on ADD, it shouldn't")
+ c.Fatal("2nd build used cache on ADD, it shouldn't")
}
- logDone("build - build two images with remote ADD")
}
-func TestBuildLastModified(t *testing.T) {
+func (s *DockerSuite) TestBuildLastModified(c *check.C) {
name := "testbuildlastmodified"
- defer deleteImages(name)
server, err := fakeStorage(map[string]string{
"file": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -710,13 +665,13 @@ RUN ls -le /file`
dockerfile := fmt.Sprintf(dFmt, server.URL())
if _, out, err = buildImageWithOut(name, dockerfile, false); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
originMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out)
// Make sure our regexp is correct
if strings.Index(originMTime, "/file") < 0 {
- t.Fatalf("Missing ls info on 'file':\n%s", out)
+ c.Fatalf("Missing ls info on 'file':\n%s", out)
}
// Build it again and make sure the mtime of the file didn't change.
@@ -724,12 +679,12 @@ RUN ls -le /file`
time.Sleep(2 * time.Second)
if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
newMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out2)
if newMTime != originMTime {
- t.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", originMTime, newMTime)
+ c.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", originMTime, newMTime)
}
// Now 'touch' the file and make sure the timestamp DID change this time
@@ -738,45 +693,41 @@ RUN ls -le /file`
"file": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
dockerfile = fmt.Sprintf(dFmt, server.URL())
if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
newMTime = regexp.MustCompile(`root.*/file.*\n`).FindString(out2)
if newMTime == originMTime {
- t.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", originMTime, newMTime)
+ c.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", originMTime, newMTime)
}
- logDone("build - use Last-Modified header")
}
-func TestBuildSixtySteps(t *testing.T) {
+func (s *DockerSuite) TestBuildSixtySteps(c *check.C) {
name := "foobuildsixtysteps"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\n"+strings.Repeat("ADD foo /\n", 60),
map[string]string{
"foo": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - build an image with sixty build steps")
}
-func TestBuildAddSingleFileToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildAddSingleFileToRoot(c *check.C) {
name := "testaddimg"
- defer deleteImages(name)
ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -790,48 +741,44 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add single file to root")
}
// Issue #3960: "ADD src ." hangs
-func TestBuildAddSingleFileToWorkdir(t *testing.T) {
+func (s *DockerSuite) TestBuildAddSingleFileToWorkdir(c *check.C) {
name := "testaddsinglefiletoworkdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
ADD test_file .`,
map[string]string{
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- done := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
- }
- close(done)
+ _, err := buildImageFromContext(name, ctx, true)
+ errChan <- err
+ close(errChan)
}()
select {
case <-time.After(5 * time.Second):
- t.Fatal("Build with adding to workdir timed out")
- case <-done:
+ c.Fatal("Build with adding to workdir timed out")
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
}
- logDone("build - add single file to workdir")
}
-func TestBuildAddSingleFileToExistDir(t *testing.T) {
+func (s *DockerSuite) TestBuildAddSingleFileToExistDir(c *check.C) {
name := "testaddsinglefiletoexistdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -846,27 +793,25 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio'
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add single file to existing dir")
}
-func TestBuildCopyAddMultipleFiles(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyAddMultipleFiles(c *check.C) {
server, err := fakeStorage(map[string]string{
"robots.txt": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
name := "testcopymultiplefilestofile"
- defer deleteImages(name)
ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -893,18 +838,16 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio'
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - multiple file copy/add tests")
}
-func TestBuildAddMultipleFilesToFile(t *testing.T) {
+func (s *DockerSuite) TestBuildAddMultipleFilesToFile(c *check.C) {
name := "testaddmultiplefilestofile"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
ADD file1.txt file2.txt test
`,
@@ -914,20 +857,18 @@ func TestBuildAddMultipleFilesToFile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using ADD with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple add files to file")
}
-func TestBuildJSONAddMultipleFilesToFile(t *testing.T) {
+func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFile(c *check.C) {
name := "testjsonaddmultiplefilestofile"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
ADD ["file1.txt", "file2.txt", "test"]
`,
@@ -937,20 +878,18 @@ func TestBuildJSONAddMultipleFilesToFile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using ADD with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple add files to file json syntax")
}
-func TestBuildAddMultipleFilesToFileWild(t *testing.T) {
+func (s *DockerSuite) TestBuildAddMultipleFilesToFileWild(c *check.C) {
name := "testaddmultiplefilestofilewild"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
ADD file*.txt test
`,
@@ -960,20 +899,18 @@ func TestBuildAddMultipleFilesToFileWild(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using ADD with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple add files to file wild")
}
-func TestBuildJSONAddMultipleFilesToFileWild(t *testing.T) {
+func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFileWild(c *check.C) {
name := "testjsonaddmultiplefilestofilewild"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
ADD ["file*.txt", "test"]
`,
@@ -983,20 +920,18 @@ func TestBuildJSONAddMultipleFilesToFileWild(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using ADD with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple add files to file wild json syntax")
}
-func TestBuildCopyMultipleFilesToFile(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyMultipleFilesToFile(c *check.C) {
name := "testcopymultiplefilestofile"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
COPY file1.txt file2.txt test
`,
@@ -1006,20 +941,18 @@ func TestBuildCopyMultipleFilesToFile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using COPY with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple copy files to file")
}
-func TestBuildJSONCopyMultipleFilesToFile(t *testing.T) {
+func (s *DockerSuite) TestBuildJSONCopyMultipleFilesToFile(c *check.C) {
name := "testjsoncopymultiplefilestofile"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
COPY ["file1.txt", "file2.txt", "test"]
`,
@@ -1029,20 +962,18 @@ func TestBuildJSONCopyMultipleFilesToFile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using COPY with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple copy files to file json syntax")
}
-func TestBuildAddFileWithWhitespace(t *testing.T) {
+func (s *DockerSuite) TestBuildAddFileWithWhitespace(c *check.C) {
name := "testaddfilewithwhitespace"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN mkdir "/test dir"
RUN mkdir "/test_dir"
@@ -1068,18 +999,16 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`,
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add file with whitespace")
}
-func TestBuildCopyFileWithWhitespace(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyFileWithWhitespace(c *check.C) {
name := "testcopyfilewithwhitespace"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN mkdir "/test dir"
RUN mkdir "/test_dir"
@@ -1105,18 +1034,16 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`,
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy file with whitespace")
}
-func TestBuildAddMultipleFilesToFileWithWhitespace(t *testing.T) {
+func (s *DockerSuite) TestBuildAddMultipleFilesToFileWithWhitespace(c *check.C) {
name := "testaddmultiplefilestofilewithwhitespace"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
ADD [ "test file1", "test file2", "test" ]
`,
@@ -1126,20 +1053,18 @@ func TestBuildAddMultipleFilesToFileWithWhitespace(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using ADD with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple add files to file with whitespace")
}
-func TestBuildCopyMultipleFilesToFileWithWhitespace(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyMultipleFilesToFileWithWhitespace(c *check.C) {
name := "testcopymultiplefilestofilewithwhitespace"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
COPY [ "test file1", "test file2", "test" ]
`,
@@ -1149,26 +1074,24 @@ func TestBuildCopyMultipleFilesToFileWithWhitespace(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "When using COPY with more than one source file, the destination must be a directory and end with a /"
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err)
}
- logDone("build - multiple copy files to file with whitespace")
}
-func TestBuildCopyWildcard(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) {
name := "testcopywildcard"
- defer deleteImages(name)
server, err := fakeStorage(map[string]string{
"robots.txt": "hello",
"index.html": "world",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -1189,67 +1112,88 @@ func TestBuildCopyWildcard(t *testing.T) {
"dir/nested_dir/nest_nest_file": "2 times nested",
"dirt": "dirty",
})
- defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
+ defer ctx.Close()
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Now make sure we use a cache the 2nd time
id2, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("didn't use the cache")
+ c.Fatal("didn't use the cache")
}
- logDone("build - copy wild card")
}
-func TestBuildCopyWildcardNoFind(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) {
name := "testcopywildcardnofind"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
COPY file*.txt /tmp/
`, nil)
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImageFromContext(name, ctx, true)
if err == nil {
- t.Fatal("should have failed to find a file")
+ c.Fatal("should have failed to find a file")
}
if !strings.Contains(err.Error(), "No source files were specified") {
- t.Fatalf("Wrong error %v, must be about no source files", err)
+ c.Fatalf("Wrong error %v, must be about no source files", err)
}
- logDone("build - copy wild card no find")
}
-func TestBuildCopyWildcardCache(t *testing.T) {
- name := "testcopywildcardcache"
+func (s *DockerSuite) TestBuildCopyWildcardInName(c *check.C) {
+ name := "testcopywildcardinname"
defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
+ COPY *.txt /tmp/
+ RUN [ "$(cat /tmp/\*.txt)" = 'hi there' ]
+ `, map[string]string{"*.txt": "hi there"})
+
+ if err != nil {
+ // Normally we would do c.Fatal(err) here but given that
+ // the odds of this failing are so rare, it must be because
+ // the OS we're running the client on doesn't support * in
+ // filenames (like windows). So, instead of failing the test
+ // just let it pass. Then we don't need to explicitly
+ // say which OSs this works on or not.
+ return
+ }
+ defer ctx.Close()
+
+ _, err = buildImageFromContext(name, ctx, true)
+ if err != nil {
+ c.Fatalf("should have built: %q", err)
+ }
+}
+
+func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) {
+ name := "testcopywildcardcache"
+ ctx, err := fakeContext(`FROM busybox
COPY file1.txt /tmp/`,
map[string]string{
"file1.txt": "test1",
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Now make sure we use a cache the 2nd time even with wild cards.
@@ -1259,19 +1203,17 @@ func TestBuildCopyWildcardCache(t *testing.T) {
id2, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("didn't use the cache")
+ c.Fatal("didn't use the cache")
}
- logDone("build - copy wild card cache")
}
-func TestBuildAddSingleFileToNonExistingDir(t *testing.T) {
+func (s *DockerSuite) TestBuildAddSingleFileToNonExistingDir(c *check.C) {
name := "testaddsinglefiletononexistingdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1285,20 +1227,18 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add single file to non-existing dir")
}
-func TestBuildAddDirContentToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildAddDirContentToRoot(c *check.C) {
name := "testadddircontenttoroot"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1311,19 +1251,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
"test_dir/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add directory contents to root")
}
-func TestBuildAddDirContentToExistingDir(t *testing.T) {
+func (s *DockerSuite) TestBuildAddDirContentToExistingDir(c *check.C) {
name := "testadddircontenttoexistingdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1338,19 +1276,17 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`,
"test_dir/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add directory contents to existing dir")
}
-func TestBuildAddWholeDirToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildAddWholeDirToRoot(c *check.C) {
name := "testaddwholedirtoroot"
- defer deleteImages(name)
ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1366,40 +1302,36 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte
"test_dir/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add whole directory to root")
}
// Testing #5941
-func TestBuildAddEtcToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildAddEtcToRoot(c *check.C) {
name := "testaddetctoroot"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
ADD . /`,
map[string]string{
"etc/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add etc directory to root")
}
// Testing #9401
-func TestBuildAddPreservesFilesSpecialBits(t *testing.T) {
+func (s *DockerSuite) TestBuildAddPreservesFilesSpecialBits(c *check.C) {
name := "testaddpreservesfilesspecialbits"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
ADD suidbin /usr/bin/suidbin
RUN chmod 4755 /usr/bin/suidbin
@@ -1411,19 +1343,17 @@ RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`,
"/data/usr/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add preserves files special bits")
}
-func TestBuildCopySingleFileToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildCopySingleFileToRoot(c *check.C) {
name := "testcopysinglefiletoroot"
- defer deleteImages(name)
ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1437,48 +1367,44 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy single file to root")
}
// Issue #3960: "ADD src ." hangs - adapted for COPY
-func TestBuildCopySingleFileToWorkdir(t *testing.T) {
+func (s *DockerSuite) TestBuildCopySingleFileToWorkdir(c *check.C) {
name := "testcopysinglefiletoworkdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
COPY test_file .`,
map[string]string{
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- done := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
- }
- close(done)
+ _, err := buildImageFromContext(name, ctx, true)
+ errChan <- err
+ close(errChan)
}()
select {
case <-time.After(5 * time.Second):
- t.Fatal("Build with adding to workdir timed out")
- case <-done:
+ c.Fatal("Build with adding to workdir timed out")
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
}
- logDone("build - copy single file to workdir")
}
-func TestBuildCopySingleFileToExistDir(t *testing.T) {
+func (s *DockerSuite) TestBuildCopySingleFileToExistDir(c *check.C) {
name := "testcopysinglefiletoexistdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1493,19 +1419,17 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio'
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy single file to existing dir")
}
-func TestBuildCopySingleFileToNonExistDir(t *testing.T) {
+func (s *DockerSuite) TestBuildCopySingleFileToNonExistDir(c *check.C) {
name := "testcopysinglefiletononexistdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1519,19 +1443,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy single file to non-existing dir")
}
-func TestBuildCopyDirContentToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyDirContentToRoot(c *check.C) {
name := "testcopydircontenttoroot"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1544,19 +1466,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
"test_dir/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy directory contents to root")
}
-func TestBuildCopyDirContentToExistDir(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyDirContentToExistDir(c *check.C) {
name := "testcopydircontenttoexistdir"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1571,19 +1491,17 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`,
"test_dir/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy directory contents to existing dir")
}
-func TestBuildCopyWholeDirToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyWholeDirToRoot(c *check.C) {
name := "testcopywholedirtoroot"
- defer deleteImages(name)
ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1599,48 +1517,43 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte
"test_dir/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy whole directory to root")
}
-func TestBuildCopyEtcToRoot(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyEtcToRoot(c *check.C) {
name := "testcopyetctoroot"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
COPY . /`,
map[string]string{
"etc/test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - copy etc directory to root")
}
-func TestBuildCopyDisallowRemote(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyDisallowRemote(c *check.C) {
name := "testcopydisallowremote"
- defer deleteImages(name)
_, out, err := buildImageWithOut(name, `FROM scratch
COPY https://index.docker.io/robots.txt /`,
true)
if err == nil || !strings.Contains(out, "Source can't be a URL for COPY") {
- t.Fatalf("Error should be about disallowed remote source, got err: %s, out: %q", err, out)
+ c.Fatalf("Error should be about disallowed remote source, got err: %s, out: %q", err, out)
}
- logDone("build - copy - disallow copy from remote")
}
-func TestBuildAddBadLinks(t *testing.T) {
+func (s *DockerSuite) TestBuildAddBadLinks(c *check.C) {
const (
dockerfile = `
FROM scratch
@@ -1652,16 +1565,15 @@ func TestBuildAddBadLinks(t *testing.T) {
var (
name = "test-link-absolute"
)
- defer deleteImages(name)
ctx, err := fakeContext(dockerfile, nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
tempDir, err := ioutil.TempDir("", "test-link-absolute-temp-")
if err != nil {
- t.Fatalf("failed to create temporary directory: %s", tempDir)
+ c.Fatalf("failed to create temporary directory: %s", tempDir)
}
defer os.RemoveAll(tempDir)
@@ -1669,7 +1581,7 @@ func TestBuildAddBadLinks(t *testing.T) {
if runtime.GOOS == "windows" {
var driveLetter string
if abs, err := filepath.Abs(tempDir); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
} else {
driveLetter = abs[:1]
}
@@ -1685,7 +1597,7 @@ func TestBuildAddBadLinks(t *testing.T) {
tarOut, err := os.Create(tarPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tarWriter := tar.NewWriter(tarOut)
@@ -1701,7 +1613,7 @@ func TestBuildAddBadLinks(t *testing.T) {
err = tarWriter.WriteHeader(header)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tarWriter.Close()
@@ -1709,26 +1621,25 @@ func TestBuildAddBadLinks(t *testing.T) {
foo, err := os.Create(fooPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer foo.Close()
if _, err := foo.WriteString("test"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) {
- t.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile)
+ c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile)
}
- logDone("build - ADD must add files in container")
}
-func TestBuildAddBadLinksVolume(t *testing.T) {
+func (s *DockerSuite) TestBuildAddBadLinksVolume(c *check.C) {
const (
dockerfileTemplate = `
FROM busybox
@@ -1741,11 +1652,10 @@ func TestBuildAddBadLinksVolume(t *testing.T) {
name = "test-link-absolute-volume"
dockerfile = ""
)
- defer deleteImages(name)
tempDir, err := ioutil.TempDir("", "test-link-absolute-volume-temp-")
if err != nil {
- t.Fatalf("failed to create temporary directory: %s", tempDir)
+ c.Fatalf("failed to create temporary directory: %s", tempDir)
}
defer os.RemoveAll(tempDir)
@@ -1754,76 +1664,73 @@ func TestBuildAddBadLinksVolume(t *testing.T) {
ctx, err := fakeContext(dockerfile, nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
fooPath := filepath.Join(ctx.Dir, targetFile)
foo, err := os.Create(fooPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer foo.Close()
if _, err := foo.WriteString("test"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) {
- t.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile)
+ c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile)
}
- logDone("build - ADD should add files in volume")
}
// Issue #5270 - ensure we throw a better error than "unexpected EOF"
// when we can't access files in the context.
-func TestBuildWithInaccessibleFilesInContext(t *testing.T) {
- testRequires(t, UnixCli) // test uses chown/chmod: not available on windows
+func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) {
+ testRequires(c, UnixCli) // test uses chown/chmod: not available on windows
{
name := "testbuildinaccessiblefiles"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"fileWithoutReadAccess": "foo"})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
// This is used to ensure we detect inaccessible files early during build in the cli client
pathToFileWithoutReadAccess := filepath.Join(ctx.Dir, "fileWithoutReadAccess")
if err = os.Chown(pathToFileWithoutReadAccess, 0, 0); err != nil {
- t.Fatalf("failed to chown file to root: %s", err)
+ c.Fatalf("failed to chown file to root: %s", err)
}
if err = os.Chmod(pathToFileWithoutReadAccess, 0700); err != nil {
- t.Fatalf("failed to chmod file to 700: %s", err)
+ c.Fatalf("failed to chmod file to 700: %s", err)
}
buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name))
buildCmd.Dir = ctx.Dir
out, _, err := runCommandWithOutput(buildCmd)
if err == nil {
- t.Fatalf("build should have failed: %s %s", err, out)
+ c.Fatalf("build should have failed: %s %s", err, out)
}
// check if we've detected the failure before we started building
if !strings.Contains(out, "no permission to read from ") {
- t.Fatalf("output should've contained the string: no permission to read from but contained: %s", out)
+ c.Fatalf("output should've contained the string: no permission to read from but contained: %s", out)
}
if !strings.Contains(out, "Error checking context is accessible") {
- t.Fatalf("output should've contained the string: Error checking context is accessible")
+ c.Fatalf("output should've contained the string: Error checking context is accessible")
}
}
{
name := "testbuildinaccessibledirectory"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"directoryWeCantStat/bar": "foo"})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
// This is used to ensure we detect inaccessible directories early during build in the cli client
@@ -1831,118 +1738,111 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) {
pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar")
if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil {
- t.Fatalf("failed to chown directory to root: %s", err)
+ c.Fatalf("failed to chown directory to root: %s", err)
}
if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil {
- t.Fatalf("failed to chmod directory to 444: %s", err)
+ c.Fatalf("failed to chmod directory to 444: %s", err)
}
if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil {
- t.Fatalf("failed to chmod file to 700: %s", err)
+ c.Fatalf("failed to chmod file to 700: %s", err)
}
buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name))
buildCmd.Dir = ctx.Dir
out, _, err := runCommandWithOutput(buildCmd)
if err == nil {
- t.Fatalf("build should have failed: %s %s", err, out)
+ c.Fatalf("build should have failed: %s %s", err, out)
}
// check if we've detected the failure before we started building
if !strings.Contains(out, "can't stat") {
- t.Fatalf("output should've contained the string: can't access %s", out)
+ c.Fatalf("output should've contained the string: can't access %s", out)
}
if !strings.Contains(out, "Error checking context is accessible") {
- t.Fatalf("output should've contained the string: Error checking context is accessible")
+ c.Fatalf("output should've contained the string: Error checking context is accessible")
}
}
{
name := "testlinksok"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\nADD . /foo/", nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
target := "../../../../../../../../../../../../../../../../../../../azA"
if err := os.Symlink(filepath.Join(ctx.Dir, "g"), target); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.Remove(target)
// This is used to ensure we don't follow links when checking if everything in the context is accessible
// This test doesn't require that we run commands as an unprivileged user
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}
{
name := "testbuildignoredinaccessible"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\nADD . /foo/",
map[string]string{
"directoryWeCantStat/bar": "foo",
".dockerignore": "directoryWeCantStat",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
// This is used to ensure we don't try to add inaccessible files when they are ignored by a .dockerignore pattern
pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat")
pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar")
if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil {
- t.Fatalf("failed to chown directory to root: %s", err)
+ c.Fatalf("failed to chown directory to root: %s", err)
}
if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil {
- t.Fatalf("failed to chmod directory to 755: %s", err)
+ c.Fatalf("failed to chmod directory to 755: %s", err)
}
if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil {
- t.Fatalf("failed to chmod file to 444: %s", err)
+ c.Fatalf("failed to chmod file to 444: %s", err)
}
buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name))
buildCmd.Dir = ctx.Dir
if out, _, err := runCommandWithOutput(buildCmd); err != nil {
- t.Fatalf("build should have worked: %s %s", err, out)
+ c.Fatalf("build should have worked: %s %s", err, out)
}
}
- logDone("build - ADD from context with inaccessible files must not pass")
- logDone("build - ADD from context with accessible links must work")
- logDone("build - ADD from context with ignored inaccessible files must work")
}
-func TestBuildForceRm(t *testing.T) {
+func (s *DockerSuite) TestBuildForceRm(c *check.C) {
containerCountBefore, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
name := "testbuildforcerm"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\nRUN true\nRUN thiswillfail", nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
buildCmd := exec.Command(dockerBinary, "build", "-t", name, "--force-rm", ".")
buildCmd.Dir = ctx.Dir
if out, _, err := runCommandWithOutput(buildCmd); err == nil {
- t.Fatalf("failed to build the image: %s, %v", out, err)
+ c.Fatalf("failed to build the image: %s, %v", out, err)
}
containerCountAfter, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
if containerCountBefore != containerCountAfter {
- t.Fatalf("--force-rm shouldn't have left containers behind")
+ c.Fatalf("--force-rm shouldn't have left containers behind")
}
- logDone("build - ensure --force-rm doesn't leave containers behind")
}
// Test that an infinite sleep during a build is killed if the client disconnects.
@@ -1952,95 +1852,79 @@ func TestBuildForceRm(t *testing.T) {
// * Run a 1-year-long sleep from a docker build.
// * When docker events sees container start, close the "docker build" command
// * Wait for docker events to emit a dying event.
-func TestBuildCancelationKillsSleep(t *testing.T) {
- // TODO(jfrazelle): Make this work on Windows.
- testRequires(t, SameHostDaemon)
-
+func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) {
name := "testbuildcancelation"
- defer deleteImages(name)
- defer deleteAllContainers()
// (Note: one year, will never finish)
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- var wg sync.WaitGroup
- defer wg.Wait()
-
finish := make(chan struct{})
defer close(finish)
eventStart := make(chan struct{})
eventDie := make(chan struct{})
+ containerID := make(chan string)
- // Start one second ago, to avoid rounding problems
- startEpoch := time.Now().Add(-1 * time.Second)
+ startEpoch := daemonTime(c).Unix()
+ // Watch for events since epoch.
+ eventsCmd := exec.Command(
+ dockerBinary, "events",
+ "--since", strconv.FormatInt(startEpoch, 10))
+ stdout, err := eventsCmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ if err := eventsCmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ defer eventsCmd.Process.Kill()
// Goroutine responsible for watching start/die events from `docker events`
- wg.Add(1)
go func() {
- defer wg.Done()
+ cid := <-containerID
- // Watch for events since epoch.
- eventsCmd := exec.Command(dockerBinary, "events",
- "-since", fmt.Sprint(startEpoch.Unix()))
- stdout, err := eventsCmd.StdoutPipe()
- err = eventsCmd.Start()
- if err != nil {
- t.Fatalf("failed to start 'docker events': %s", err)
- }
-
- go func() {
- <-finish
- eventsCmd.Process.Kill()
- }()
-
- var started, died bool
- matchStart := regexp.MustCompile(" \\(from busybox\\:latest\\) start$")
- matchDie := regexp.MustCompile(" \\(from busybox\\:latest\\) die$")
+ matchStart := regexp.MustCompile(cid + `(.*) start$`)
+ matchDie := regexp.MustCompile(cid + `(.*) die$`)
//
// Read lines of `docker events` looking for container start and stop.
//
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
- if ok := matchStart.MatchString(scanner.Text()); ok {
- if started {
- t.Fatal("assertion fail: more than one container started")
- }
+ switch {
+ case matchStart.MatchString(scanner.Text()):
close(eventStart)
- started = true
- }
- if ok := matchDie.MatchString(scanner.Text()); ok {
- if died {
- t.Fatal("assertion fail: more than one container died")
- }
+ case matchDie.MatchString(scanner.Text()):
close(eventDie)
- died = true
}
}
-
- err = eventsCmd.Wait()
- if err != nil && !IsKilled(err) {
- t.Fatalf("docker events had bad exit status: %s", err)
- }
}()
buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
buildCmd.Dir = ctx.Dir
- buildCmd.Stdout = os.Stdout
- err = buildCmd.Start()
- if err != nil {
- t.Fatalf("failed to run build: %s", err)
+ stdoutBuild, err := buildCmd.StdoutPipe()
+ if err := buildCmd.Start(); err != nil {
+ c.Fatalf("failed to run build: %s", err)
+ }
+
+ matchCID := regexp.MustCompile("Running in ")
+ scanner := bufio.NewScanner(stdoutBuild)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if ok := matchCID.MatchString(line); ok {
+ containerID <- line[len(line)-12:]
+ break
+ }
}
select {
- case <-time.After(30 * time.Second):
- t.Fatal("failed to observe build container start in timely fashion")
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe build container start in timely fashion")
case <-eventStart:
// Proceeds from here when we see the container fly past in the
// output of "docker events".
@@ -2049,56 +1933,52 @@ func TestBuildCancelationKillsSleep(t *testing.T) {
// Send a kill to the `docker build` command.
// Causes the underlying build to be cancelled due to socket close.
- err = buildCmd.Process.Kill()
- if err != nil {
- t.Fatalf("error killing build command: %s", err)
+ if err := buildCmd.Process.Kill(); err != nil {
+ c.Fatalf("error killing build command: %s", err)
}
// Get the exit status of `docker build`, check it exited because killed.
- err = buildCmd.Wait()
- if err != nil && !IsKilled(err) {
- t.Fatalf("wait failed during build run: %T %s", err, err)
+ if err := buildCmd.Wait(); err != nil && !IsKilled(err) {
+ c.Fatalf("wait failed during build run: %T %s", err, err)
}
select {
- case <-time.After(30 * time.Second):
+ case <-time.After(5 * time.Second):
// If we don't get here in a timely fashion, it wasn't killed.
- t.Fatal("container cancel did not succeed")
+ c.Fatal("container cancel did not succeed")
case <-eventDie:
// We saw the container shut down in the `docker events` stream,
// as expected.
}
- logDone("build - ensure canceled job finishes immediately")
}
-func TestBuildRm(t *testing.T) {
+func (s *DockerSuite) TestBuildRm(c *check.C) {
name := "testbuildrm"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\nADD foo /\nADD foo /", map[string]string{"foo": "bar"})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
{
containerCountBefore, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "--rm", "-t", name, ".")
+ out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm", "-t", name, ".")
if err != nil {
- t.Fatal("failed to build the image", out)
+ c.Fatal("failed to build the image", out)
}
containerCountAfter, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
if containerCountBefore != containerCountAfter {
- t.Fatalf("-rm shouldn't have left containers behind")
+ c.Fatalf("-rm shouldn't have left containers behind")
}
deleteImages(name)
}
@@ -2106,22 +1986,22 @@ func TestBuildRm(t *testing.T) {
{
containerCountBefore, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", name, ".")
+ out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name, ".")
if err != nil {
- t.Fatal("failed to build the image", out)
+ c.Fatal("failed to build the image", out)
}
containerCountAfter, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
if containerCountBefore != containerCountAfter {
- t.Fatalf("--rm shouldn't have left containers behind")
+ c.Fatalf("--rm shouldn't have left containers behind")
}
deleteImages(name)
}
@@ -2129,33 +2009,30 @@ func TestBuildRm(t *testing.T) {
{
containerCountBefore, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "--rm=false", "-t", name, ".")
+ out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm=false", "-t", name, ".")
if err != nil {
- t.Fatal("failed to build the image", out)
+ c.Fatal("failed to build the image", out)
}
containerCountAfter, err := getContainerCount()
if err != nil {
- t.Fatalf("failed to get the container count: %s", err)
+ c.Fatalf("failed to get the container count: %s", err)
}
if containerCountBefore == containerCountAfter {
- t.Fatalf("--rm=false should have left containers behind")
+ c.Fatalf("--rm=false should have left containers behind")
}
- deleteAllContainers()
deleteImages(name)
}
- logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default")
- logDone("build - ensure --rm=false overrides the default")
}
-func TestBuildWithVolumes(t *testing.T) {
+func (s *DockerSuite) TestBuildWithVolumes(c *check.C) {
var (
result map[string]map[string]struct{}
name = "testbuildvolumes"
@@ -2171,7 +2048,6 @@ func TestBuildWithVolumes(t *testing.T) {
"/test8]": emptyMap,
}
)
- defer deleteImages(name)
_, err := buildImage(name,
`FROM scratch
VOLUME /test1
@@ -2182,52 +2058,48 @@ func TestBuildWithVolumes(t *testing.T) {
`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Volumes")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
err = unmarshalJSON([]byte(res), &result)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
equal := reflect.DeepEqual(&result, &expected)
if !equal {
- t.Fatalf("Volumes %s, expected %s", result, expected)
+ c.Fatalf("Volumes %s, expected %s", result, expected)
}
- logDone("build - with volumes")
}
-func TestBuildMaintainer(t *testing.T) {
+func (s *DockerSuite) TestBuildMaintainer(c *check.C) {
name := "testbuildmaintainer"
expected := "dockerio"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM scratch
MAINTAINER dockerio`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Author")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Maintainer %s, expected %s", res, expected)
+ c.Fatalf("Maintainer %s, expected %s", res, expected)
}
- logDone("build - maintainer")
}
-func TestBuildUser(t *testing.T) {
+func (s *DockerSuite) TestBuildUser(c *check.C) {
name := "testbuilduser"
expected := "dockerio"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
@@ -2235,22 +2107,20 @@ func TestBuildUser(t *testing.T) {
RUN [ $(whoami) = 'dockerio' ]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.User")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("User %s, expected %s", res, expected)
+ c.Fatalf("User %s, expected %s", res, expected)
}
- logDone("build - user")
}
-func TestBuildRelativeWorkdir(t *testing.T) {
+func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) {
name := "testbuildrelativeworkdir"
expected := "/test2/test3"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
RUN [ "$PWD" = '/' ]
@@ -2262,22 +2132,20 @@ func TestBuildRelativeWorkdir(t *testing.T) {
RUN [ "$PWD" = '/test2/test3' ]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.WorkingDir")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Workdir %s, expected %s", res, expected)
+ c.Fatalf("Workdir %s, expected %s", res, expected)
}
- logDone("build - relative workdir")
}
-func TestBuildWorkdirWithEnvVariables(t *testing.T) {
+func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) {
name := "testbuildworkdirwithenvvariables"
expected := "/test1/test2"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
ENV DIRPATH /test1
@@ -2286,21 +2154,19 @@ func TestBuildWorkdirWithEnvVariables(t *testing.T) {
WORKDIR $SUBDIRNAME/$MISSING_VAR`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.WorkingDir")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Workdir %s, expected %s", res, expected)
+ c.Fatalf("Workdir %s, expected %s", res, expected)
}
- logDone("build - workdir with env variables")
}
-func TestBuildRelativeCopy(t *testing.T) {
+func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) {
name := "testbuildrelativecopy"
- defer deleteImages(name)
dockerfile := `
FROM busybox
WORKDIR /test1
@@ -2329,19 +2195,17 @@ func TestBuildRelativeCopy(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImageFromContext(name, ctx, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - relative copy/add")
}
-func TestBuildEnv(t *testing.T) {
+func (s *DockerSuite) TestBuildEnv(c *check.C) {
name := "testbuildenv"
expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
ENV PATH /test:$PATH
@@ -2349,116 +2213,106 @@ func TestBuildEnv(t *testing.T) {
RUN [ $(env | grep PORT) = 'PORT=2375' ]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Env")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Env %s, expected %s", res, expected)
+ c.Fatalf("Env %s, expected %s", res, expected)
}
- logDone("build - env")
}
-func TestBuildContextCleanup(t *testing.T) {
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestBuildContextCleanup(c *check.C) {
+ testRequires(c, SameHostDaemon)
name := "testbuildcontextcleanup"
- defer deleteImages(name)
entries, err := ioutil.ReadDir("/var/lib/docker/tmp")
if err != nil {
- t.Fatalf("failed to list contents of tmp dir: %s", err)
+ c.Fatalf("failed to list contents of tmp dir: %s", err)
}
_, err = buildImage(name,
`FROM scratch
ENTRYPOINT ["/bin/echo"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp")
if err != nil {
- t.Fatalf("failed to list contents of tmp dir: %s", err)
+ c.Fatalf("failed to list contents of tmp dir: %s", err)
}
if err = compareDirectoryEntries(entries, entriesFinal); err != nil {
- t.Fatalf("context should have been deleted, but wasn't")
+ c.Fatalf("context should have been deleted, but wasn't")
}
- logDone("build - verify context cleanup works properly")
}
-func TestBuildContextCleanupFailedBuild(t *testing.T) {
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) {
+ testRequires(c, SameHostDaemon)
name := "testbuildcontextcleanup"
- defer deleteImages(name)
- defer deleteAllContainers()
entries, err := ioutil.ReadDir("/var/lib/docker/tmp")
if err != nil {
- t.Fatalf("failed to list contents of tmp dir: %s", err)
+ c.Fatalf("failed to list contents of tmp dir: %s", err)
}
_, err = buildImage(name,
`FROM scratch
RUN /non/existing/command`,
true)
if err == nil {
- t.Fatalf("expected build to fail, but it didn't")
+ c.Fatalf("expected build to fail, but it didn't")
}
entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp")
if err != nil {
- t.Fatalf("failed to list contents of tmp dir: %s", err)
+ c.Fatalf("failed to list contents of tmp dir: %s", err)
}
if err = compareDirectoryEntries(entries, entriesFinal); err != nil {
- t.Fatalf("context should have been deleted, but wasn't")
+ c.Fatalf("context should have been deleted, but wasn't")
}
- logDone("build - verify context cleanup works properly after an unsuccessful build")
}
-func TestBuildCmd(t *testing.T) {
+func (s *DockerSuite) TestBuildCmd(c *check.C) {
name := "testbuildcmd"
- expected := "[/bin/echo Hello World]"
- defer deleteImages(name)
+ expected := "{[/bin/echo Hello World]}"
_, err := buildImage(name,
`FROM scratch
CMD ["/bin/echo", "Hello World"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Cmd")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Cmd %s, expected %s", res, expected)
+ c.Fatalf("Cmd %s, expected %s", res, expected)
}
- logDone("build - cmd")
}
-func TestBuildExpose(t *testing.T) {
+func (s *DockerSuite) TestBuildExpose(c *check.C) {
name := "testbuildexpose"
- expected := "map[2375/tcp:map[]]"
- defer deleteImages(name)
+ expected := "map[2375/tcp:{}]"
_, err := buildImage(name,
`FROM scratch
EXPOSE 2375`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.ExposedPorts")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Exposed ports %s, expected %s", res, expected)
+ c.Fatalf("Exposed ports %s, expected %s", res, expected)
}
- logDone("build - expose")
}
-func TestBuildExposeMorePorts(t *testing.T) {
+func (s *DockerSuite) TestBuildExposeMorePorts(c *check.C) {
// start building docker file with a large number of ports
portList := make([]string, 50)
line := make([]string, 100)
@@ -2484,127 +2338,118 @@ func TestBuildExposeMorePorts(t *testing.T) {
tmpl.Execute(buf, portList)
name := "testbuildexpose"
- defer deleteImages(name)
_, err := buildImage(name, buf.String(), true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// check if all the ports are saved inside Config.ExposedPorts
res, err := inspectFieldJSON(name, "Config.ExposedPorts")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var exposedPorts map[string]interface{}
if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
for _, p := range expectedPorts {
ep := fmt.Sprintf("%d/tcp", p)
if _, ok := exposedPorts[ep]; !ok {
- t.Errorf("Port(%s) is not exposed", ep)
+ c.Errorf("Port(%s) is not exposed", ep)
} else {
delete(exposedPorts, ep)
}
}
if len(exposedPorts) != 0 {
- t.Errorf("Unexpected extra exposed ports %v", exposedPorts)
+ c.Errorf("Unexpected extra exposed ports %v", exposedPorts)
}
- logDone("build - expose large number of ports")
}
-func TestBuildExposeOrder(t *testing.T) {
+func (s *DockerSuite) TestBuildExposeOrder(c *check.C) {
buildID := func(name, exposed string) string {
_, err := buildImage(name, fmt.Sprintf(`FROM scratch
EXPOSE %s`, exposed), true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id, err := inspectField(name, "Id")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
return id
}
id1 := buildID("testbuildexpose1", "80 2375")
id2 := buildID("testbuildexpose2", "2375 80")
- defer deleteImages("testbuildexpose1", "testbuildexpose2")
if id1 != id2 {
- t.Errorf("EXPOSE should invalidate the cache only when ports actually changed")
+ c.Errorf("EXPOSE should invalidate the cache only when ports actually changed")
}
- logDone("build - expose order")
}
-func TestBuildExposeUpperCaseProto(t *testing.T) {
+func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) {
name := "testbuildexposeuppercaseproto"
- expected := "map[5678/udp:map[]]"
- defer deleteImages(name)
+ expected := "map[5678/udp:{}]"
_, err := buildImage(name,
`FROM scratch
EXPOSE 5678/UDP`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.ExposedPorts")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Exposed ports %s, expected %s", res, expected)
+ c.Fatalf("Exposed ports %s, expected %s", res, expected)
}
- logDone("build - expose port with upper case proto")
}
-func TestBuildExposeHostPort(t *testing.T) {
+func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) {
// start building docker file with ip:hostPort:containerPort
name := "testbuildexpose"
- expected := "map[5678/tcp:map[]]"
- defer deleteImages(name)
+ expected := "map[5678/tcp:{}]"
_, out, err := buildImageWithOut(name,
`FROM scratch
EXPOSE 192.168.1.2:2375:5678`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(out, "to map host ports to container ports (ip:hostPort:containerPort) is deprecated.") {
- t.Fatal("Missing warning message")
+ c.Fatal("Missing warning message")
}
res, err := inspectField(name, "Config.ExposedPorts")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Exposed ports %s, expected %s", res, expected)
+ c.Fatalf("Exposed ports %s, expected %s", res, expected)
}
- logDone("build - ignore exposing host's port")
}
-func TestBuildEmptyEntrypointInheritance(t *testing.T) {
+func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) {
name := "testbuildentrypointinheritance"
name2 := "testbuildentrypointinheritance2"
- defer deleteImages(name, name2)
_, err := buildImage(name,
`FROM busybox
ENTRYPOINT ["/bin/echo"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Entrypoint")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- expected := "[/bin/echo]"
+ expected := "{[/bin/echo]}"
if res != expected {
- t.Fatalf("Entrypoint %s, expected %s", res, expected)
+ c.Fatalf("Entrypoint %s, expected %s", res, expected)
}
_, err = buildImage(name2,
@@ -2612,69 +2457,64 @@ func TestBuildEmptyEntrypointInheritance(t *testing.T) {
ENTRYPOINT []`, name),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err = inspectField(name2, "Config.Entrypoint")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- expected = "[]"
+ expected = "{[]}"
if res != expected {
- t.Fatalf("Entrypoint %s, expected %s", res, expected)
+ c.Fatalf("Entrypoint %s, expected %s", res, expected)
}
- logDone("build - empty entrypoint inheritance")
}
-func TestBuildEmptyEntrypoint(t *testing.T) {
+func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) {
name := "testbuildentrypoint"
- defer deleteImages(name)
- expected := "[]"
+ expected := "{[]}"
_, err := buildImage(name,
`FROM busybox
ENTRYPOINT []`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Entrypoint")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Entrypoint %s, expected %s", res, expected)
+ c.Fatalf("Entrypoint %s, expected %s", res, expected)
}
- logDone("build - empty entrypoint")
}
-func TestBuildEntrypoint(t *testing.T) {
+func (s *DockerSuite) TestBuildEntrypoint(c *check.C) {
name := "testbuildentrypoint"
- expected := "[/bin/echo]"
- defer deleteImages(name)
+ expected := "{[/bin/echo]}"
_, err := buildImage(name,
`FROM scratch
ENTRYPOINT ["/bin/echo"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Entrypoint")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Entrypoint %s, expected %s", res, expected)
+ c.Fatalf("Entrypoint %s, expected %s", res, expected)
}
- logDone("build - entrypoint")
}
// #6445 ensure ONBUILD triggers aren't committed to grandchildren
-func TestBuildOnBuildLimitedInheritence(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) {
var (
out2, out3 string
)
@@ -2687,15 +2527,14 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) {
`
ctx, err := fakeContext(dockerfile1, nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- out1, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", name1, ".")
+ out1, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name1, ".")
if err != nil {
- t.Fatalf("build failed to complete: %s, %v", out1, err)
+ c.Fatalf("build failed to complete: %s, %v", out1, err)
}
- defer deleteImages(name1)
}
{
name2 := "testonbuildtrigger2"
@@ -2704,15 +2543,14 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) {
`
ctx, err := fakeContext(dockerfile2, nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- out2, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-t", name2, ".")
+ out2, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name2, ".")
if err != nil {
- t.Fatalf("build failed to complete: %s, %v", out2, err)
+ c.Fatalf("build failed to complete: %s, %v", out2, err)
}
- defer deleteImages(name2)
}
{
name3 := "testonbuildtrigger3"
@@ -2721,34 +2559,31 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) {
`
ctx, err := fakeContext(dockerfile3, nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- out3, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-t", name3, ".")
+ out3, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name3, ".")
if err != nil {
- t.Fatalf("build failed to complete: %s, %v", out3, err)
+ c.Fatalf("build failed to complete: %s, %v", out3, err)
}
- defer deleteImages(name3)
}
// ONBUILD should be run in second build.
if !strings.Contains(out2, "ONBUILD PARENT") {
- t.Fatalf("ONBUILD instruction did not run in child of ONBUILD parent")
+ c.Fatalf("ONBUILD instruction did not run in child of ONBUILD parent")
}
// ONBUILD should *not* be run in third build.
if strings.Contains(out3, "ONBUILD PARENT") {
- t.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent")
+ c.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent")
}
- logDone("build - onbuild")
}
-func TestBuildWithCache(t *testing.T) {
+func (s *DockerSuite) TestBuildWithCache(c *check.C) {
name := "testbuildwithcache"
- defer deleteImages(name)
id1, err := buildImage(name,
`FROM scratch
MAINTAINER dockerio
@@ -2756,7 +2591,7 @@ func TestBuildWithCache(t *testing.T) {
ENTRYPOINT ["/bin/echo"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImage(name,
`FROM scratch
@@ -2765,18 +2600,16 @@ func TestBuildWithCache(t *testing.T) {
ENTRYPOINT ["/bin/echo"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("The cache should have been used but hasn't.")
+ c.Fatal("The cache should have been used but hasn't.")
}
- logDone("build - with cache")
}
-func TestBuildWithoutCache(t *testing.T) {
+func (s *DockerSuite) TestBuildWithoutCache(c *check.C) {
name := "testbuildwithoutcache"
name2 := "testbuildwithoutcache2"
- defer deleteImages(name, name2)
id1, err := buildImage(name,
`FROM scratch
MAINTAINER dockerio
@@ -2784,7 +2617,7 @@ func TestBuildWithoutCache(t *testing.T) {
ENTRYPOINT ["/bin/echo"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImage(name2,
@@ -2794,18 +2627,15 @@ func TestBuildWithoutCache(t *testing.T) {
ENTRYPOINT ["/bin/echo"]`,
false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
- logDone("build - without cache")
}
-func TestBuildConditionalCache(t *testing.T) {
+func (s *DockerSuite) TestBuildConditionalCache(c *check.C) {
name := "testbuildconditionalcache"
- name2 := "testbuildconditionalcache2"
- defer deleteImages(name, name2)
dockerfile := `
FROM busybox
@@ -2814,42 +2644,39 @@ func TestBuildConditionalCache(t *testing.T) {
"foo": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatalf("Error building #1: %s", err)
+ c.Fatalf("Error building #1: %s", err)
}
if err := ctx.Add("foo", "bye"); err != nil {
- t.Fatalf("Error modifying foo: %s", err)
+ c.Fatalf("Error modifying foo: %s", err)
}
id2, err := buildImageFromContext(name, ctx, false)
if err != nil {
- t.Fatalf("Error building #2: %s", err)
+ c.Fatalf("Error building #2: %s", err)
}
if id2 == id1 {
- t.Fatal("Should not have used the cache")
+ c.Fatal("Should not have used the cache")
}
id3, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatalf("Error building #3: %s", err)
+ c.Fatalf("Error building #3: %s", err)
}
if id3 != id2 {
- t.Fatal("Should have used the cache")
+ c.Fatal("Should have used the cache")
}
-
- logDone("build - conditional cache")
}
-func TestBuildADDLocalFileWithCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddLocalFileWithCache(c *check.C) {
name := "testbuildaddlocalfilewithcache"
name2 := "testbuildaddlocalfilewithcache2"
- defer deleteImages(name, name2)
dockerfile := `
FROM busybox
MAINTAINER dockerio
@@ -2860,26 +2687,24 @@ func TestBuildADDLocalFileWithCache(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("The cache should have been used but hasn't.")
+ c.Fatal("The cache should have been used but hasn't.")
}
- logDone("build - add local file with cache")
}
-func TestBuildADDMultipleLocalFileWithCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddMultipleLocalFileWithCache(c *check.C) {
name := "testbuildaddmultiplelocalfilewithcache"
name2 := "testbuildaddmultiplelocalfilewithcache2"
- defer deleteImages(name, name2)
dockerfile := `
FROM busybox
MAINTAINER dockerio
@@ -2890,26 +2715,24 @@ func TestBuildADDMultipleLocalFileWithCache(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("The cache should have been used but hasn't.")
+ c.Fatal("The cache should have been used but hasn't.")
}
- logDone("build - add multiple local files with cache")
}
-func TestBuildADDLocalFileWithoutCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddLocalFileWithoutCache(c *check.C) {
name := "testbuildaddlocalfilewithoutcache"
name2 := "testbuildaddlocalfilewithoutcache2"
- defer deleteImages(name, name2)
dockerfile := `
FROM busybox
MAINTAINER dockerio
@@ -2920,26 +2743,24 @@ func TestBuildADDLocalFileWithoutCache(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
- logDone("build - add local file without cache")
}
-func TestBuildCopyDirButNotFile(t *testing.T) {
+func (s *DockerSuite) TestBuildCopyDirButNotFile(c *check.C) {
name := "testbuildcopydirbutnotfile"
name2 := "testbuildcopydirbutnotfile2"
- defer deleteImages(name, name2)
dockerfile := `
FROM scratch
COPY dir /tmp/`
@@ -2948,33 +2769,31 @@ func TestBuildCopyDirButNotFile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Check that adding file with similar name doesn't mess with cache
if err := ctx.Add("dir_file", "hello2"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("The cache should have been used but wasn't")
+ c.Fatal("The cache should have been used but wasn't")
}
- logDone("build - add current directory but not file")
}
-func TestBuildADDCurrentDirWithCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddCurrentDirWithCache(c *check.C) {
name := "testbuildaddcurrentdirwithcache"
name2 := name + "2"
name3 := name + "3"
name4 := name + "4"
name5 := name + "5"
- defer deleteImages(name, name2, name3, name4, name5)
dockerfile := `
FROM scratch
MAINTAINER dockerio
@@ -2984,60 +2803,58 @@ func TestBuildADDCurrentDirWithCache(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Check that adding file invalidate cache of "ADD ."
if err := ctx.Add("bar", "hello2"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
// Check that changing file invalidate cache of "ADD ."
if err := ctx.Add("foo", "hello1"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id3, err := buildImageFromContext(name3, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id2 == id3 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
// Check that changing file to same content invalidate cache of "ADD ."
time.Sleep(1 * time.Second) // wait second because of mtime precision
if err := ctx.Add("foo", "hello1"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id4, err := buildImageFromContext(name4, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id3 == id4 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
id5, err := buildImageFromContext(name5, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id4 != id5 {
- t.Fatal("The cache should have been used but hasn't.")
+ c.Fatal("The cache should have been used but hasn't.")
}
- logDone("build - add current directory with cache")
}
-func TestBuildADDCurrentDirWithoutCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddCurrentDirWithoutCache(c *check.C) {
name := "testbuildaddcurrentdirwithoutcache"
name2 := "testbuildaddcurrentdirwithoutcache2"
- defer deleteImages(name, name2)
dockerfile := `
FROM scratch
MAINTAINER dockerio
@@ -3047,30 +2864,28 @@ func TestBuildADDCurrentDirWithoutCache(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
- logDone("build - add current directory without cache")
}
-func TestBuildADDRemoteFileWithCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddRemoteFileWithCache(c *check.C) {
name := "testbuildaddremotefilewithcache"
- defer deleteImages(name)
server, err := fakeStorage(map[string]string{
"baz": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3080,7 +2895,7 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) {
ADD %s/baz /usr/lib/baz/quux`, server.URL()),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImage(name,
fmt.Sprintf(`FROM scratch
@@ -3088,23 +2903,21 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) {
ADD %s/baz /usr/lib/baz/quux`, server.URL()),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("The cache should have been used but hasn't.")
+ c.Fatal("The cache should have been used but hasn't.")
}
- logDone("build - add remote file with cache")
}
-func TestBuildADDRemoteFileWithoutCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddRemoteFileWithoutCache(c *check.C) {
name := "testbuildaddremotefilewithoutcache"
name2 := "testbuildaddremotefilewithoutcache2"
- defer deleteImages(name, name2)
server, err := fakeStorage(map[string]string{
"baz": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3114,7 +2927,7 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) {
ADD %s/baz /usr/lib/baz/quux`, server.URL()),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImage(name2,
fmt.Sprintf(`FROM scratch
@@ -3122,26 +2935,23 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) {
ADD %s/baz /usr/lib/baz/quux`, server.URL()),
false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
- logDone("build - add remote file without cache")
}
-func TestBuildADDRemoteFileMTime(t *testing.T) {
+func (s *DockerSuite) TestBuildAddRemoteFileMTime(c *check.C) {
name := "testbuildaddremotefilemtime"
name2 := name + "2"
name3 := name + "3"
name4 := name + "4"
- defer deleteImages(name, name2, name3, name4)
-
files := map[string]string{"baz": "hello"}
server, err := fakeStorage(files)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3149,21 +2959,21 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
MAINTAINER dockerio
ADD %s/baz /usr/lib/baz/quux`, server.URL()), nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("The cache should have been used but wasn't - #1")
+ c.Fatal("The cache should have been used but wasn't - #1")
}
// Now create a different server withsame contents (causes different mtim)
@@ -3174,7 +2984,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
server2, err := fakeStorage(files)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server2.Close()
@@ -3182,36 +2992,34 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
MAINTAINER dockerio
ADD %s/baz /usr/lib/baz/quux`, server2.URL()), nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx2.Close()
id3, err := buildImageFromContext(name3, ctx2, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id3 {
- t.Fatal("The cache should not have been used but was")
+ c.Fatal("The cache should not have been used but was")
}
// And for good measure do it again and make sure cache is used this time
id4, err := buildImageFromContext(name4, ctx2, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id3 != id4 {
- t.Fatal("The cache should have been used but wasn't - #2")
+ c.Fatal("The cache should have been used but wasn't - #2")
}
- logDone("build - add remote file testing mtime")
}
-func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddLocalAndRemoteFilesWithCache(c *check.C) {
name := "testbuildaddlocalandremotefilewithcache"
- defer deleteImages(name)
server, err := fakeStorage(map[string]string{
"baz": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3223,24 +3031,23 @@ func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) {
"foo": "hello world",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 != id2 {
- t.Fatal("The cache should have been used but hasn't.")
+ c.Fatal("The cache should have been used but hasn't.")
}
- logDone("build - add local and remote file with cache")
}
-func testContextTar(t *testing.T, compression archive.Compression) {
+func testContextTar(c *check.C, compression archive.Compression) {
ctx, err := fakeContext(
`FROM busybox
ADD foo /foo
@@ -3251,57 +3058,51 @@ CMD ["cat", "/foo"]`,
)
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
context, err := archive.Tar(ctx.Dir, compression)
if err != nil {
- t.Fatalf("failed to build context tar: %v", err)
+ c.Fatalf("failed to build context tar: %v", err)
}
name := "contexttar"
buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-")
- defer deleteImages(name)
buildCmd.Stdin = context
if out, _, err := runCommandWithOutput(buildCmd); err != nil {
- t.Fatalf("build failed to complete: %v %v", out, err)
+ c.Fatalf("build failed to complete: %v %v", out, err)
}
- logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", compression))
}
-func TestBuildContextTarGzip(t *testing.T) {
- testContextTar(t, archive.Gzip)
+func (s *DockerSuite) TestBuildContextTarGzip(c *check.C) {
+ testContextTar(c, archive.Gzip)
}
-func TestBuildContextTarNoCompression(t *testing.T) {
- testContextTar(t, archive.Uncompressed)
+func (s *DockerSuite) TestBuildContextTarNoCompression(c *check.C) {
+ testContextTar(c, archive.Uncompressed)
}
-func TestBuildNoContext(t *testing.T) {
+func (s *DockerSuite) TestBuildNoContext(c *check.C) {
buildCmd := exec.Command(dockerBinary, "build", "-t", "nocontext", "-")
buildCmd.Stdin = strings.NewReader("FROM busybox\nCMD echo ok\n")
if out, _, err := runCommandWithOutput(buildCmd); err != nil {
- t.Fatalf("build failed to complete: %v %v", out, err)
+ c.Fatalf("build failed to complete: %v %v", out, err)
}
- if out, _, err := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" || err != nil {
- t.Fatalf("run produced invalid output: %q, expected %q", out, "ok")
+ if out, _ := dockerCmd(c, "run", "--rm", "nocontext"); out != "ok\n" {
+ c.Fatalf("run produced invalid output: %q, expected %q", out, "ok")
}
-
- deleteImages("nocontext")
- logDone("build - build an image with no context")
}
// TODO: TestCaching
-func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) {
+func (s *DockerSuite) TestBuildAddLocalAndRemoteFilesWithoutCache(c *check.C) {
name := "testbuildaddlocalandremotefilewithoutcache"
name2 := "testbuildaddlocalandremotefilewithoutcache2"
- defer deleteImages(name, name2)
server, err := fakeStorage(map[string]string{
"baz": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3313,26 +3114,24 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) {
"foo": "hello world",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
id1, err := buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImageFromContext(name2, ctx, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("The cache should have been invalided but hasn't.")
+ c.Fatal("The cache should have been invalided but hasn't.")
}
- logDone("build - add local and remote file without cache")
}
-func TestBuildWithVolumeOwnership(t *testing.T) {
+func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) {
name := "testbuildimg"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox:latest
@@ -3341,36 +3140,34 @@ func TestBuildWithVolumeOwnership(t *testing.T) {
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "--rm", "testbuildimg", "ls", "-la", "/test")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if expected := "drw-------"; !strings.Contains(out, expected) {
- t.Fatalf("expected %s received %s", expected, out)
+ c.Fatalf("expected %s received %s", expected, out)
}
if expected := "daemon daemon"; !strings.Contains(out, expected) {
- t.Fatalf("expected %s received %s", expected, out)
+ c.Fatalf("expected %s received %s", expected, out)
}
- logDone("build - volume ownership")
}
// testing #1405 - config.Cmd does not get cleaned up if
// utilizing cache
-func TestBuildEntrypointRunCleanup(t *testing.T) {
+func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) {
name := "testbuildcmdcleanup"
- defer deleteImages(name)
if _, err := buildImage(name,
`FROM busybox
RUN echo "hello"`,
true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
ctx, err := fakeContext(`FROM busybox
@@ -3382,25 +3179,23 @@ func TestBuildEntrypointRunCleanup(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Cmd")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Cmd must be cleaned up
- if expected := ""; res != expected {
- t.Fatalf("Cmd %s, expected %s", res, expected)
+ if res != "" {
+ c.Fatalf("Cmd %s, expected nil", res)
}
- logDone("build - cleanup cmd after RUN")
}
-func TestBuildForbiddenContextPath(t *testing.T) {
+func (s *DockerSuite) TestBuildForbiddenContextPath(c *check.C) {
name := "testbuildforbidpath"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
ADD ../../ test/
`,
@@ -3410,51 +3205,47 @@ func TestBuildForbiddenContextPath(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "Forbidden path outside the build context: ../../ "
if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) {
- t.Fatalf("Wrong error: (should contain \"%s\") got:\n%v", expected, err)
+ c.Fatalf("Wrong error: (should contain \"%s\") got:\n%v", expected, err)
}
- logDone("build - forbidden context path")
}
-func TestBuildADDFileNotFound(t *testing.T) {
+func (s *DockerSuite) TestBuildAddFileNotFound(c *check.C) {
name := "testbuildaddnotfound"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM scratch
ADD foo /usr/local/bar`,
map[string]string{"bar": "hello"})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
if !strings.Contains(err.Error(), "foo: no such file or directory") {
- t.Fatalf("Wrong error %v, must be about missing foo file or directory", err)
+ c.Fatalf("Wrong error %v, must be about missing foo file or directory", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - add file not found")
}
-func TestBuildInheritance(t *testing.T) {
+func (s *DockerSuite) TestBuildInheritance(c *check.C) {
name := "testbuildinheritance"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM scratch
EXPOSE 2375`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
ports1, err := inspectField(name, "Config.ExposedPorts")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImage(name,
@@ -3462,133 +3253,118 @@ func TestBuildInheritance(t *testing.T) {
ENTRYPOINT ["/bin/echo"]`, name),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Entrypoint")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if expected := "[/bin/echo]"; res != expected {
- t.Fatalf("Entrypoint %s, expected %s", res, expected)
+ if expected := "{[/bin/echo]}"; res != expected {
+ c.Fatalf("Entrypoint %s, expected %s", res, expected)
}
ports2, err := inspectField(name, "Config.ExposedPorts")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if ports1 != ports2 {
- t.Fatalf("Ports must be same: %s != %s", ports1, ports2)
+ c.Fatalf("Ports must be same: %s != %s", ports1, ports2)
}
- logDone("build - inheritance")
}
-func TestBuildFails(t *testing.T) {
+func (s *DockerSuite) TestBuildFails(c *check.C) {
name := "testbuildfails"
- defer deleteImages(name)
- defer deleteAllContainers()
_, err := buildImage(name,
`FROM busybox
RUN sh -c "exit 23"`,
true)
if err != nil {
if !strings.Contains(err.Error(), "returned a non-zero code: 23") {
- t.Fatalf("Wrong error %v, must be about non-zero code 23", err)
+ c.Fatalf("Wrong error %v, must be about non-zero code 23", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - unsuccessful")
}
-func TestBuildFailsDockerfileEmpty(t *testing.T) {
+func (s *DockerSuite) TestBuildFailsDockerfileEmpty(c *check.C) {
name := "testbuildfails"
- defer deleteImages(name)
_, err := buildImage(name, ``, true)
if err != nil {
- if !strings.Contains(err.Error(), "Dockerfile cannot be empty") {
- t.Fatalf("Wrong error %v, must be about empty Dockerfile", err)
+ if !strings.Contains(err.Error(), "The Dockerfile (Dockerfile) cannot be empty") {
+ c.Fatalf("Wrong error %v, must be about empty Dockerfile", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - unsuccessful with empty dockerfile")
}
-func TestBuildOnBuild(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuild(c *check.C) {
name := "testbuildonbuild"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
ONBUILD RUN touch foobar`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, err = buildImage(name,
fmt.Sprintf(`FROM %s
RUN [ -f foobar ]`, name),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - onbuild")
}
-func TestBuildOnBuildForbiddenChained(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildForbiddenChained(c *check.C) {
name := "testbuildonbuildforbiddenchained"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
ONBUILD ONBUILD RUN touch foobar`,
true)
if err != nil {
if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") {
- t.Fatalf("Wrong error %v, must be about chaining ONBUILD", err)
+ c.Fatalf("Wrong error %v, must be about chaining ONBUILD", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - onbuild forbidden chained")
}
-func TestBuildOnBuildForbiddenFrom(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildForbiddenFrom(c *check.C) {
name := "testbuildonbuildforbiddenfrom"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
ONBUILD FROM scratch`,
true)
if err != nil {
if !strings.Contains(err.Error(), "FROM isn't allowed as an ONBUILD trigger") {
- t.Fatalf("Wrong error %v, must be about FROM forbidden", err)
+ c.Fatalf("Wrong error %v, must be about FROM forbidden", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - onbuild forbidden from")
}
-func TestBuildOnBuildForbiddenMaintainer(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainer(c *check.C) {
name := "testbuildonbuildforbiddenmaintainer"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
ONBUILD MAINTAINER docker.io`,
true)
if err != nil {
if !strings.Contains(err.Error(), "MAINTAINER isn't allowed as an ONBUILD trigger") {
- t.Fatalf("Wrong error %v, must be about MAINTAINER forbidden", err)
+ c.Fatalf("Wrong error %v, must be about MAINTAINER forbidden", err)
}
} else {
- t.Fatal("Error must not be nil")
+ c.Fatal("Error must not be nil")
}
- logDone("build - onbuild forbidden maintainer")
}
// gh #2446
-func TestBuildAddToSymlinkDest(t *testing.T) {
+func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) {
name := "testbuildaddtosymlinkdest"
- defer deleteImages(name)
ctx, err := fakeContext(`FROM busybox
RUN mkdir /foo
RUN ln -s /foo /bar
@@ -3599,18 +3375,16 @@ func TestBuildAddToSymlinkDest(t *testing.T) {
"foo": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add to symlink destination")
}
-func TestBuildEscapeWhitespace(t *testing.T) {
+func (s *DockerSuite) TestBuildEscapeWhitespace(c *check.C) {
name := "testbuildescaping"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM busybox
@@ -3622,20 +3396,18 @@ docker.com>"
res, err := inspectField(name, "Author")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != "\"Docker IO \"" {
- t.Fatalf("Parsed string did not match the escaped string. Got: %q", res)
+ c.Fatalf("Parsed string did not match the escaped string. Got: %q", res)
}
- logDone("build - validate escaping whitespace")
}
-func TestBuildVerifyIntString(t *testing.T) {
+func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) {
// Verify that strings that look like ints are still passed as strings
name := "testbuildstringing"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM busybox
@@ -3644,19 +3416,17 @@ func TestBuildVerifyIntString(t *testing.T) {
out, rc, err := runCommandWithOutput(exec.Command(dockerBinary, "inspect", name))
if rc != 0 || err != nil {
- t.Fatalf("Unexcepted error from inspect: rc: %v err: %v", rc, err)
+ c.Fatalf("Unexpected error from inspect: rc: %v err: %v", rc, err)
}
if !strings.Contains(out, "\"123\"") {
- t.Fatalf("Output does not contain the int as a string:\n%s", out)
+ c.Fatalf("Output does not contain the int as a string:\n%s", out)
}
- logDone("build - verify int/strings as strings")
}
-func TestBuildDockerignore(t *testing.T) {
+func (s *DockerSuite) TestBuildDockerignore(c *check.C) {
name := "testbuilddockerignore"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /bla
@@ -3665,29 +3435,36 @@ func TestBuildDockerignore(t *testing.T) {
RUN [[ ! -e /bla/src/_vendor ]]
RUN [[ ! -e /bla/.gitignore ]]
RUN [[ ! -e /bla/README.md ]]
+ RUN [[ ! -e /bla/dir/foo ]]
+ RUN [[ ! -e /bla/foo ]]
RUN [[ ! -e /bla/.git ]]`
ctx, err := fakeContext(dockerfile, map[string]string{
"Makefile": "all:",
".git/HEAD": "ref: foo",
"src/x.go": "package main",
"src/_vendor/v.go": "package main",
+ "dir/foo": "",
".gitignore": "",
"README.md": "readme",
- ".dockerignore": ".git\npkg\n.gitignore\nsrc/_vendor\n*.md",
+ ".dockerignore": `
+.git
+pkg
+.gitignore
+src/_vendor
+*.md
+dir`,
})
- defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
+ defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - test .dockerignore")
}
-func TestBuildDockerignoreCleanPaths(t *testing.T) {
+func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) {
name := "testbuilddockerignorecleanpaths"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /tmp/
@@ -3699,19 +3476,66 @@ func TestBuildDockerignoreCleanPaths(t *testing.T) {
".dockerignore": "./foo\ndir1//foo\n./dir1/../foo2",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - test .dockerignore with clean paths")
}
-func TestBuildDockerignoringDockerfile(t *testing.T) {
- name := "testbuilddockerignoredockerfile"
+func (s *DockerSuite) TestBuildDockerignoreExceptions(c *check.C) {
+ name := "testbuilddockerignoreexceptions"
defer deleteImages(name)
dockerfile := `
+ FROM busybox
+ ADD . /bla
+ RUN [[ -f /bla/src/x.go ]]
+ RUN [[ -f /bla/Makefile ]]
+ RUN [[ ! -e /bla/src/_vendor ]]
+ RUN [[ ! -e /bla/.gitignore ]]
+ RUN [[ ! -e /bla/README.md ]]
+ RUN [[ -e /bla/dir/dir/foo ]]
+ RUN [[ ! -e /bla/dir/foo1 ]]
+ RUN [[ -f /bla/dir/e ]]
+ RUN [[ -f /bla/dir/e-dir/foo ]]
+ RUN [[ ! -e /bla/foo ]]
+ RUN [[ ! -e /bla/.git ]]`
+ ctx, err := fakeContext(dockerfile, map[string]string{
+ "Makefile": "all:",
+ ".git/HEAD": "ref: foo",
+ "src/x.go": "package main",
+ "src/_vendor/v.go": "package main",
+ "dir/foo": "",
+ "dir/foo1": "",
+ "dir/dir/f1": "",
+ "dir/dir/foo": "",
+ "dir/e": "",
+ "dir/e-dir/foo": "",
+ ".gitignore": "",
+ "README.md": "readme",
+ ".dockerignore": `
+.git
+pkg
+.gitignore
+src/_vendor
+*.md
+dir
+!dir/e*
+!dir/dir/foo`,
+ })
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer ctx.Close()
+ if _, err := buildImageFromContext(name, ctx, true); err != nil {
+ c.Fatal(err)
+ }
+}
+
+func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) {
+ name := "testbuilddockerignoredockerfile"
+ dockerfile := `
FROM busybox
ADD . /tmp/
RUN ! ls /tmp/Dockerfile
@@ -3721,26 +3545,24 @@ func TestBuildDockerignoringDockerfile(t *testing.T) {
".dockerignore": "Dockerfile\n",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't ignore Dockerfile correctly:%s", err)
+ c.Fatalf("Didn't ignore Dockerfile correctly:%s", err)
}
// now try it with ./Dockerfile
ctx.Add(".dockerignore", "./Dockerfile\n")
if _, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't ignore ./Dockerfile correctly:%s", err)
+ c.Fatalf("Didn't ignore ./Dockerfile correctly:%s", err)
}
- logDone("build - test .dockerignore of Dockerfile")
}
-func TestBuildDockerignoringRenamedDockerfile(t *testing.T) {
+func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) {
name := "testbuilddockerignoredockerfile"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /tmp/
@@ -3753,26 +3575,24 @@ func TestBuildDockerignoringRenamedDockerfile(t *testing.T) {
".dockerignore": "MyDockerfile\n",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't ignore MyDockerfile correctly:%s", err)
+ c.Fatalf("Didn't ignore MyDockerfile correctly:%s", err)
}
// now try it with ./MyDockerfile
ctx.Add(".dockerignore", "./MyDockerfile\n")
if _, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err)
+ c.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err)
}
- logDone("build - test .dockerignore of renamed Dockerfile")
}
-func TestBuildDockerignoringDockerignore(t *testing.T) {
+func (s *DockerSuite) TestBuildDockerignoringDockerignore(c *check.C) {
name := "testbuilddockerignoredockerignore"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /tmp/
@@ -3784,20 +3604,18 @@ func TestBuildDockerignoringDockerignore(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't ignore .dockerignore correctly:%s", err)
+ c.Fatalf("Didn't ignore .dockerignore correctly:%s", err)
}
- logDone("build - test .dockerignore of .dockerignore")
}
-func TestBuildDockerignoreTouchDockerfile(t *testing.T) {
+func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) {
var id1 string
var id2 string
name := "testbuilddockerignoretouchdockerfile"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /tmp/`
@@ -3807,48 +3625,46 @@ func TestBuildDockerignoreTouchDockerfile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't build it correctly:%s", err)
+ c.Fatalf("Didn't build it correctly:%s", err)
}
if id2, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't build it correctly:%s", err)
+ c.Fatalf("Didn't build it correctly:%s", err)
}
if id1 != id2 {
- t.Fatalf("Didn't use the cache - 1")
+ c.Fatalf("Didn't use the cache - 1")
}
// Now make sure touching Dockerfile doesn't invalidate the cache
if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil {
- t.Fatalf("Didn't add Dockerfile: %s", err)
+ c.Fatalf("Didn't add Dockerfile: %s", err)
}
if id2, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't build it correctly:%s", err)
+ c.Fatalf("Didn't build it correctly:%s", err)
}
if id1 != id2 {
- t.Fatalf("Didn't use the cache - 2")
+ c.Fatalf("Didn't use the cache - 2")
}
// One more time but just 'touch' it instead of changing the content
if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil {
- t.Fatalf("Didn't add Dockerfile: %s", err)
+ c.Fatalf("Didn't add Dockerfile: %s", err)
}
if id2, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("Didn't build it correctly:%s", err)
+ c.Fatalf("Didn't build it correctly:%s", err)
}
if id1 != id2 {
- t.Fatalf("Didn't use the cache - 3")
+ c.Fatalf("Didn't use the cache - 3")
}
- logDone("build - test .dockerignore touch dockerfile")
}
-func TestBuildDockerignoringWholeDir(t *testing.T) {
+func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) {
name := "testbuilddockerignorewholedir"
- defer deleteImages(name)
dockerfile := `
FROM busybox
COPY . /
@@ -3857,21 +3673,20 @@ func TestBuildDockerignoringWholeDir(t *testing.T) {
ctx, err := fakeContext(dockerfile, map[string]string{
"Dockerfile": "FROM scratch",
"Makefile": "all:",
+ ".gitignore": "",
".dockerignore": ".*\n",
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err = buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - test .dockerignore whole dir with .*")
}
-func TestBuildLineBreak(t *testing.T) {
+func (s *DockerSuite) TestBuildLineBreak(c *check.C) {
name := "testbuildlinebreak"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
RUN sh -c 'echo root:testpass \
@@ -3881,14 +3696,12 @@ RUN [ "$(cat /tmp/passwd)" = "root:testpass" ]
RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - line break with \\")
}
-func TestBuildEOLInLine(t *testing.T) {
+func (s *DockerSuite) TestBuildEOLInLine(c *check.C) {
name := "testbuildeolinline"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
RUN sh -c 'echo root:testpass > /tmp/passwd'
@@ -3898,14 +3711,12 @@ RUN [ "$(cat /tmp/passwd)" = "root:testpass" ]
RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - end of line in dockerfile instruction")
}
-func TestBuildCommentsShebangs(t *testing.T) {
+func (s *DockerSuite) TestBuildCommentsShebangs(c *check.C) {
name := "testbuildcomments"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
# This is an ordinary comment.
@@ -3918,14 +3729,12 @@ RUN [ "$(cat /hello.sh)" = $'#!/bin/sh\necho hello world' ]
RUN [ "$(/hello.sh)" = "hello world" ]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - comments and shebangs")
}
-func TestBuildUsersAndGroups(t *testing.T) {
+func (s *DockerSuite) TestBuildUsersAndGroups(c *check.C) {
name := "testbuildusers"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
@@ -3982,14 +3791,12 @@ USER 1042:1043
RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - users and groups")
}
-func TestBuildEnvUsage(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvUsage(c *check.C) {
name := "testbuildenvusage"
- defer deleteImages(name)
dockerfile := `FROM busybox
ENV HOME /root
ENV PATH $HOME/bin:$PATH
@@ -4013,20 +3820,18 @@ RUN [ "$ghi" = "def" ]
"hello/docker/world": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
_, err = buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - environment variables usage")
}
-func TestBuildEnvUsage2(t *testing.T) {
+func (s *DockerSuite) TestBuildEnvUsage2(c *check.C) {
name := "testbuildenvusage2"
- defer deleteImages(name)
dockerfile := `FROM busybox
ENV abc=def
RUN [ "$abc" = "def" ]
@@ -4071,6 +3876,27 @@ RUN [ "$abc" = "'foo'" ]
ENV abc \"foo\"
RUN [ "$abc" = '"foo"' ]
+ENV abc=ABC
+RUN [ "$abc" = "ABC" ]
+ENV def=${abc:-DEF}
+RUN [ "$def" = "ABC" ]
+ENV def=${ccc:-DEF}
+RUN [ "$def" = "DEF" ]
+ENV def=${ccc:-${def}xx}
+RUN [ "$def" = "DEFxx" ]
+ENV def=${def:+ALT}
+RUN [ "$def" = "ALT" ]
+ENV def=${def:+${abc}:}
+RUN [ "$def" = "ABC:" ]
+ENV def=${ccc:-\$abc:}
+RUN [ "$def" = '$abc:' ]
+ENV def=${ccc:-\${abc}:}
+RUN [ "$def" = '${abc:}' ]
+ENV mypath=${mypath:+$mypath:}/home
+RUN [ "$mypath" = '/home' ]
+ENV mypath=${mypath:+$mypath:}/away
+RUN [ "$mypath" = '/home:/away' ]
+
ENV e1=bar
ENV e2=$e1
ENV e3=$e11
@@ -4096,20 +3922,18 @@ RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ]
"hello/docker/world": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
_, err = buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - environment variables usage2")
}
-func TestBuildAddScript(t *testing.T) {
+func (s *DockerSuite) TestBuildAddScript(c *check.C) {
name := "testbuildaddscript"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD test /test
@@ -4120,20 +3944,18 @@ RUN [ "$(cat /testfile)" = 'test!' ]`
"test": "#!/bin/sh\necho 'test!' > /testfile",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
_, err = buildImageFromContext(name, ctx, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - add and run script")
}
-func TestBuildAddTar(t *testing.T) {
+func (s *DockerSuite) TestBuildAddTar(c *check.C) {
name := "testbuildaddtar"
- defer deleteImages(name)
ctx := func() *FakeContext {
dockerfile := `
@@ -4154,7 +3976,7 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi`
tmpDir, err := ioutil.TempDir("", "fake-context")
testTar, err := os.Create(filepath.Join(tmpDir, "test.tar"))
if err != nil {
- t.Fatalf("failed to create test.tar archive: %v", err)
+ c.Fatalf("failed to create test.tar archive: %v", err)
}
defer testTar.Close()
@@ -4164,32 +3986,30 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi`
Name: "test/foo",
Size: 2,
}); err != nil {
- t.Fatalf("failed to write tar file header: %v", err)
+ c.Fatalf("failed to write tar file header: %v", err)
}
if _, err := tw.Write([]byte("Hi")); err != nil {
- t.Fatalf("failed to write tar file content: %v", err)
+ c.Fatalf("failed to write tar file content: %v", err)
}
if err := tw.Close(); err != nil {
- t.Fatalf("failed to close tar archive: %v", err)
+ c.Fatalf("failed to close tar archive: %v", err)
}
if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil {
- t.Fatalf("failed to open destination dockerfile: %v", err)
+ c.Fatalf("failed to open destination dockerfile: %v", err)
}
return fakeContextFromDir(tmpDir)
}()
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("build failed to complete for TestBuildAddTar: %v", err)
+ c.Fatalf("build failed to complete for TestBuildAddTar: %v", err)
}
- logDone("build - ADD tar")
}
-func TestBuildAddTarXz(t *testing.T) {
+func (s *DockerSuite) TestBuildAddTarXz(c *check.C) {
name := "testbuildaddtarxz"
- defer deleteImages(name)
ctx := func() *FakeContext {
dockerfile := `
@@ -4199,7 +4019,7 @@ func TestBuildAddTarXz(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "fake-context")
testTar, err := os.Create(filepath.Join(tmpDir, "test.tar"))
if err != nil {
- t.Fatalf("failed to create test.tar archive: %v", err)
+ c.Fatalf("failed to create test.tar archive: %v", err)
}
defer testTar.Close()
@@ -4209,23 +4029,23 @@ func TestBuildAddTarXz(t *testing.T) {
Name: "test/foo",
Size: 2,
}); err != nil {
- t.Fatalf("failed to write tar file header: %v", err)
+ c.Fatalf("failed to write tar file header: %v", err)
}
if _, err := tw.Write([]byte("Hi")); err != nil {
- t.Fatalf("failed to write tar file content: %v", err)
+ c.Fatalf("failed to write tar file content: %v", err)
}
if err := tw.Close(); err != nil {
- t.Fatalf("failed to close tar archive: %v", err)
+ c.Fatalf("failed to close tar archive: %v", err)
}
xzCompressCmd := exec.Command("xz", "-k", "test.tar")
xzCompressCmd.Dir = tmpDir
out, _, err := runCommandWithOutput(xzCompressCmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil {
- t.Fatalf("failed to open destination dockerfile: %v", err)
+ c.Fatalf("failed to open destination dockerfile: %v", err)
}
return fakeContextFromDir(tmpDir)
}()
@@ -4233,15 +4053,13 @@ func TestBuildAddTarXz(t *testing.T) {
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err)
+ c.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err)
}
- logDone("build - ADD tar.xz")
}
-func TestBuildAddTarXzGz(t *testing.T) {
+func (s *DockerSuite) TestBuildAddTarXzGz(c *check.C) {
name := "testbuildaddtarxzgz"
- defer deleteImages(name)
ctx := func() *FakeContext {
dockerfile := `
@@ -4251,7 +4069,7 @@ func TestBuildAddTarXzGz(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "fake-context")
testTar, err := os.Create(filepath.Join(tmpDir, "test.tar"))
if err != nil {
- t.Fatalf("failed to create test.tar archive: %v", err)
+ c.Fatalf("failed to create test.tar archive: %v", err)
}
defer testTar.Close()
@@ -4261,31 +4079,31 @@ func TestBuildAddTarXzGz(t *testing.T) {
Name: "test/foo",
Size: 2,
}); err != nil {
- t.Fatalf("failed to write tar file header: %v", err)
+ c.Fatalf("failed to write tar file header: %v", err)
}
if _, err := tw.Write([]byte("Hi")); err != nil {
- t.Fatalf("failed to write tar file content: %v", err)
+ c.Fatalf("failed to write tar file content: %v", err)
}
if err := tw.Close(); err != nil {
- t.Fatalf("failed to close tar archive: %v", err)
+ c.Fatalf("failed to close tar archive: %v", err)
}
xzCompressCmd := exec.Command("xz", "-k", "test.tar")
xzCompressCmd.Dir = tmpDir
out, _, err := runCommandWithOutput(xzCompressCmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
gzipCompressCmd := exec.Command("gzip", "test.tar.xz")
gzipCompressCmd.Dir = tmpDir
out, _, err = runCommandWithOutput(gzipCompressCmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil {
- t.Fatalf("failed to open destination dockerfile: %v", err)
+ c.Fatalf("failed to open destination dockerfile: %v", err)
}
return fakeContextFromDir(tmpDir)
}()
@@ -4293,15 +4111,13 @@ func TestBuildAddTarXzGz(t *testing.T) {
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err)
+ c.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err)
}
- logDone("build - ADD tar.xz.gz")
}
-func TestBuildFromGIT(t *testing.T) {
+func (s *DockerSuite) TestBuildFromGIT(c *check.C) {
name := "testbuildfromgit"
- defer deleteImages(name)
git, err := fakeGIT("repo", map[string]string{
"Dockerfile": `FROM busybox
ADD first /first
@@ -4310,317 +4126,315 @@ func TestBuildFromGIT(t *testing.T) {
"first": "test git data",
}, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer git.Close()
_, err = buildImageFromPath(name, git.RepoURL, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Author")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != "docker" {
- t.Fatalf("Maintainer should be docker, got %s", res)
+ c.Fatalf("Maintainer should be docker, got %s", res)
}
- logDone("build - build from GIT")
}
-func TestBuildCleanupCmdOnEntrypoint(t *testing.T) {
- name := "testbuildcmdcleanuponentrypoint"
+func (s *DockerSuite) TestBuildFromGITWithContext(c *check.C) {
+ name := "testbuildfromgit"
defer deleteImages(name)
+ git, err := fakeGIT("repo", map[string]string{
+ "docker/Dockerfile": `FROM busybox
+ ADD first /first
+ RUN [ -f /first ]
+ MAINTAINER docker`,
+ "docker/first": "test git data",
+ }, true)
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer git.Close()
+
+ u := fmt.Sprintf("%s#master:docker", git.RepoURL)
+ _, err = buildImageFromPath(name, u, true)
+ if err != nil {
+ c.Fatal(err)
+ }
+ res, err := inspectField(name, "Author")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if res != "docker" {
+ c.Fatalf("Maintainer should be docker, got %s", res)
+ }
+}
+
+func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) {
+ name := "testbuildcmdcleanuponentrypoint"
if _, err := buildImage(name,
`FROM scratch
CMD ["test"]
ENTRYPOINT ["echo"]`,
true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImage(name,
fmt.Sprintf(`FROM %s
ENTRYPOINT ["cat"]`, name),
true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectField(name, "Config.Cmd")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if expected := ""; res != expected {
- t.Fatalf("Cmd %s, expected %s", res, expected)
+ if res != "" {
+ c.Fatalf("Cmd %s, expected nil", res)
}
+
res, err = inspectField(name, "Config.Entrypoint")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if expected := "[cat]"; res != expected {
- t.Fatalf("Entrypoint %s, expected %s", res, expected)
+ if expected := "{[cat]}"; res != expected {
+ c.Fatalf("Entrypoint %s, expected %s", res, expected)
}
- logDone("build - cleanup cmd on ENTRYPOINT")
}
-func TestBuildClearCmd(t *testing.T) {
+func (s *DockerSuite) TestBuildClearCmd(c *check.C) {
name := "testbuildclearcmd"
- defer deleteImages(name)
_, err := buildImage(name,
`From scratch
ENTRYPOINT ["/bin/bash"]
CMD []`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Cmd")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != "[]" {
- t.Fatalf("Cmd %s, expected %s", res, "[]")
+ c.Fatalf("Cmd %s, expected %s", res, "[]")
}
- logDone("build - clearcmd")
}
-func TestBuildEmptyCmd(t *testing.T) {
+func (s *DockerSuite) TestBuildEmptyCmd(c *check.C) {
name := "testbuildemptycmd"
- defer deleteImages(name)
if _, err := buildImage(name, "FROM scratch\nMAINTAINER quux\n", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Cmd")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != "null" {
- t.Fatalf("Cmd %s, expected %s", res, "null")
+ c.Fatalf("Cmd %s, expected %s", res, "null")
}
- logDone("build - empty cmd")
}
-func TestBuildOnBuildOutput(t *testing.T) {
+func (s *DockerSuite) TestBuildOnBuildOutput(c *check.C) {
name := "testbuildonbuildparent"
- defer deleteImages(name)
if _, err := buildImage(name, "FROM busybox\nONBUILD RUN echo foo\n", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- childname := "testbuildonbuildchild"
- defer deleteImages(childname)
-
_, out, err := buildImageWithOut(name, "FROM "+name+"\nMAINTAINER quux\n", true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(out, "Trigger 0, RUN echo foo") {
- t.Fatal("failed to find the ONBUILD output", out)
+ c.Fatal("failed to find the ONBUILD output", out)
}
- logDone("build - onbuild output")
}
-func TestBuildInvalidTag(t *testing.T) {
- name := "abcd:" + makeRandomString(200)
- defer deleteImages(name)
+func (s *DockerSuite) TestBuildInvalidTag(c *check.C) {
+ name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200)
_, out, err := buildImageWithOut(name, "FROM scratch\nMAINTAINER quux\n", true)
// if the error doesnt check for illegal tag name, or the image is built
// then this should fail
if !strings.Contains(out, "Illegal tag name") || strings.Contains(out, "Sending build context to Docker daemon") {
- t.Fatalf("failed to stop before building. Error: %s, Output: %s", err, out)
+ c.Fatalf("failed to stop before building. Error: %s, Output: %s", err, out)
}
- logDone("build - invalid tag")
}
-func TestBuildCmdShDashC(t *testing.T) {
+func (s *DockerSuite) TestBuildCmdShDashC(c *check.C) {
name := "testbuildcmdshc"
- defer deleteImages(name)
if _, err := buildImage(name, "FROM busybox\nCMD echo cmd\n", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Cmd")
if err != nil {
- t.Fatal(err, res)
+ c.Fatal(err, res)
}
expected := `["/bin/sh","-c","echo cmd"]`
if res != expected {
- t.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res)
+ c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res)
}
- logDone("build - cmd should have sh -c for non-json")
}
-func TestBuildCmdSpaces(t *testing.T) {
+func (s *DockerSuite) TestBuildCmdSpaces(c *check.C) {
// Test to make sure that when we strcat arrays we take into account
// the arg separator to make sure ["echo","hi"] and ["echo hi"] don't
// look the same
name := "testbuildcmdspaces"
- defer deleteImages(name)
var id1 string
var id2 string
var err error
if id1, err = buildImage(name, "FROM busybox\nCMD [\"echo hi\"]\n", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id2, err = buildImage(name, "FROM busybox\nCMD [\"echo\", \"hi\"]\n", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("Should not have resulted in the same CMD")
+ c.Fatal("Should not have resulted in the same CMD")
}
// Now do the same with ENTRYPOINT
if id1, err = buildImage(name, "FROM busybox\nENTRYPOINT [\"echo hi\"]\n", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id2, err = buildImage(name, "FROM busybox\nENTRYPOINT [\"echo\", \"hi\"]\n", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if id1 == id2 {
- t.Fatal("Should not have resulted in the same ENTRYPOINT")
+ c.Fatal("Should not have resulted in the same ENTRYPOINT")
}
- logDone("build - cmd with spaces")
}
-func TestBuildCmdJSONNoShDashC(t *testing.T) {
+func (s *DockerSuite) TestBuildCmdJSONNoShDashC(c *check.C) {
name := "testbuildcmdjson"
- defer deleteImages(name)
if _, err := buildImage(name, "FROM busybox\nCMD [\"echo\", \"cmd\"]", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Cmd")
if err != nil {
- t.Fatal(err, res)
+ c.Fatal(err, res)
}
expected := `["echo","cmd"]`
if res != expected {
- t.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res)
+ c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res)
}
- logDone("build - cmd should not have /bin/sh -c for json")
}
-func TestBuildErrorInvalidInstruction(t *testing.T) {
+func (s *DockerSuite) TestBuildErrorInvalidInstruction(c *check.C) {
name := "testbuildignoreinvalidinstruction"
- defer deleteImages(name)
out, _, err := buildImageWithOut(name, "FROM busybox\nfoo bar", true)
if err == nil {
- t.Fatalf("Should have failed: %s", out)
+ c.Fatalf("Should have failed: %s", out)
}
- logDone("build - error invalid Dockerfile instruction")
}
-func TestBuildEntrypointInheritance(t *testing.T) {
- defer deleteImages("parent", "child")
- defer deleteAllContainers()
+func (s *DockerSuite) TestBuildEntrypointInheritance(c *check.C) {
if _, err := buildImage("parent", `
FROM busybox
ENTRYPOINT exit 130
`, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
status, _ := runCommand(exec.Command(dockerBinary, "run", "parent"))
if status != 130 {
- t.Fatalf("expected exit code 130 but received %d", status)
+ c.Fatalf("expected exit code 130 but received %d", status)
}
if _, err := buildImage("child", `
FROM parent
ENTRYPOINT exit 5
`, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
status, _ = runCommand(exec.Command(dockerBinary, "run", "child"))
if status != 5 {
- t.Fatalf("expected exit code 5 but received %d", status)
+ c.Fatalf("expected exit code 5 but received %d", status)
}
- logDone("build - clear entrypoint")
}
-func TestBuildEntrypointInheritanceInspect(t *testing.T) {
+func (s *DockerSuite) TestBuildEntrypointInheritanceInspect(c *check.C) {
var (
name = "testbuildepinherit"
name2 = "testbuildepinherit2"
expected = `["/bin/sh","-c","echo quux"]`
)
- defer deleteImages(name, name2)
- defer deleteAllContainers()
-
if _, err := buildImage(name, "FROM busybox\nENTRYPOINT /foo/bar", true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImage(name2, fmt.Sprintf("FROM %s\nENTRYPOINT echo quux", name), true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name2, "Config.Entrypoint")
if err != nil {
- t.Fatal(err, res)
+ c.Fatal(err, res)
}
if res != expected {
- t.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res)
+ c.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
expected = "quux"
if strings.TrimSpace(out) != expected {
- t.Fatalf("Expected output is %s, got %s", expected, out)
+ c.Fatalf("Expected output is %s, got %s", expected, out)
}
- logDone("build - entrypoint override inheritance properly")
}
-func TestBuildRunShEntrypoint(t *testing.T) {
+func (s *DockerSuite) TestBuildRunShEntrypoint(c *check.C) {
name := "testbuildentrypoint"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
ENTRYPOINT /bin/echo`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- logDone("build - entrypoint with /bin/echo running successfully")
}
-func TestBuildExoticShellInterpolation(t *testing.T) {
+func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) {
name := "testbuildexoticshellinterpolation"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM busybox
@@ -4642,21 +4456,18 @@ func TestBuildExoticShellInterpolation(t *testing.T) {
RUN [ "${SOME_UNSET_VAR:-${SOME_VAR:-d.e.f}}" = 'a.b.c' ]
`, false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - exotic shell interpolation")
}
-func TestBuildVerifySingleQuoteFails(t *testing.T) {
+func (s *DockerSuite) TestBuildVerifySingleQuoteFails(c *check.C) {
// This testcase is supposed to generate an error because the
// JSON array we're passing in on the CMD uses single quotes instead
// of double quotes (per the JSON spec). This means we interpret it
// as a "string" insead of "JSON array" and pass it on to "sh -c" and
// it should barf on it.
name := "testbuildsinglequotefails"
- defer deleteImages(name)
- defer deleteAllContainers()
_, err := buildImage(name,
`FROM busybox
@@ -4665,15 +4476,13 @@ func TestBuildVerifySingleQuoteFails(t *testing.T) {
_, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name))
if err == nil {
- t.Fatal("The image was not supposed to be able to run")
+ c.Fatal("The image was not supposed to be able to run")
}
- logDone("build - verify single quotes break the build")
}
-func TestBuildVerboseOut(t *testing.T) {
+func (s *DockerSuite) TestBuildVerboseOut(c *check.C) {
name := "testbuildverboseout"
- defer deleteImages(name)
_, out, err := buildImageWithOut(name,
`FROM busybox
@@ -4681,87 +4490,81 @@ RUN echo 123`,
false)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(out, "\n123\n") {
- t.Fatalf("Output should contain %q: %q", "123", out)
+ c.Fatalf("Output should contain %q: %q", "123", out)
}
- logDone("build - verbose output from commands")
}
-func TestBuildWithTabs(t *testing.T) {
+func (s *DockerSuite) TestBuildWithTabs(c *check.C) {
name := "testbuildwithtabs"
- defer deleteImages(name)
_, err := buildImage(name,
"FROM busybox\nRUN echo\tone\t\ttwo", true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "ContainerConfig.Cmd")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected1 := `["/bin/sh","-c","echo\tone\t\ttwo"]`
expected2 := `["/bin/sh","-c","echo\u0009one\u0009\u0009two"]` // syntactically equivalent, and what Go 1.3 generates
if res != expected1 && res != expected2 {
- t.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2)
+ c.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2)
}
- logDone("build - with tabs")
}
-func TestBuildLabels(t *testing.T) {
+func (s *DockerSuite) TestBuildLabels(c *check.C) {
name := "testbuildlabel"
expected := `{"License":"GPL","Vendor":"Acme"}`
- defer deleteImages(name)
_, err := buildImage(name,
`FROM busybox
LABEL Vendor=Acme
LABEL License GPL`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
res, err := inspectFieldJSON(name, "Config.Labels")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res != expected {
- t.Fatalf("Labels %s, expected %s", res, expected)
+ c.Fatalf("Labels %s, expected %s", res, expected)
}
- logDone("build - label")
}
-func TestBuildLabelsCache(t *testing.T) {
+func (s *DockerSuite) TestBuildLabelsCache(c *check.C) {
name := "testbuildlabelcache"
- defer deleteImages(name)
id1, err := buildImage(name,
`FROM busybox
LABEL Vendor=Acme`, false)
if err != nil {
- t.Fatalf("Build 1 should have worked: %v", err)
+ c.Fatalf("Build 1 should have worked: %v", err)
}
id2, err := buildImage(name,
`FROM busybox
LABEL Vendor=Acme`, true)
if err != nil || id1 != id2 {
- t.Fatalf("Build 2 should have worked & used cache(%s,%s): %v", id1, id2, err)
+ c.Fatalf("Build 2 should have worked & used cache(%s,%s): %v", id1, id2, err)
}
id2, err = buildImage(name,
`FROM busybox
LABEL Vendor=Acme1`, true)
if err != nil || id1 == id2 {
- t.Fatalf("Build 3 should have worked & NOT used cache(%s,%s): %v", id1, id2, err)
+ c.Fatalf("Build 3 should have worked & NOT used cache(%s,%s): %v", id1, id2, err)
}
id2, err = buildImage(name,
`FROM busybox
LABEL Vendor Acme`, true) // Note: " " and "=" should be same
if err != nil || id1 != id2 {
- t.Fatalf("Build 4 should have worked & used cache(%s,%s): %v", id1, id2, err)
+ c.Fatalf("Build 4 should have worked & used cache(%s,%s): %v", id1, id2, err)
}
// Now make sure the cache isn't used by mistake
@@ -4769,28 +4572,26 @@ func TestBuildLabelsCache(t *testing.T) {
`FROM busybox
LABEL f1=b1 f2=b2`, false)
if err != nil {
- t.Fatalf("Build 5 should have worked: %q", err)
+ c.Fatalf("Build 5 should have worked: %q", err)
}
id2, err = buildImage(name,
`FROM busybox
LABEL f1="b1 f2=b2"`, true)
if err != nil || id1 == id2 {
- t.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s): %q", id1, id2, err)
+ c.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s): %q", id1, id2, err)
}
- logDone("build - label cache")
}
-func TestBuildStderr(t *testing.T) {
+func (s *DockerSuite) TestBuildStderr(c *check.C) {
// This test just makes sure that no non-error output goes
// to stderr
name := "testbuildstderr"
- defer deleteImages(name)
_, _, stderr, err := buildImageWithStdoutStderr(name,
"FROM busybox\nRUN echo one", true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if runtime.GOOS == "windows" {
@@ -4798,22 +4599,20 @@ func TestBuildStderr(t *testing.T) {
lines := strings.Split(stderr, "\n")
for _, v := range lines {
if v != "" && !strings.Contains(v, "SECURITY WARNING:") {
- t.Fatalf("Stderr contains unexpected output line: %q", v)
+ c.Fatalf("Stderr contains unexpected output line: %q", v)
}
}
} else {
if stderr != "" {
- t.Fatalf("Stderr should have been empty, instead its: %q", stderr)
+ c.Fatalf("Stderr should have been empty, instead its: %q", stderr)
}
}
- logDone("build - testing stderr")
}
-func TestBuildChownSingleFile(t *testing.T) {
- testRequires(t, UnixCli) // test uses chown: not available on windows
+func (s *DockerSuite) TestBuildChownSingleFile(c *check.C) {
+ testRequires(c, UnixCli) // test uses chown: not available on windows
name := "testbuildchownsinglefile"
- defer deleteImages(name)
ctx, err := fakeContext(`
FROM busybox
@@ -4824,46 +4623,45 @@ RUN [ $(ls -l /test | awk '{print $3":"$4}') = 'root:root' ]
"test": "test",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if err := os.Chown(filepath.Join(ctx.Dir, "test"), 4242, 4242); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - change permission on single file")
}
-func TestBuildSymlinkBreakout(t *testing.T) {
+func (s *DockerSuite) TestBuildSymlinkBreakout(c *check.C) {
name := "testbuildsymlinkbreakout"
tmpdir, err := ioutil.TempDir("", name)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpdir)
ctx := filepath.Join(tmpdir, "context")
if err := os.MkdirAll(ctx, 0755); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte(`
from busybox
add symlink.tar /
add inject /symlink/
`), 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
inject := filepath.Join(ctx, "inject")
if err := ioutil.WriteFile(inject, nil, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
f, err := os.Create(filepath.Join(ctx, "symlink.tar"))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
w := tar.NewWriter(f)
w.WriteHeader(&tar.Header{
@@ -4883,19 +4681,17 @@ func TestBuildSymlinkBreakout(t *testing.T) {
w.Close()
f.Close()
if _, err := buildImageFromContext(name, fakeContextFromDir(ctx), false); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := os.Lstat(filepath.Join(tmpdir, "inject")); err == nil {
- t.Fatal("symlink breakout - inject")
+ c.Fatal("symlink breakout - inject")
} else if !os.IsNotExist(err) {
- t.Fatalf("unexpected error: %v", err)
+ c.Fatalf("unexpected error: %v", err)
}
- logDone("build - symlink breakout")
}
-func TestBuildXZHost(t *testing.T) {
+func (s *DockerSuite) TestBuildXZHost(c *check.C) {
name := "testbuildxzhost"
- defer deleteImages(name)
ctx, err := fakeContext(`
FROM busybox
@@ -4911,23 +4707,21 @@ RUN [ ! -e /injected ]`,
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("build - xz host is being used")
}
-func TestBuildVolumesRetainContents(t *testing.T) {
+func (s *DockerSuite) TestBuildVolumesRetainContents(c *check.C) {
var (
name = "testbuildvolumescontent"
expected = "some text"
)
- defer deleteImages(name)
ctx, err := fakeContext(`
FROM busybox
COPY content /foo/file
@@ -4937,27 +4731,25 @@ CMD cat /foo/file`,
"content": expected,
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err := buildImageFromContext(name, ctx, false); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if out != expected {
- t.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out)
+ c.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out)
}
- logDone("build - volumes retain contents in build")
}
-func TestBuildRenamedDockerfile(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestBuildRenamedDockerfile(c *check.C) {
ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
@@ -4970,100 +4762,98 @@ func TestBuildRenamedDockerfile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".")
+ out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".")
if err != nil {
- t.Fatalf("Failed to build: %s\n%s", out, err)
+ c.Fatalf("Failed to build: %s\n%s", out, err)
}
if !strings.Contains(out, "from Dockerfile") {
- t.Fatalf("test1 should have used Dockerfile, output:%s", out)
+ c.Fatalf("test1 should have used Dockerfile, output:%s", out)
}
- out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-f", filepath.Join("files", "Dockerfile"), "-t", "test2", ".")
+ out, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-f", filepath.Join("files", "Dockerfile"), "-t", "test2", ".")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(out, "from files/Dockerfile") {
- t.Fatalf("test2 should have used files/Dockerfile, output:%s", out)
+ c.Fatalf("test2 should have used files/Dockerfile, output:%s", out)
}
- out, _, err = dockerCmdInDir(t, ctx.Dir, "build", fmt.Sprintf("--file=%s", filepath.Join("files", "dFile")), "-t", "test3", ".")
+ out, _, err = dockerCmdInDir(c, ctx.Dir, "build", fmt.Sprintf("--file=%s", filepath.Join("files", "dFile")), "-t", "test3", ".")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(out, "from files/dFile") {
- t.Fatalf("test3 should have used files/dFile, output:%s", out)
+ c.Fatalf("test3 should have used files/dFile, output:%s", out)
}
- out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "--file=dFile", "-t", "test4", ".")
+ out, _, err = dockerCmdInDir(c, ctx.Dir, "build", "--file=dFile", "-t", "test4", ".")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !strings.Contains(out, "from dFile") {
- t.Fatalf("test4 should have used dFile, output:%s", out)
+ c.Fatalf("test4 should have used dFile, output:%s", out)
}
dirWithNoDockerfile, _ := ioutil.TempDir(os.TempDir(), "test5")
nonDockerfileFile := filepath.Join(dirWithNoDockerfile, "notDockerfile")
if _, err = os.Create(nonDockerfileFile); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- out, _, err = dockerCmdInDir(t, ctx.Dir, "build", fmt.Sprintf("--file=%s", nonDockerfileFile), "-t", "test5", ".")
+ out, _, err = dockerCmdInDir(c, ctx.Dir, "build", fmt.Sprintf("--file=%s", nonDockerfileFile), "-t", "test5", ".")
if err == nil {
- t.Fatalf("test5 was supposed to fail to find passwd")
+ c.Fatalf("test5 was supposed to fail to find passwd")
}
- if expected := fmt.Sprintf("The Dockerfile (%s) must be within the build context (.)", strings.Replace(nonDockerfileFile, `\`, `\\`, -1)); !strings.Contains(out, expected) {
- t.Fatalf("wrong error messsage:%v\nexpected to contain=%v", out, expected)
+ if expected := fmt.Sprintf("The Dockerfile (%s) must be within the build context (.)", nonDockerfileFile); !strings.Contains(out, expected) {
+ c.Fatalf("wrong error messsage:%v\nexpected to contain=%v", out, expected)
}
- out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test6", "..")
+ out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test6", "..")
if err != nil {
- t.Fatalf("test6 failed: %s", err)
+ c.Fatalf("test6 failed: %s", err)
}
if !strings.Contains(out, "from Dockerfile") {
- t.Fatalf("test6 should have used root Dockerfile, output:%s", out)
+ c.Fatalf("test6 should have used root Dockerfile, output:%s", out)
}
- out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join(ctx.Dir, "files", "Dockerfile"), "-t", "test7", "..")
+ out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join(ctx.Dir, "files", "Dockerfile"), "-t", "test7", "..")
if err != nil {
- t.Fatalf("test7 failed: %s", err)
+ c.Fatalf("test7 failed: %s", err)
}
if !strings.Contains(out, "from files/Dockerfile") {
- t.Fatalf("test7 should have used files Dockerfile, output:%s", out)
+ c.Fatalf("test7 should have used files Dockerfile, output:%s", out)
}
- out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test8", ".")
+ out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test8", ".")
if err == nil || !strings.Contains(out, "must be within the build context") {
- t.Fatalf("test8 should have failed with Dockerfile out of context: %s", err)
+ c.Fatalf("test8 should have failed with Dockerfile out of context: %s", err)
}
tmpDir := os.TempDir()
- out, _, err = dockerCmdInDir(t, tmpDir, "build", "-t", "test9", ctx.Dir)
+ out, _, err = dockerCmdInDir(c, tmpDir, "build", "-t", "test9", ctx.Dir)
if err != nil {
- t.Fatalf("test9 - failed: %s", err)
+ c.Fatalf("test9 - failed: %s", err)
}
if !strings.Contains(out, "from Dockerfile") {
- t.Fatalf("test9 should have used root Dockerfile, output:%s", out)
+ c.Fatalf("test9 should have used root Dockerfile, output:%s", out)
}
- out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", "dFile2", "-t", "test10", ".")
+ out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", "dFile2", "-t", "test10", ".")
if err != nil {
- t.Fatalf("test10 should have worked: %s", err)
+ c.Fatalf("test10 should have worked: %s", err)
}
if !strings.Contains(out, "from files/dFile2") {
- t.Fatalf("test10 should have used files/dFile2, output:%s", out)
+ c.Fatalf("test10 should have used files/dFile2, output:%s", out)
}
- logDone("build - rename dockerfile")
}
-func TestBuildFromMixedcaseDockerfile(t *testing.T) {
- testRequires(t, UnixCli) // Dockerfile overwrites dockerfile on windows
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) {
+ testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows
ctx, err := fakeContext(`FROM busybox
RUN echo from dockerfile`,
@@ -5072,24 +4862,22 @@ func TestBuildFromMixedcaseDockerfile(t *testing.T) {
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".")
+ out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".")
if err != nil {
- t.Fatalf("Failed to build: %s\n%s", out, err)
+ c.Fatalf("Failed to build: %s\n%s", out, err)
}
if !strings.Contains(out, "from dockerfile") {
- t.Fatalf("Missing proper output: %s", out)
+ c.Fatalf("Missing proper output: %s", out)
}
- logDone("build - mixedcase Dockerfile")
}
-func TestBuildWithTwoDockerfiles(t *testing.T) {
- testRequires(t, UnixCli) // Dockerfile overwrites dockerfile on windows
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildWithTwoDockerfiles(c *check.C) {
+ testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows
ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
@@ -5098,30 +4886,28 @@ RUN echo from Dockerfile`,
})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".")
+ out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".")
if err != nil {
- t.Fatalf("Failed to build: %s\n%s", out, err)
+ c.Fatalf("Failed to build: %s\n%s", out, err)
}
if !strings.Contains(out, "from Dockerfile") {
- t.Fatalf("Missing proper output: %s", out)
+ c.Fatalf("Missing proper output: %s", out)
}
- logDone("build - two Dockerfiles")
}
-func TestBuildFromURLWithF(t *testing.T) {
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildFromURLWithF(c *check.C) {
server, err := fakeStorage(map[string]string{"baz": `FROM busybox
RUN echo from baz
COPY * /tmp/
RUN find /tmp/`})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -5130,34 +4916,32 @@ RUN echo from Dockerfile`,
map[string]string{})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Make sure that -f is ignored and that we don't use the Dockerfile
// that's in the current dir
- out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz")
+ out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz")
if err != nil {
- t.Fatalf("Failed to build: %s\n%s", out, err)
+ c.Fatalf("Failed to build: %s\n%s", out, err)
}
if !strings.Contains(out, "from baz") ||
strings.Contains(out, "/tmp/baz") ||
!strings.Contains(out, "/tmp/Dockerfile") {
- t.Fatalf("Missing proper output: %s", out)
+ c.Fatalf("Missing proper output: %s", out)
}
- logDone("build - from URL with -f")
}
-func TestBuildFromStdinWithF(t *testing.T) {
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildFromStdinWithF(c *check.C) {
ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
map[string]string{})
defer ctx.Close()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Make sure that -f is ignored and that we don't use the Dockerfile
@@ -5170,19 +4954,18 @@ COPY * /tmp/
RUN find /tmp/`)
out, status, err := runCommandWithOutput(dockerCommand)
if err != nil || status != 0 {
- t.Fatalf("Error building: %s", err)
+ c.Fatalf("Error building: %s", err)
}
if !strings.Contains(out, "from baz") ||
strings.Contains(out, "/tmp/baz") ||
!strings.Contains(out, "/tmp/Dockerfile") {
- t.Fatalf("Missing proper output: %s", out)
+ c.Fatalf("Missing proper output: %s", out)
}
- logDone("build - from stdin with -f")
}
-func TestBuildFromOfficialNames(t *testing.T) {
+func (s *DockerSuite) TestBuildFromOfficialNames(c *check.C) {
name := "testbuildfromofficial"
fromNames := []string{
"busybox",
@@ -5196,45 +4979,44 @@ func TestBuildFromOfficialNames(t *testing.T) {
imgName := fmt.Sprintf("%s%d", name, idx)
_, err := buildImage(imgName, "FROM "+fromName, true)
if err != nil {
- t.Errorf("Build failed using FROM %s: %s", fromName, err)
+ c.Errorf("Build failed using FROM %s: %s", fromName, err)
}
deleteImages(imgName)
}
- logDone("build - from official names")
}
-func TestBuildDockerfileOutsideContext(t *testing.T) {
- testRequires(t, UnixCli) // uses os.Symlink: not implemented in windows at the time of writing (go-1.4.2)
+func (s *DockerSuite) TestBuildDockerfileOutsideContext(c *check.C) {
+ testRequires(c, UnixCli) // uses os.Symlink: not implemented in windows at the time of writing (go-1.4.2)
name := "testbuilddockerfileoutsidecontext"
tmpdir, err := ioutil.TempDir("", name)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpdir)
ctx := filepath.Join(tmpdir, "context")
if err := os.MkdirAll(ctx, 0755); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte("FROM scratch\nENV X Y"), 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
wd, err := os.Getwd()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.Chdir(wd)
if err := os.Chdir(ctx); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(tmpdir, "outsideDockerfile"), []byte("FROM scratch\nENV x y"), 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := os.Symlink(filepath.Join("..", "outsideDockerfile"), filepath.Join(ctx, "dockerfile1")); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := os.Symlink(filepath.Join(tmpdir, "outsideDockerfile"), filepath.Join(ctx, "dockerfile2")); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
for _, dockerfilePath := range []string{
@@ -5244,10 +5026,10 @@ func TestBuildDockerfileOutsideContext(t *testing.T) {
} {
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", dockerfilePath, "."))
if err == nil {
- t.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out)
+ c.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out)
}
if !strings.Contains(out, "must be within the build context") && !strings.Contains(out, "Cannot locate Dockerfile") {
- t.Fatalf("Unexpected error with %s. Out: %s", dockerfilePath, out)
+ c.Fatalf("Unexpected error with %s. Out: %s", dockerfilePath, out)
}
deleteImages(name)
}
@@ -5258,14 +5040,11 @@ func TestBuildDockerfileOutsideContext(t *testing.T) {
// There is a Dockerfile in the context, but since there is no Dockerfile in the current directory, the following should fail
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", "Dockerfile", ctx))
if err == nil {
- t.Fatalf("Expected error. Out: %s", out)
+ c.Fatalf("Expected error. Out: %s", out)
}
- deleteImages(name)
-
- logDone("build - Dockerfile outside context")
}
-func TestBuildSpaces(t *testing.T) {
+func (s *DockerSuite) TestBuildSpaces(c *check.C) {
// Test to make sure that leading/trailing spaces on a command
// doesn't change the error msg we get
var (
@@ -5274,23 +5053,22 @@ func TestBuildSpaces(t *testing.T) {
)
name := "testspaces"
- defer deleteImages(name)
ctx, err := fakeContext("FROM busybox\nCOPY\n",
map[string]string{
"Dockerfile": "FROM busybox\nCOPY\n",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err1 = buildImageFromContext(name, ctx, false); err1 == nil {
- t.Fatal("Build 1 was supposed to fail, but didn't")
+ c.Fatal("Build 1 was supposed to fail, but didn't")
}
ctx.Add("Dockerfile", "FROM busybox\nCOPY ")
if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil {
- t.Fatal("Build 2 was supposed to fail, but didn't")
+ c.Fatal("Build 2 was supposed to fail, but didn't")
}
removeLogTimestamps := func(s string) string {
@@ -5303,12 +5081,12 @@ func TestBuildSpaces(t *testing.T) {
// Ignore whitespace since that's what were verifying doesn't change stuff
if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
- t.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", err1, err2)
+ c.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", err1, err2)
}
ctx.Add("Dockerfile", "FROM busybox\n COPY")
if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil {
- t.Fatal("Build 3 was supposed to fail, but didn't")
+ c.Fatal("Build 3 was supposed to fail, but didn't")
}
// Skip over the times
@@ -5317,12 +5095,12 @@ func TestBuildSpaces(t *testing.T) {
// Ignore whitespace since that's what were verifying doesn't change stuff
if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
- t.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", err1, err2)
+ c.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", err1, err2)
}
ctx.Add("Dockerfile", "FROM busybox\n COPY ")
if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil {
- t.Fatal("Build 4 was supposed to fail, but didn't")
+ c.Fatal("Build 4 was supposed to fail, but didn't")
}
// Skip over the times
@@ -5331,16 +5109,14 @@ func TestBuildSpaces(t *testing.T) {
// Ignore whitespace since that's what were verifying doesn't change stuff
if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) {
- t.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", err1, err2)
+ c.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", err1, err2)
}
- logDone("build - test spaces")
}
-func TestBuildSpacesWithQuotes(t *testing.T) {
+func (s *DockerSuite) TestBuildSpacesWithQuotes(c *check.C) {
// Test to make sure that spaces in quotes aren't lost
name := "testspacesquotes"
- defer deleteImages(name)
dockerfile := `FROM busybox
RUN echo " \
@@ -5348,19 +5124,18 @@ RUN echo " \
_, out, err := buildImageWithOut(name, dockerfile, false)
if err != nil {
- t.Fatal("Build failed:", err)
+ c.Fatal("Build failed:", err)
}
expecting := "\n foo \n"
if !strings.Contains(out, expecting) {
- t.Fatalf("Bad output: %q expecting to contian %q", out, expecting)
+ c.Fatalf("Bad output: %q expecting to contain %q", out, expecting)
}
- logDone("build - test spaces with quotes")
}
// #4393
-func TestBuildVolumeFileExistsinContainer(t *testing.T) {
+func (s *DockerSuite) TestBuildVolumeFileExistsinContainer(c *check.C) {
buildCmd := exec.Command(dockerBinary, "build", "-t", "docker-test-errcreatevolumewithfile", "-")
buildCmd.Stdin = strings.NewReader(`
FROM busybox
@@ -5370,13 +5145,12 @@ func TestBuildVolumeFileExistsinContainer(t *testing.T) {
out, _, err := runCommandWithOutput(buildCmd)
if err == nil || !strings.Contains(out, "file exists") {
- t.Fatalf("expected build to fail when file exists in container at requested volume path")
+ c.Fatalf("expected build to fail when file exists in container at requested volume path")
}
- logDone("build - errors when volume is specified where a file exists")
}
-func TestBuildMissingArgs(t *testing.T) {
+func (s *DockerSuite) TestBuildMissingArgs(c *check.C) {
// Test to make sure that all Dockerfile commands (except the ones listed
// in skipCmds) will generate an error if no args are provided.
// Note: INSERT is deprecated so we exclude it because of that.
@@ -5387,8 +5161,6 @@ func TestBuildMissingArgs(t *testing.T) {
"INSERT": {},
}
- defer deleteAllContainers()
-
for cmd := range command.Commands {
cmd = strings.ToUpper(cmd)
if _, ok := skipCmds[cmd]; ok {
@@ -5405,57 +5177,50 @@ func TestBuildMissingArgs(t *testing.T) {
ctx, err := fakeContext(dockerfile, map[string]string{})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
var out string
if out, err = buildImageFromContext("args", ctx, true); err == nil {
- t.Fatalf("%s was supposed to fail. Out:%s", cmd, out)
+ c.Fatalf("%s was supposed to fail. Out:%s", cmd, out)
}
if !strings.Contains(err.Error(), cmd+" requires") {
- t.Fatalf("%s returned the wrong type of error:%s", cmd, err)
+ c.Fatalf("%s returned the wrong type of error:%s", cmd, err)
}
}
- logDone("build - verify missing args")
}
-func TestBuildEmptyScratch(t *testing.T) {
- defer deleteImages("sc")
+func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) {
_, out, err := buildImageWithOut("sc", "FROM scratch", true)
if err == nil {
- t.Fatalf("Build was supposed to fail")
+ c.Fatalf("Build was supposed to fail")
}
if !strings.Contains(out, "No image was generated") {
- t.Fatalf("Wrong error message: %v", out)
+ c.Fatalf("Wrong error message: %v", out)
}
- logDone("build - empty scratch Dockerfile")
}
-func TestBuildDotDotFile(t *testing.T) {
- defer deleteImages("sc")
+func (s *DockerSuite) TestBuildDotDotFile(c *check.C) {
ctx, err := fakeContext("FROM busybox\n",
map[string]string{
"..gitme": "",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
if _, err = buildImageFromContext("sc", ctx, false); err != nil {
- t.Fatalf("Build was supposed to work: %s", err)
+ c.Fatalf("Build was supposed to work: %s", err)
}
- logDone("build - ..file")
}
-func TestBuildNotVerbose(t *testing.T) {
- defer deleteAllContainers()
- defer deleteImages("verbose")
+func (s *DockerSuite) TestBuildNotVerbose(c *check.C) {
ctx, err := fakeContext("FROM busybox\nENV abc=hi\nRUN echo $abc there", map[string]string{})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
@@ -5464,10 +5229,10 @@ func TestBuildNotVerbose(t *testing.T) {
buildCmd.Dir = ctx.Dir
out, _, err := runCommandWithOutput(buildCmd)
if err != nil {
- t.Fatalf("failed to build the image w/o -q: %s, %v", out, err)
+ c.Fatalf("failed to build the image w/o -q: %s, %v", out, err)
}
if !strings.Contains(out, "hi there") {
- t.Fatalf("missing output:%s\n", out)
+ c.Fatalf("missing output:%s\n", out)
}
// Now do it w/o verbose
@@ -5475,25 +5240,21 @@ func TestBuildNotVerbose(t *testing.T) {
buildCmd.Dir = ctx.Dir
out, _, err = runCommandWithOutput(buildCmd)
if err != nil {
- t.Fatalf("failed to build the image w/ -q: %s, %v", out, err)
+ c.Fatalf("failed to build the image w/ -q: %s, %v", out, err)
}
if strings.Contains(out, "hi there") {
- t.Fatalf("Bad output, should not contain 'hi there':%s", out)
+ c.Fatalf("Bad output, should not contain 'hi there':%s", out)
}
- logDone("build - not verbose")
}
-func TestBuildRUNoneJSON(t *testing.T) {
+func (s *DockerSuite) TestBuildRUNoneJSON(c *check.C) {
name := "testbuildrunonejson"
- defer deleteAllContainers()
- defer deleteImages(name)
-
ctx, err := fakeContext(`FROM hello-world:frozen
RUN [ "/hello" ]`, map[string]string{})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
@@ -5501,90 +5262,80 @@ RUN [ "/hello" ]`, map[string]string{})
buildCmd.Dir = ctx.Dir
out, _, err := runCommandWithOutput(buildCmd)
if err != nil {
- t.Fatalf("failed to build the image: %s, %v", out, err)
+ c.Fatalf("failed to build the image: %s, %v", out, err)
}
if !strings.Contains(out, "Hello from Docker") {
- t.Fatalf("bad output: %s", out)
+ c.Fatalf("bad output: %s", out)
}
- logDone("build - RUN with one JSON arg")
}
-func TestBuildResourceConstraintsAreUsed(t *testing.T) {
+func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) {
name := "testbuildresourceconstraints"
- defer deleteAllContainers()
- defer deleteImages(name)
ctx, err := fakeContext(`
FROM hello-world:frozen
RUN ["/hello"]
`, map[string]string{})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- cmd := exec.Command(dockerBinary, "build", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=1", "--cpu-shares=100", "-t", name, ".")
+ cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "--cpu-quota=8000", "-t", name, ".")
cmd.Dir = ctx.Dir
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
- }
- out, _, err = dockerCmd(t, "ps", "-lq")
- if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
+ out, _ = dockerCmd(c, "ps", "-lq")
- cID := stripTrailingCharacters(out)
+ cID := strings.TrimSpace(out)
type hostConfig struct {
- Memory float64 // Use float64 here since the json decoder sees it that way
- MemorySwap int
+ Memory int64
+ MemorySwap int64
CpusetCpus string
- CpuShares int
+ CpusetMems string
+ CpuShares int64
+ CpuQuota int64
}
cfg, err := inspectFieldJSON(cID, "HostConfig")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var c1 hostConfig
if err := json.Unmarshal([]byte(cfg), &c1); err != nil {
- t.Fatal(err, cfg)
+ c.Fatal(err, cfg)
}
- mem := int64(c1.Memory)
- if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "1" || c1.CpuShares != 100 {
- t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d",
- mem, c1.MemorySwap, c1.CpusetCpus, c1.CpuShares)
+ if c1.Memory != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 || c1.CpuQuota != 8000 {
+ c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d, CpuQuota: %d",
+ c1.Memory, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares, c1.CpuQuota)
}
// Make sure constraints aren't saved to image
- _, _, err = dockerCmd(t, "run", "--name=test", name)
- if err != nil {
- t.Fatal(err)
- }
+ _, _ = dockerCmd(c, "run", "--name=test", name)
+
cfg, err = inspectFieldJSON("test", "HostConfig")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var c2 hostConfig
if err := json.Unmarshal([]byte(cfg), &c2); err != nil {
- t.Fatal(err, cfg)
+ c.Fatal(err, cfg)
}
- mem = int64(c2.Memory)
- if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "1" || c2.CpuShares == 100 {
- t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d",
- mem, c2.MemorySwap, c2.CpusetCpus, c2.CpuShares)
+ if c2.Memory == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 || c2.CpuQuota == 8000 {
+ c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d, CpuQuota: %d",
+ c2.Memory, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares, c2.CpuQuota)
}
- logDone("build - resource constraints applied")
}
-func TestBuildEmptyStringVolume(t *testing.T) {
+func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) {
name := "testbuildemptystringvolume"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM busybox
@@ -5592,8 +5343,86 @@ func TestBuildEmptyStringVolume(t *testing.T) {
VOLUME $foo
`, false)
if err == nil {
- t.Fatal("Should have failed to build")
+ c.Fatal("Should have failed to build")
}
- logDone("build - empty string volume")
+}
+
+func (s *DockerSuite) TestBuildContainerWithCgroupParent(c *check.C) {
+ testRequires(c, NativeExecDriver)
+ testRequires(c, SameHostDaemon)
+ defer deleteImages()
+
+ cgroupParent := "test"
+ data, err := ioutil.ReadFile("/proc/self/cgroup")
+ if err != nil {
+ c.Fatalf("failed to read '/proc/self/cgroup - %v", err)
+ }
+ selfCgroupPaths := parseCgroupPaths(string(data))
+ _, found := selfCgroupPaths["memory"]
+ if !found {
+ c.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths)
+ }
+ cmd := exec.Command(dockerBinary, "build", "--cgroup-parent", cgroupParent, "-")
+ cmd.Stdin = strings.NewReader(`
+FROM busybox
+RUN cat /proc/self/cgroup
+`)
+
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err)
+ }
+}
+
+func (s *DockerSuite) TestBuildNoDupOutput(c *check.C) {
+ // Check to make sure our build output prints the Dockerfile cmd
+ // property - there was a bug that caused it to be duplicated on the
+ // Step X line
+ name := "testbuildnodupoutput"
+
+ _, out, err := buildImageWithOut(name, `
+ FROM busybox
+ RUN env`, false)
+ if err != nil {
+ c.Fatalf("Build should have worked: %q", err)
+ }
+
+ exp := "\nStep 1 : RUN env\n"
+ if !strings.Contains(out, exp) {
+ c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", out, exp)
+ }
+}
+
+func (s *DockerSuite) TestBuildBadCmdFlag(c *check.C) {
+ name := "testbuildbadcmdflag"
+
+ _, out, err := buildImageWithOut(name, `
+ FROM busybox
+ MAINTAINER --boo joe@example.com`, false)
+ if err == nil {
+ c.Fatal("Build should have failed")
+ }
+
+ exp := "\nUnknown flag: boo\n"
+ if !strings.Contains(out, exp) {
+ c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", out, exp)
+ }
+}
+
+func (s *DockerSuite) TestBuildRUNErrMsg(c *check.C) {
+ // Test to make sure the bad command is quoted with just "s and
+ // not as a Go []string
+ name := "testbuildbadrunerrmsg"
+ _, out, err := buildImageWithOut(name, `
+ FROM busybox
+ RUN badEXE a1 \& a2 a3`, false) // tab between a2 and a3
+ if err == nil {
+ c.Fatal("Should have failed to build")
+ }
+
+ exp := `The command '/bin/sh -c badEXE a1 \& a2 a3' returned a non-zero code: 127`
+ if !strings.Contains(out, exp) {
+ c.Fatalf("RUN doesn't have the correct output:\nGot:%s\nExpected:%s", out, exp)
+ }
}
diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go
index 24ebf0cf7..d4d494995 100644
--- a/integration-cli/docker_cli_by_digest_test.go
+++ b/integration-cli/docker_cli_by_digest_test.go
@@ -5,9 +5,9 @@ import (
"os/exec"
"regexp"
"strings"
- "testing"
"github.com/docker/docker/utils"
+ "github.com/go-check/check"
)
var (
@@ -22,18 +22,17 @@ func setupImage() (string, error) {
func setupImageWithTag(tag string) (string, error) {
containerName := "busyboxbydigest"
- c := exec.Command(dockerBinary, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox")
- if _, err := runCommand(c); err != nil {
+ cmd := exec.Command(dockerBinary, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox")
+ if _, err := runCommand(cmd); err != nil {
return "", err
}
// tag the image to upload it to the private registry
repoAndTag := utils.ImageReference(repoName, tag)
- c = exec.Command(dockerBinary, "commit", containerName, repoAndTag)
- if out, _, err := runCommandWithOutput(c); err != nil {
+ cmd = exec.Command(dockerBinary, "commit", containerName, repoAndTag)
+ if out, _, err := runCommandWithOutput(cmd); err != nil {
return "", fmt.Errorf("image tagging failed: %s, %v", out, err)
}
- defer deleteImages(repoAndTag)
// delete the container as we don't need it any more
if err := deleteContainer(containerName); err != nil {
@@ -41,15 +40,15 @@ func setupImageWithTag(tag string) (string, error) {
}
// push the image
- c = exec.Command(dockerBinary, "push", repoAndTag)
- out, _, err := runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "push", repoAndTag)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
return "", fmt.Errorf("pushing the image to the private registry has failed: %s, %v", out, err)
}
// delete our local repo that we previously tagged
- c = exec.Command(dockerBinary, "rmi", repoAndTag)
- if out, _, err := runCommandWithOutput(c); err != nil {
+ cmd = exec.Command(dockerBinary, "rmi", repoAndTag)
+ if out, _, err := runCommandWithOutput(cmd); err != nil {
return "", fmt.Errorf("error deleting images prior to real test: %s, %v", out, err)
}
@@ -63,473 +62,436 @@ func setupImageWithTag(tag string) (string, error) {
return pushDigest, nil
}
-func TestPullByTagDisplaysDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
pushDigest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
// pull from the registry using the tag
- c := exec.Command(dockerBinary, "pull", repoName)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", repoName)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by tag: %s, %v", out, err)
+ c.Fatalf("error pulling by tag: %s, %v", out, err)
}
- defer deleteImages(repoName)
// the pull output includes "Digest: ", so find that
matches := digestRegex.FindStringSubmatch(out)
if len(matches) != 2 {
- t.Fatalf("unable to parse digest from pull output: %s", out)
+ c.Fatalf("unable to parse digest from pull output: %s", out)
}
pullDigest := matches[1]
// make sure the pushed and pull digests match
if pushDigest != pullDigest {
- t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
+ c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
}
-
- logDone("by_digest - pull by tag displays digest")
}
-func TestPullByDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) {
pushDigest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
// pull from the registry using the @ reference
imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
- c := exec.Command(dockerBinary, "pull", imageReference)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
- defer deleteImages(imageReference)
// the pull output includes "Digest: ", so find that
matches := digestRegex.FindStringSubmatch(out)
if len(matches) != 2 {
- t.Fatalf("unable to parse digest from pull output: %s", out)
+ c.Fatalf("unable to parse digest from pull output: %s", out)
}
pullDigest := matches[1]
// make sure the pushed and pull digests match
if pushDigest != pullDigest {
- t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
+ c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
}
-
- logDone("by_digest - pull by digest")
}
-func TestCreateByDigest(t *testing.T) {
- defer setupRegistry(t)()
+func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) {
+ // pull from the registry using the @ reference
+ imageReference := fmt.Sprintf("%s@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", repoName)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
+ if err == nil || !strings.Contains(out, "pulling with digest reference failed from v2 registry") {
+ c.Fatalf("expected non-zero exit status and correct error message when pulling non-existing image: %s", out)
+ }
+}
+func (s *DockerRegistrySuite) TestCreateByDigest(c *check.C) {
pushDigest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
containerName := "createByDigest"
- c := exec.Command(dockerBinary, "create", "--name", containerName, imageReference)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "create", "--name", containerName, imageReference)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error creating by digest: %s, %v", out, err)
+ c.Fatalf("error creating by digest: %s, %v", out, err)
}
- defer deleteContainer(containerName)
res, err := inspectField(containerName, "Config.Image")
if err != nil {
- t.Fatalf("failed to get Config.Image: %s, %v", out, err)
+ c.Fatalf("failed to get Config.Image: %s, %v", out, err)
}
if res != imageReference {
- t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
+ c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
}
-
- logDone("by_digest - create by digest")
}
-func TestRunByDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestRunByDigest(c *check.C) {
pushDigest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
containerName := "runByDigest"
- c := exec.Command(dockerBinary, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest")
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest")
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error run by digest: %s, %v", out, err)
+ c.Fatalf("error run by digest: %s, %v", out, err)
}
- defer deleteContainer(containerName)
foundRegex := regexp.MustCompile("found=([^\n]+)")
matches := foundRegex.FindStringSubmatch(out)
if len(matches) != 2 {
- t.Fatalf("error locating expected 'found=1' output: %s", out)
+ c.Fatalf("error locating expected 'found=1' output: %s", out)
}
if matches[1] != "1" {
- t.Fatalf("Expected %q, got %q", "1", matches[1])
+ c.Fatalf("Expected %q, got %q", "1", matches[1])
}
res, err := inspectField(containerName, "Config.Image")
if err != nil {
- t.Fatalf("failed to get Config.Image: %s, %v", out, err)
+ c.Fatalf("failed to get Config.Image: %s, %v", out, err)
}
if res != imageReference {
- t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
+ c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
}
-
- logDone("by_digest - run by digest")
}
-func TestRemoveImageByDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestRemoveImageByDigest(c *check.C) {
digest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference := fmt.Sprintf("%s@%s", repoName, digest)
// pull from the registry using the @ reference
- c := exec.Command(dockerBinary, "pull", imageReference)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
// make sure inspect runs ok
if _, err := inspectField(imageReference, "Id"); err != nil {
- t.Fatalf("failed to inspect image: %v", err)
+ c.Fatalf("failed to inspect image: %v", err)
}
// do the delete
if err := deleteImages(imageReference); err != nil {
- t.Fatalf("unexpected error deleting image: %v", err)
+ c.Fatalf("unexpected error deleting image: %v", err)
}
// try to inspect again - it should error this time
if _, err := inspectField(imageReference, "Id"); err == nil {
- t.Fatalf("unexpected nil err trying to inspect what should be a non-existent image")
+ c.Fatalf("unexpected nil err trying to inspect what should be a non-existent image")
} else if !strings.Contains(err.Error(), "No such image") {
- t.Fatalf("expected 'No such image' output, got %v", err)
+ c.Fatalf("expected 'No such image' output, got %v", err)
}
-
- logDone("by_digest - remove image by digest")
}
-func TestBuildByDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestBuildByDigest(c *check.C) {
digest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference := fmt.Sprintf("%s@%s", repoName, digest)
// pull from the registry using the @ reference
- c := exec.Command(dockerBinary, "pull", imageReference)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
// get the image id
imageID, err := inspectField(imageReference, "Id")
if err != nil {
- t.Fatalf("error getting image id: %v", err)
+ c.Fatalf("error getting image id: %v", err)
}
// do the build
name := "buildbydigest"
- defer deleteImages(name)
_, err = buildImage(name, fmt.Sprintf(
`FROM %s
CMD ["/bin/echo", "Hello World"]`, imageReference),
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// get the build's image id
res, err := inspectField(name, "Config.Image")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// make sure they match
if res != imageID {
- t.Fatalf("Image %s, expected %s", res, imageID)
+ c.Fatalf("Image %s, expected %s", res, imageID)
}
-
- logDone("by_digest - build by digest")
}
-func TestTagByDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestTagByDigest(c *check.C) {
digest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference := fmt.Sprintf("%s@%s", repoName, digest)
// pull from the registry using the @ reference
- c := exec.Command(dockerBinary, "pull", imageReference)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
// tag it
tag := "tagbydigest"
- c = exec.Command(dockerBinary, "tag", imageReference, tag)
- if _, err := runCommand(c); err != nil {
- t.Fatalf("unexpected error tagging: %v", err)
+ cmd = exec.Command(dockerBinary, "tag", imageReference, tag)
+ if _, err := runCommand(cmd); err != nil {
+ c.Fatalf("unexpected error tagging: %v", err)
}
expectedID, err := inspectField(imageReference, "Id")
if err != nil {
- t.Fatalf("error getting original image id: %v", err)
+ c.Fatalf("error getting original image id: %v", err)
}
tagID, err := inspectField(tag, "Id")
if err != nil {
- t.Fatalf("error getting tagged image id: %v", err)
+ c.Fatalf("error getting tagged image id: %v", err)
}
if tagID != expectedID {
- t.Fatalf("expected image id %q, got %q", expectedID, tagID)
+ c.Fatalf("expected image id %q, got %q", expectedID, tagID)
}
-
- logDone("by_digest - tag by digest")
}
-func TestListImagesWithoutDigests(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestListImagesWithoutDigests(c *check.C) {
digest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference := fmt.Sprintf("%s@%s", repoName, digest)
// pull from the registry using the @ reference
- c := exec.Command(dockerBinary, "pull", imageReference)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
- c = exec.Command(dockerBinary, "images")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "images")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error listing images: %s, %v", out, err)
+ c.Fatalf("error listing images: %s, %v", out, err)
}
if strings.Contains(out, "DIGEST") {
- t.Fatalf("list output should not have contained DIGEST header: %s", out)
+ c.Fatalf("list output should not have contained DIGEST header: %s", out)
}
- logDone("by_digest - list images - digest header not displayed by default")
}
-func TestListImagesWithDigests(t *testing.T) {
- defer setupRegistry(t)()
- defer deleteImages(repoName+":tag1", repoName+":tag2")
+func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
// setup image1
digest1, err := setupImageWithTag("tag1")
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1)
- defer deleteImages(imageReference1)
- t.Logf("imageReference1 = %s", imageReference1)
+ c.Logf("imageReference1 = %s", imageReference1)
// pull image1 by digest
- c := exec.Command(dockerBinary, "pull", imageReference1)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", imageReference1)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
// list images
- c = exec.Command(dockerBinary, "images", "--digests")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "images", "--digests")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error listing images: %s, %v", out, err)
+ c.Fatalf("error listing images: %s, %v", out, err)
}
// make sure repo shown, tag=, digest = $digest1
re1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`)
if !re1.MatchString(out) {
- t.Fatalf("expected %q: %s", re1.String(), out)
+ c.Fatalf("expected %q: %s", re1.String(), out)
}
// setup image2
digest2, err := setupImageWithTag("tag2")
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2)
- defer deleteImages(imageReference2)
- t.Logf("imageReference2 = %s", imageReference2)
+ c.Logf("imageReference2 = %s", imageReference2)
// pull image1 by digest
- c = exec.Command(dockerBinary, "pull", imageReference1)
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "pull", imageReference1)
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
// pull image2 by digest
- c = exec.Command(dockerBinary, "pull", imageReference2)
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "pull", imageReference2)
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
// list images
- c = exec.Command(dockerBinary, "images", "--digests")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "images", "--digests")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error listing images: %s, %v", out, err)
+ c.Fatalf("error listing images: %s, %v", out, err)
}
// make sure repo shown, tag=, digest = $digest1
if !re1.MatchString(out) {
- t.Fatalf("expected %q: %s", re1.String(), out)
+ c.Fatalf("expected %q: %s", re1.String(), out)
}
// make sure repo shown, tag=, digest = $digest2
re2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`)
if !re2.MatchString(out) {
- t.Fatalf("expected %q: %s", re2.String(), out)
+ c.Fatalf("expected %q: %s", re2.String(), out)
}
// pull tag1
- c = exec.Command(dockerBinary, "pull", repoName+":tag1")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "pull", repoName+":tag1")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling tag1: %s, %v", out, err)
+ c.Fatalf("error pulling tag1: %s, %v", out, err)
}
// list images
- c = exec.Command(dockerBinary, "images", "--digests")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "images", "--digests")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error listing images: %s, %v", out, err)
+ c.Fatalf("error listing images: %s, %v", out, err)
}
// make sure image 1 has repo, tag, AND repo, , digest
reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*\s`)
reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`)
if !reWithTag1.MatchString(out) {
- t.Fatalf("expected %q: %s", reWithTag1.String(), out)
+ c.Fatalf("expected %q: %s", reWithTag1.String(), out)
}
if !reWithDigest1.MatchString(out) {
- t.Fatalf("expected %q: %s", reWithDigest1.String(), out)
+ c.Fatalf("expected %q: %s", reWithDigest1.String(), out)
}
// make sure image 2 has repo, , digest
if !re2.MatchString(out) {
- t.Fatalf("expected %q: %s", re2.String(), out)
+ c.Fatalf("expected %q: %s", re2.String(), out)
}
// pull tag 2
- c = exec.Command(dockerBinary, "pull", repoName+":tag2")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "pull", repoName+":tag2")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling tag2: %s, %v", out, err)
+ c.Fatalf("error pulling tag2: %s, %v", out, err)
}
// list images
- c = exec.Command(dockerBinary, "images", "--digests")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "images", "--digests")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error listing images: %s, %v", out, err)
+ c.Fatalf("error listing images: %s, %v", out, err)
}
// make sure image 1 has repo, tag, digest
if !reWithTag1.MatchString(out) {
- t.Fatalf("expected %q: %s", re1.String(), out)
+ c.Fatalf("expected %q: %s", re1.String(), out)
}
// make sure image 2 has repo, tag, digest
reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*\s`)
reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`)
if !reWithTag2.MatchString(out) {
- t.Fatalf("expected %q: %s", reWithTag2.String(), out)
+ c.Fatalf("expected %q: %s", reWithTag2.String(), out)
}
if !reWithDigest2.MatchString(out) {
- t.Fatalf("expected %q: %s", reWithDigest2.String(), out)
+ c.Fatalf("expected %q: %s", reWithDigest2.String(), out)
}
// list images
- c = exec.Command(dockerBinary, "images", "--digests")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "images", "--digests")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error listing images: %s, %v", out, err)
+ c.Fatalf("error listing images: %s, %v", out, err)
}
// make sure image 1 has repo, tag, digest
if !reWithTag1.MatchString(out) {
- t.Fatalf("expected %q: %s", re1.String(), out)
+ c.Fatalf("expected %q: %s", re1.String(), out)
}
// make sure image 2 has repo, tag, digest
if !reWithTag2.MatchString(out) {
- t.Fatalf("expected %q: %s", re2.String(), out)
+ c.Fatalf("expected %q: %s", re2.String(), out)
}
// make sure busybox has tag, but not digest
busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*\s`)
if !busyboxRe.MatchString(out) {
- t.Fatalf("expected %q: %s", busyboxRe.String(), out)
+ c.Fatalf("expected %q: %s", busyboxRe.String(), out)
}
-
- logDone("by_digest - list images with digests")
}
-func TestDeleteImageByIDOnlyPulledByDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) {
pushDigest, err := setupImage()
if err != nil {
- t.Fatalf("error setting up image: %v", err)
+ c.Fatalf("error setting up image: %v", err)
}
// pull from the registry using the @ reference
imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
- c := exec.Command(dockerBinary, "pull", imageReference)
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error pulling by digest: %s, %v", out, err)
+ c.Fatalf("error pulling by digest: %s, %v", out, err)
}
// just in case...
- defer deleteImages(imageReference)
imageID, err := inspectField(imageReference, ".Id")
if err != nil {
- t.Fatalf("error inspecting image id: %v", err)
+ c.Fatalf("error inspecting image id: %v", err)
}
- c = exec.Command(dockerBinary, "rmi", imageID)
- if _, err := runCommand(c); err != nil {
- t.Fatalf("error deleting image by id: %v", err)
+ cmd = exec.Command(dockerBinary, "rmi", imageID)
+ if _, err := runCommand(cmd); err != nil {
+ c.Fatalf("error deleting image by id: %v", err)
}
-
- logDone("by_digest - delete image by id only pulled by digest")
}
diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go
index 8d596bdda..ef157c63f 100644
--- a/integration-cli/docker_cli_commit_test.go
+++ b/integration-cli/docker_cli_commit_test.go
@@ -3,149 +3,127 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestCommitAfterContainerIsDone(t *testing.T) {
+func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %s, %v", out, err)
+ c.Fatalf("failed to run container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID)
if _, _, err = runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("error thrown while waiting for container: %s, %v", out, err)
+ c.Fatalf("error thrown while waiting for container: %s, %v", out, err)
}
commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID)
out, _, err = runCommandWithOutput(commitCmd)
if err != nil {
- t.Fatalf("failed to commit container to image: %s, %v", out, err)
+ c.Fatalf("failed to commit container to image: %s, %v", out, err)
}
- cleanedImageID := stripTrailingCharacters(out)
+ cleanedImageID := strings.TrimSpace(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID)
if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("failed to inspect image: %s, %v", out, err)
+ c.Fatalf("failed to inspect image: %s, %v", out, err)
}
-
- deleteContainer(cleanedContainerID)
- deleteImages(cleanedImageID)
-
- logDone("commit - echo foo and commit the image")
}
-func TestCommitWithoutPause(t *testing.T) {
+func (s *DockerSuite) TestCommitWithoutPause(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %s, %v", out, err)
+ c.Fatalf("failed to run container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID)
if _, _, err = runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("error thrown while waiting for container: %s, %v", out, err)
+ c.Fatalf("error thrown while waiting for container: %s, %v", out, err)
}
commitCmd := exec.Command(dockerBinary, "commit", "-p=false", cleanedContainerID)
out, _, err = runCommandWithOutput(commitCmd)
if err != nil {
- t.Fatalf("failed to commit container to image: %s, %v", out, err)
+ c.Fatalf("failed to commit container to image: %s, %v", out, err)
}
- cleanedImageID := stripTrailingCharacters(out)
+ cleanedImageID := strings.TrimSpace(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID)
if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("failed to inspect image: %s, %v", out, err)
+ c.Fatalf("failed to inspect image: %s, %v", out, err)
}
-
- deleteContainer(cleanedContainerID)
- deleteImages(cleanedImageID)
-
- logDone("commit - echo foo and commit the image with --pause=false")
}
//test commit a paused container should not unpause it after commit
-func TestCommitPausedContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCommitPausedContainer(c *check.C) {
defer unpauseAllContainers()
cmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox")
out, _, _, err := runCommandWithStdoutStderr(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
cmd = exec.Command(dockerBinary, "pause", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(cmd)
if err != nil {
- t.Fatalf("failed to pause container: %v, output: %q", err, out)
+ c.Fatalf("failed to pause container: %v, output: %q", err, out)
}
commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID)
out, _, err = runCommandWithOutput(commitCmd)
if err != nil {
- t.Fatalf("failed to commit container to image: %s, %v", out, err)
- }
- cleanedImageID := stripTrailingCharacters(out)
- defer deleteImages(cleanedImageID)
-
- cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Paused}}", cleanedContainerID)
- out, _, _, err = runCommandWithStdoutStderr(cmd)
- if err != nil {
- t.Fatalf("failed to inspect container: %v, output: %q", err, out)
+ c.Fatalf("failed to commit container to image: %s, %v", out, err)
}
+ out, err = inspectField(cleanedContainerID, "State.Paused")
+ c.Assert(err, check.IsNil)
if !strings.Contains(out, "true") {
- t.Fatalf("commit should not unpause a paused container")
+ c.Fatalf("commit should not unpause a paused container")
}
-
- logDone("commit - commit a paused container will not unpause it")
}
-func TestCommitNewFile(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCommitNewFile(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "commiter", "busybox", "/bin/sh", "-c", "echo koye > /foo")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "commit", "commiter")
imageID, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
imageID = strings.Trim(imageID, "\r\n")
- defer deleteImages(imageID)
cmd = exec.Command(dockerBinary, "run", imageID, "cat", "/foo")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual != "koye" {
- t.Fatalf("expected output koye received %q", actual)
+ c.Fatalf("expected output koye received %q", actual)
}
- logDone("commit - commit file and read")
}
-func TestCommitHardlink(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCommitHardlink(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-t", "--name", "hardlinks", "busybox", "sh", "-c", "touch file1 && ln file1 file2 && ls -di file1 file2")
firstOuput, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
chunks := strings.Split(strings.TrimSpace(firstOuput), " ")
@@ -158,21 +136,20 @@ func TestCommitHardlink(t *testing.T) {
}
}
if !found {
- t.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:])
+ c.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:])
}
cmd = exec.Command(dockerBinary, "commit", "hardlinks", "hardlinks")
imageID, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(imageID, err)
+ c.Fatal(imageID, err)
}
imageID = strings.Trim(imageID, "\r\n")
- defer deleteImages(imageID)
cmd = exec.Command(dockerBinary, "run", "-t", "hardlinks", "ls", "-di", "file1", "file2")
secondOuput, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
chunks = strings.Split(strings.TrimSpace(secondOuput), " ")
@@ -185,68 +162,60 @@ func TestCommitHardlink(t *testing.T) {
}
}
if !found {
- t.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:])
+ c.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:])
}
- logDone("commit - commit hardlinks")
}
-func TestCommitTTY(t *testing.T) {
- defer deleteImages("ttytest")
- defer deleteAllContainers()
+func (s *DockerSuite) TestCommitTTY(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-t", "--name", "tty", "busybox", "/bin/ls")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "commit", "tty", "ttytest")
imageID, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
imageID = strings.Trim(imageID, "\r\n")
cmd = exec.Command(dockerBinary, "run", "ttytest", "/bin/ls")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("commit - commit tty")
}
-func TestCommitWithHostBindMount(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "bind-commit", "-v", "/dev/null:/winning", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "commit", "bind-commit", "bindtest")
imageID, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(imageID, err)
+ c.Fatal(imageID, err)
}
imageID = strings.Trim(imageID, "\r\n")
- defer deleteImages(imageID)
cmd = exec.Command(dockerBinary, "run", "bindtest", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("commit - commit bind mounted file")
}
-func TestCommitChange(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCommitChange(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "test", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "commit",
@@ -257,25 +226,68 @@ func TestCommitChange(t *testing.T) {
"test", "test-commit")
imageId, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(imageId, err)
+ c.Fatal(imageId, err)
}
imageId = strings.Trim(imageId, "\r\n")
- defer deleteImages(imageId)
expected := map[string]string{
- "Config.ExposedPorts": "map[8080/tcp:map[]]",
+ "Config.ExposedPorts": "map[8080/tcp:{}]",
"Config.Env": "[DEBUG=true test=1 PATH=/foo]",
}
for conf, value := range expected {
res, err := inspectField(imageId, conf)
- if err != nil {
- t.Errorf("failed to get value %s, error: %s", conf, err)
- }
+ c.Assert(err, check.IsNil)
if res != value {
- t.Errorf("%s('%s'), expected %s", conf, res, value)
+ c.Errorf("%s('%s'), expected %s", conf, res, value)
}
}
- logDone("commit - commit --change")
+}
+
+// TODO: commit --run is deprecated, remove this once --run is removed
+func (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) {
+ name := "commit-test"
+ out, _ := dockerCmd(c, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo")
+ id := strings.TrimSpace(out)
+
+ dockerCmd(c, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test")
+
+ out, _ = dockerCmd(c, "run", "--name", name, "commit-test")
+ if strings.TrimSpace(out) != "testing" {
+ c.Fatal("run config in committed container was not merged")
+ }
+
+ type cfg struct {
+ Env []string
+ Cmd []string
+ }
+ config1 := cfg{}
+ if err := inspectFieldAndMarshall(id, "Config", &config1); err != nil {
+ c.Fatal(err)
+ }
+ config2 := cfg{}
+ if err := inspectFieldAndMarshall(name, "Config", &config2); err != nil {
+ c.Fatal(err)
+ }
+
+ // Env has at least PATH loaded as well here, so let's just grab the FOO one
+ var env1, env2 string
+ for _, e := range config1.Env {
+ if strings.HasPrefix(e, "FOO") {
+ env1 = e
+ break
+ }
+ }
+ for _, e := range config2.Env {
+ if strings.HasPrefix(e, "FOO") {
+ env2 = e
+ break
+ }
+ }
+
+ if len(config1.Env) != len(config2.Env) || env1 != env2 && env2 != "" {
+ c.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env)
+ }
+
}
diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go
new file mode 100644
index 000000000..5ccd7af10
--- /dev/null
+++ b/integration-cli/docker_cli_config_test.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/docker/docker/pkg/homedir"
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestConfigHttpHeader(c *check.C) {
+ testRequires(c, UnixCli) // Can't set/unset HOME on windows right now
+ // We either need a level of Go that supports Unsetenv (for cases
+ // when HOME/USERPROFILE isn't set), or we need to be able to use
+ // os/user but user.Current() only works if we aren't statically compiling
+
+ var headers map[string][]string
+
+ server := httptest.NewServer(http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ headers = r.Header
+ }))
+ defer server.Close()
+
+ homeKey := homedir.Key()
+ homeVal := homedir.Get()
+ tmpDir, _ := ioutil.TempDir("", "fake-home")
+ defer os.RemoveAll(tmpDir)
+
+ dotDocker := filepath.Join(tmpDir, ".docker")
+ os.Mkdir(dotDocker, 0600)
+ tmpCfg := filepath.Join(dotDocker, "config.json")
+
+ defer func() { os.Setenv(homeKey, homeVal) }()
+ os.Setenv(homeKey, tmpDir)
+
+ data := `{
+ "HttpHeaders": { "MyHeader": "MyValue" }
+ }`
+
+ err := ioutil.WriteFile(tmpCfg, []byte(data), 0600)
+ if err != nil {
+ c.Fatalf("Err creating file(%s): %v", tmpCfg, err)
+ }
+
+ cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "ps")
+ out, _, _ := runCommandWithOutput(cmd)
+
+ if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" {
+ c.Fatalf("Missing/bad header: %q\nout:%v", headers, out)
+ }
+}
diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go
index db5f36388..fd63bdbf2 100644
--- a/integration-cli/docker_cli_cp_test.go
+++ b/integration-cli/docker_cli_cp_test.go
@@ -9,7 +9,8 @@ import (
"path"
"path/filepath"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
const (
@@ -24,27 +25,26 @@ const (
// Test for #5656
// Check that garbage paths don't escape the container's rootfs
-func TestCpGarbagePath(t *testing.T) {
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+func (s *DockerSuite) TestCpGarbagePath(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
hostFile, err := os.Create(cpFullPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer hostFile.Close()
defer os.RemoveAll(cpTestPathParent)
@@ -53,7 +53,7 @@ func TestCpGarbagePath(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-integration")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tmpname := filepath.Join(tmpdir, cpTestName)
@@ -61,52 +61,47 @@ func TestCpGarbagePath(t *testing.T) {
path := path.Join("../../../../../../../../../../../../", cpFullPath)
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
- if err != nil {
- t.Fatalf("couldn't copy from garbage path: %s:%s %s", cleanedContainerID, path, err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
file, _ := os.Open(tmpname)
defer file.Close()
test, err := ioutil.ReadAll(file)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if string(test) == cpHostContents {
- t.Errorf("output matched host file -- garbage path can escape container rootfs")
+ c.Errorf("output matched host file -- garbage path can escape container rootfs")
}
if string(test) != cpContainerContents {
- t.Errorf("output doesn't match the input for garbage path")
+ c.Errorf("output doesn't match the input for garbage path")
}
- logDone("cp - garbage paths relative to container's rootfs")
}
// Check that relative paths are relative to the container's rootfs
-func TestCpRelativePath(t *testing.T) {
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+func (s *DockerSuite) TestCpRelativePath(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
hostFile, err := os.Create(cpFullPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer hostFile.Close()
defer os.RemoveAll(cpTestPathParent)
@@ -116,7 +111,7 @@ func TestCpRelativePath(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-integration")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tmpname := filepath.Join(tmpdir, cpTestName)
@@ -128,55 +123,50 @@ func TestCpRelativePath(t *testing.T) {
// get this unix-path manipulation on windows with filepath.
relPath = cpFullPath[1:]
} else {
- t.Fatalf("path %s was assumed to be an absolute path", cpFullPath)
+ c.Fatalf("path %s was assumed to be an absolute path", cpFullPath)
}
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir)
- if err != nil {
- t.Fatalf("couldn't copy from relative path: %s:%s %s", cleanedContainerID, relPath, err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+relPath, tmpdir)
file, _ := os.Open(tmpname)
defer file.Close()
test, err := ioutil.ReadAll(file)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if string(test) == cpHostContents {
- t.Errorf("output matched host file -- relative path can escape container rootfs")
+ c.Errorf("output matched host file -- relative path can escape container rootfs")
}
if string(test) != cpContainerContents {
- t.Errorf("output doesn't match the input for relative path")
+ c.Errorf("output doesn't match the input for relative path")
}
- logDone("cp - relative paths relative to container's rootfs")
}
// Check that absolute paths are relative to the container's rootfs
-func TestCpAbsolutePath(t *testing.T) {
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+func (s *DockerSuite) TestCpAbsolutePath(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
hostFile, err := os.Create(cpFullPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer hostFile.Close()
defer os.RemoveAll(cpTestPathParent)
@@ -186,7 +176,7 @@ func TestCpAbsolutePath(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-integration")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tmpname := filepath.Join(tmpdir, cpTestName)
@@ -194,53 +184,48 @@ func TestCpAbsolutePath(t *testing.T) {
path := cpFullPath
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
- if err != nil {
- t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
file, _ := os.Open(tmpname)
defer file.Close()
test, err := ioutil.ReadAll(file)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if string(test) == cpHostContents {
- t.Errorf("output matched host file -- absolute path can escape container rootfs")
+ c.Errorf("output matched host file -- absolute path can escape container rootfs")
}
if string(test) != cpContainerContents {
- t.Errorf("output doesn't match the input for absolute path")
+ c.Errorf("output doesn't match the input for absolute path")
}
- logDone("cp - absolute paths relative to container's rootfs")
}
// Test for #5619
// Check that absolute symlinks are still relative to the container's rootfs
-func TestCpAbsoluteSymlink(t *testing.T) {
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path")
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
hostFile, err := os.Create(cpFullPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer hostFile.Close()
defer os.RemoveAll(cpTestPathParent)
@@ -250,7 +235,7 @@ func TestCpAbsoluteSymlink(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-integration")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tmpname := filepath.Join(tmpdir, cpTestName)
@@ -258,53 +243,48 @@ func TestCpAbsoluteSymlink(t *testing.T) {
path := path.Join("/", "container_path")
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
- if err != nil {
- t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
file, _ := os.Open(tmpname)
defer file.Close()
test, err := ioutil.ReadAll(file)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if string(test) == cpHostContents {
- t.Errorf("output matched host file -- absolute symlink can escape container rootfs")
+ c.Errorf("output matched host file -- absolute symlink can escape container rootfs")
}
if string(test) != cpContainerContents {
- t.Errorf("output doesn't match the input for absolute symlink")
+ c.Errorf("output doesn't match the input for absolute symlink")
}
- logDone("cp - absolute symlink relative to container's rootfs")
}
// Test for #5619
// Check that symlinks which are part of the resource path are still relative to the container's rootfs
-func TestCpSymlinkComponent(t *testing.T) {
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path")
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+func (s *DockerSuite) TestCpSymlinkComponent(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
hostFile, err := os.Create(cpFullPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer hostFile.Close()
defer os.RemoveAll(cpTestPathParent)
@@ -314,7 +294,7 @@ func TestCpSymlinkComponent(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-integration")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tmpname := filepath.Join(tmpdir, cpTestName)
@@ -322,237 +302,333 @@ func TestCpSymlinkComponent(t *testing.T) {
path := path.Join("/", "container_path", cpTestName)
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
- if err != nil {
- t.Fatalf("couldn't copy from symlink path component: %s:%s %s", cleanedContainerID, path, err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
file, _ := os.Open(tmpname)
defer file.Close()
test, err := ioutil.ReadAll(file)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if string(test) == cpHostContents {
- t.Errorf("output matched host file -- symlink path component can escape container rootfs")
+ c.Errorf("output matched host file -- symlink path component can escape container rootfs")
}
if string(test) != cpContainerContents {
- t.Errorf("output doesn't match the input for symlink path component")
+ c.Errorf("output doesn't match the input for symlink path component")
}
- logDone("cp - symlink path components relative to container's rootfs")
}
// Check that cp with unprivileged user doesn't return any error
-func TestCpUnprivilegedUser(t *testing.T) {
- testRequires(t, UnixCli) // uses chmod/su: not available on windows
+func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) {
+ testRequires(c, UnixCli) // uses chmod/su: not available on windows
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName)
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName)
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
tmpdir, err := ioutil.TempDir("", "docker-integration")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpdir)
if err = os.Chmod(tmpdir, 0777); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
path := cpTestName
_, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+cleanedContainerID+":"+path+" "+tmpdir))
if err != nil {
- t.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err)
+ c.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err)
}
- logDone("cp - unprivileged user")
}
-func TestCpVolumePath(t *testing.T) {
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestCpSpecialFiles(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ outDir, err := ioutil.TempDir("", "cp-test-special-files")
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer os.RemoveAll(outDir)
+
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
+ }
+
+ cleanedContainerID := strings.TrimSpace(out)
+
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
+ }
+
+ // Copy actual /etc/resolv.conf
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/resolv.conf", outDir)
+
+ expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf")
+ actual, err := ioutil.ReadFile(outDir + "/resolv.conf")
+
+ if !bytes.Equal(actual, expected) {
+ c.Fatalf("Expected copied file to be duplicate of the container resolvconf")
+ }
+
+ // Copy actual /etc/hosts
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hosts", outDir)
+
+ expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts")
+ actual, err = ioutil.ReadFile(outDir + "/hosts")
+
+ if !bytes.Equal(actual, expected) {
+ c.Fatalf("Expected copied file to be duplicate of the container hosts")
+ }
+
+ // Copy actual /etc/resolv.conf
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hostname", outDir)
+
+ expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname")
+ actual, err = ioutil.ReadFile(outDir + "/hostname")
+
+ if !bytes.Equal(actual, expected) {
+ c.Fatalf("Expected copied file to be duplicate of the container resolvconf")
+ }
+
+}
+
+func (s *DockerSuite) TestCpVolumePath(c *check.C) {
+ testRequires(c, SameHostDaemon)
tmpDir, err := ioutil.TempDir("", "cp-test-volumepath")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpDir)
outDir, err := ioutil.TempDir("", "cp-test-volumepath-out")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(outDir)
_, err = os.Create(tmpDir + "/test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- out, exitCode, err := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar")
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+ out, exitCode := dockerCmd(c, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
// Copy actual volume path
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir)
- if err != nil {
- t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo", outDir)
+
stat, err := os.Stat(outDir + "/foo")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !stat.IsDir() {
- t.Fatal("expected copied content to be dir")
+ c.Fatal("expected copied content to be dir")
}
stat, err = os.Stat(outDir + "/foo/bar")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if stat.IsDir() {
- t.Fatal("Expected file `bar` to be a file")
+ c.Fatal("Expected file `bar` to be a file")
}
// Copy file nested in volume
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir)
- if err != nil {
- t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo/bar", outDir)
+
stat, err = os.Stat(outDir + "/bar")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if stat.IsDir() {
- t.Fatal("Expected file `bar` to be a file")
+ c.Fatal("Expected file `bar` to be a file")
}
// Copy Bind-mounted dir
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir)
- if err != nil {
- t.Fatalf("couldn't copy from bind-mounted volume path: %s:%s %v", cleanedContainerID, "/baz", err)
- }
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz", outDir)
stat, err = os.Stat(outDir + "/baz")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !stat.IsDir() {
- t.Fatal("Expected `baz` to be a dir")
+ c.Fatal("Expected `baz` to be a dir")
}
// Copy file nested in bind-mounted dir
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir)
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz/test", outDir)
fb, err := ioutil.ReadFile(outDir + "/baz/test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
fb2, err := ioutil.ReadFile(tmpDir + "/test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !bytes.Equal(fb, fb2) {
- t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
+ c.Fatalf("Expected copied file to be duplicate of bind-mounted file")
}
// Copy bind-mounted file
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir)
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", outDir)
fb, err = ioutil.ReadFile(outDir + "/test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
fb2, err = ioutil.ReadFile(tmpDir + "/test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !bytes.Equal(fb, fb2) {
- t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
+ c.Fatalf("Expected copied file to be duplicate of bind-mounted file")
}
- logDone("cp - volume path")
}
-func TestCpToDot(t *testing.T) {
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
- if err != nil || exitCode != 0 {
- t.Fatal("failed to create a container", out, err)
+func (s *DockerSuite) TestCpToDot(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
}
tmpdir, err := ioutil.TempDir("", "docker-integration")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpdir)
cwd, err := os.Getwd()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.Chdir(cwd)
if err := os.Chdir(tmpdir); err != nil {
- t.Fatal(err)
- }
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", ".")
- if err != nil {
- t.Fatalf("couldn't docker cp to \".\" path: %s", err)
+ c.Fatal(err)
}
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", ".")
content, err := ioutil.ReadFile("./test")
if string(content) != "lololol\n" {
- t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n")
+ c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n")
}
- logDone("cp - to dot path")
}
-func TestCpToStdout(t *testing.T) {
- out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
- if err != nil || exitCode != 0 {
- t.Fatalf("failed to create a container:%s\n%s", out, err)
+func (s *DockerSuite) TestCpToStdout(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
+ if exitCode != 0 {
+ c.Fatalf("failed to create a container:%s\n", out)
}
- cID := stripTrailingCharacters(out)
- defer deleteContainer(cID)
+ cID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatalf("failed to set up container:%s\n%s", out, err)
+ out, _ = dockerCmd(c, "wait", cID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatalf("failed to set up container:%s\n", out)
}
- out, _, err = runCommandPipelineWithOutput(
+ out, _, err := runCommandPipelineWithOutput(
exec.Command(dockerBinary, "cp", cID+":/test", "-"),
exec.Command("tar", "-vtf", "-"))
+
if err != nil {
- t.Fatalf("Failed to run commands: %s", err)
+ c.Fatalf("Failed to run commands: %s", err)
}
if !strings.Contains(out, "test") || !strings.Contains(out, "-rw") {
- t.Fatalf("Missing file from tar TOC:\n%s", out)
+ c.Fatalf("Missing file from tar TOC:\n%s", out)
+ }
+}
+
+func (s *DockerSuite) TestCpNameHasColon(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
+ }
+
+ cleanedContainerID := strings.TrimSpace(out)
+
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
+ }
+
+ tmpdir, err := ioutil.TempDir("", "docker-integration")
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/te:s:t", tmpdir)
+ content, err := ioutil.ReadFile(tmpdir + "/te:s:t")
+ if string(content) != "lololol\n" {
+ c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n")
+ }
+}
+
+func (s *DockerSuite) TestCopyAndRestart(c *check.C) {
+ expectedMsg := "hello"
+ out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", expectedMsg).CombinedOutput()
+ if err != nil {
+ c.Fatal(string(out), err)
+ }
+ id := strings.TrimSpace(string(out))
+
+ if out, err = exec.Command(dockerBinary, "wait", id).CombinedOutput(); err != nil {
+ c.Fatalf("unable to wait for container: %s", err)
+ }
+
+ status := strings.TrimSpace(string(out))
+ if status != "0" {
+ c.Fatalf("container exited with status %s", status)
+ }
+
+ tmpDir, err := ioutil.TempDir("", "test-docker-restart-after-copy-")
+ if err != nil {
+ c.Fatalf("unable to make temporary directory: %s", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ if _, err = exec.Command(dockerBinary, "cp", fmt.Sprintf("%s:/etc/issue", id), tmpDir).CombinedOutput(); err != nil {
+ c.Fatalf("unable to copy from busybox container: %s", err)
+ }
+
+ if out, err = exec.Command(dockerBinary, "start", "-a", id).CombinedOutput(); err != nil {
+ c.Fatalf("unable to start busybox container after copy: %s - %s", err, out)
+ }
+
+ msg := strings.TrimSpace(string(out))
+ if msg != expectedMsg {
+ c.Fatalf("expected %q but got %q", expectedMsg, msg)
}
- logDone("cp - to stdout")
}
diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go
index e32400e60..019ea97fc 100644
--- a/integration-cli/docker_cli_create_test.go
+++ b/integration-cli/docker_cli_create_test.go
@@ -2,31 +2,31 @@ package main
import (
"encoding/json"
+ "fmt"
"os"
"os/exec"
"reflect"
- "testing"
+ "strings"
"time"
"github.com/docker/docker/nat"
+ "github.com/go-check/check"
)
// Make sure we can create a simple container with some args
-func TestCreateArgs(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestCreateArgs(c *check.C) {
runCmd := exec.Command(dockerBinary, "create", "busybox", "command", "arg1", "arg2", "arg with space")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
out, _, err = runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("out should've been a container id: %s, %v", out, err)
+ c.Fatalf("out should've been a container id: %s, %v", out, err)
}
containers := []struct {
@@ -37,48 +37,46 @@ func TestCreateArgs(t *testing.T) {
Image string
}{}
if err := json.Unmarshal([]byte(out), &containers); err != nil {
- t.Fatalf("Error inspecting the container: %s", err)
+ c.Fatalf("Error inspecting the container: %s", err)
}
if len(containers) != 1 {
- t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+ c.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)
+ cont := containers[0]
+ if cont.Path != "command" {
+ c.Fatalf("Unexpected container path. Expected command, received: %s", cont.Path)
}
b := false
expected := []string{"arg1", "arg2", "arg with space"}
for i, arg := range expected {
- if arg != c.Args[i] {
+ if arg != cont.Args[i] {
b = true
break
}
}
- if len(c.Args) != len(expected) || b {
- t.Fatalf("Unexpected args. Expected %v, received: %v", expected, c.Args)
+ if len(cont.Args) != len(expected) || b {
+ c.Fatalf("Unexpected args. Expected %v, received: %v", expected, cont.Args)
}
- logDone("create - args")
}
// Make sure we can set hostconfig options too
-func TestCreateHostConfig(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCreateHostConfig(c *check.C) {
runCmd := exec.Command(dockerBinary, "create", "-P", "busybox", "echo")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
out, _, err = runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("out should've been a container id: %s, %v", out, err)
+ c.Fatalf("out should've been a container id: %s, %v", out, err)
}
containers := []struct {
@@ -87,39 +85,37 @@ func TestCreateHostConfig(t *testing.T) {
}
}{}
if err := json.Unmarshal([]byte(out), &containers); err != nil {
- t.Fatalf("Error inspecting the container: %s", err)
+ c.Fatalf("Error inspecting the container: %s", err)
}
if len(containers) != 1 {
- t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+ c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
}
- c := containers[0]
- if c.HostConfig == nil {
- t.Fatalf("Expected HostConfig, got none")
+ cont := containers[0]
+ if cont.HostConfig == nil {
+ c.Fatalf("Expected HostConfig, got none")
}
- if !c.HostConfig.PublishAllPorts {
- t.Fatalf("Expected PublishAllPorts, got false")
+ if !cont.HostConfig.PublishAllPorts {
+ c.Fatalf("Expected PublishAllPorts, got false")
}
- logDone("create - hostconfig")
}
-func TestCreateWithPortRange(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCreateWithPortRange(c *check.C) {
runCmd := exec.Command(dockerBinary, "create", "-p", "3300-3303:3300-3303/tcp", "busybox", "echo")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
out, _, err = runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("out should've been a container id: %s, %v", out, err)
+ c.Fatalf("out should've been a container id: %s, %v", out, err)
}
containers := []struct {
@@ -128,47 +124,45 @@ func TestCreateWithPortRange(t *testing.T) {
}
}{}
if err := json.Unmarshal([]byte(out), &containers); err != nil {
- t.Fatalf("Error inspecting the container: %s", err)
+ c.Fatalf("Error inspecting the container: %s", err)
}
if len(containers) != 1 {
- t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+ c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
}
- c := containers[0]
- if c.HostConfig == nil {
- t.Fatalf("Expected HostConfig, got none")
+ cont := containers[0]
+ if cont.HostConfig == nil {
+ c.Fatalf("Expected HostConfig, got none")
}
- if len(c.HostConfig.PortBindings) != 4 {
- t.Fatalf("Expected 4 ports bindings, got %d", len(c.HostConfig.PortBindings))
+ if len(cont.HostConfig.PortBindings) != 4 {
+ c.Fatalf("Expected 4 ports bindings, got %d", len(cont.HostConfig.PortBindings))
}
- for k, v := range c.HostConfig.PortBindings {
+ for k, v := range cont.HostConfig.PortBindings {
if len(v) != 1 {
- t.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v)
+ c.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v)
}
if k.Port() != v[0].HostPort {
- t.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort)
+ c.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort)
}
}
- logDone("create - port range")
}
-func TestCreateWithiLargePortRange(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCreateWithiLargePortRange(c *check.C) {
runCmd := exec.Command(dockerBinary, "create", "-p", "1-65535:1-65535/tcp", "busybox", "echo")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
out, _, err = runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("out should've been a container id: %s, %v", out, err)
+ c.Fatalf("out should've been a container id: %s, %v", out, err)
}
containers := []struct {
@@ -177,130 +171,173 @@ func TestCreateWithiLargePortRange(t *testing.T) {
}
}{}
if err := json.Unmarshal([]byte(out), &containers); err != nil {
- t.Fatalf("Error inspecting the container: %s", err)
+ c.Fatalf("Error inspecting the container: %s", err)
}
if len(containers) != 1 {
- t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+ c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
}
- c := containers[0]
- if c.HostConfig == nil {
- t.Fatalf("Expected HostConfig, got none")
+ cont := containers[0]
+ if cont.HostConfig == nil {
+ c.Fatalf("Expected HostConfig, got none")
}
- if len(c.HostConfig.PortBindings) != 65535 {
- t.Fatalf("Expected 65535 ports bindings, got %d", len(c.HostConfig.PortBindings))
+ if len(cont.HostConfig.PortBindings) != 65535 {
+ c.Fatalf("Expected 65535 ports bindings, got %d", len(cont.HostConfig.PortBindings))
}
- for k, v := range c.HostConfig.PortBindings {
+ for k, v := range cont.HostConfig.PortBindings {
if len(v) != 1 {
- t.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v)
+ c.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v)
}
if k.Port() != v[0].HostPort {
- t.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort)
+ c.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort)
}
}
- logDone("create - large port range")
}
// "test123" should be printed by docker create + start
-func TestCreateEchoStdout(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestCreateEchoStdout(c *check.C) {
runCmd := exec.Command(dockerBinary, "create", "busybox", "echo", "test123")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "start", "-ai", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if out != "test123\n" {
- t.Errorf("container should've printed 'test123', got %q", out)
+ c.Errorf("container should've printed 'test123', got %q", out)
}
- logDone("create - echo test123")
}
-func TestCreateVolumesCreated(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestCreateVolumesCreated(c *check.C) {
+ testRequires(c, SameHostDaemon)
name := "test_create_volume"
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-v", "/foo", "busybox")); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
dir, err := inspectFieldMap(name, "Volumes", "/foo")
if err != nil {
- t.Fatalf("Error getting volume host path: %q", err)
+ c.Fatalf("Error getting volume host path: %q", err)
}
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
- t.Fatalf("Volume was not created")
+ c.Fatalf("Volume was not created")
}
if err != nil {
- t.Fatalf("Error statting volume host path: %q", err)
+ c.Fatalf("Error statting volume host path: %q", err)
}
- logDone("create - volumes are created")
}
-func TestCreateLabels(t *testing.T) {
+func (s *DockerSuite) TestCreateLabels(c *check.C) {
name := "test_create_labels"
expected := map[string]string{"k1": "v1", "k2": "v2"}
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k1=v1", "--label", "k2=v2", "busybox")); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
actual := make(map[string]string)
err := inspectFieldAndMarshall(name, "Config.Labels", &actual)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("Expected %s got %s", expected, actual)
+ c.Fatalf("Expected %s got %s", expected, actual)
}
-
- deleteAllContainers()
-
- logDone("create - labels")
}
-func TestCreateLabelFromImage(t *testing.T) {
+func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) {
imageName := "testcreatebuildlabel"
- defer deleteImages(imageName)
_, err := buildImage(imageName,
`FROM busybox
LABEL k1=v1 k2=v2`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
name := "test_create_labels_from_image"
expected := map[string]string{"k2": "x", "k3": "v3", "k1": "v1"}
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k2=x", "--label", "k3=v3", imageName)); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
actual := make(map[string]string)
err = inspectFieldAndMarshall(name, "Config.Labels", &actual)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("Expected %s got %s", expected, actual)
+ c.Fatalf("Expected %s got %s", expected, actual)
+ }
+}
+
+func (s *DockerSuite) TestCreateHostnameWithNumber(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-h", "web.0", "busybox", "hostname")
+ if strings.TrimSpace(out) != "web.0" {
+ c.Fatalf("hostname not set, expected `web.0`, got: %s", out)
+ }
+}
+
+func (s *DockerSuite) TestCreateRM(c *check.C) {
+ // Test to make sure we can 'rm' a new container that is in
+ // "Created" state, and has ever been run. Test "rm -f" too.
+
+ // create a container
+ createCmd := exec.Command(dockerBinary, "create", "busybox")
+ out, _, err := runCommandWithOutput(createCmd)
+ if err != nil {
+ c.Fatalf("Failed to create container:%s\n%s", out, err)
+ }
+ cID := strings.TrimSpace(out)
+
+ rmCmd := exec.Command(dockerBinary, "rm", cID)
+ out, _, err = runCommandWithOutput(rmCmd)
+ if err != nil {
+ c.Fatalf("Failed to rm container:%s\n%s", out, err)
}
- deleteAllContainers()
+ // Now do it again so we can "rm -f" this time
+ createCmd = exec.Command(dockerBinary, "create", "busybox")
+ out, _, err = runCommandWithOutput(createCmd)
+ if err != nil {
+ c.Fatalf("Failed to create 2nd container:%s\n%s", out, err)
+ }
- logDone("create - labels from image")
+ cID = strings.TrimSpace(out)
+ rmCmd = exec.Command(dockerBinary, "rm", "-f", cID)
+ out, _, err = runCommandWithOutput(rmCmd)
+ if err != nil {
+ c.Fatalf("Failed to rm -f container:%s\n%s", out, err)
+ }
+}
+
+func (s *DockerSuite) TestCreateModeIpcContainer(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ cmd := exec.Command(dockerBinary, "create", "busybox")
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ id := strings.TrimSpace(out)
+
+ cmd = exec.Command(dockerBinary, "create", fmt.Sprintf("--ipc=container:%s", id), "busybox")
+ out, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("Create container with ipc mode container should success with non running container: %s\n%s", out, err)
+ }
}
diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go
index c515a6378..d668781dc 100644
--- a/integration-cli/docker_cli_daemon_test.go
+++ b/integration-cli/docker_cli_daemon_test.go
@@ -6,148 +6,127 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
+ "net"
"os"
"os/exec"
"path/filepath"
+ "regexp"
+ "strconv"
"strings"
- "testing"
"time"
+ "github.com/docker/libnetwork/iptables"
"github.com/docker/libtrust"
+ "github.com/go-check/check"
)
-func TestDaemonRestartWithRunningContainersPorts(t *testing.T) {
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+func (s *DockerDaemonSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
}
- defer d.Stop()
- if out, err := d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil {
- t.Fatalf("Could not run top1: err=%v\n%s", err, out)
+ if out, err := s.d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil {
+ c.Fatalf("Could not run top1: err=%v\n%s", err, out)
}
// --restart=no by default
- if out, err := d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil {
- t.Fatalf("Could not run top2: err=%v\n%s", err, out)
+ if out, err := s.d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil {
+ c.Fatalf("Could not run top2: err=%v\n%s", err, out)
}
testRun := func(m map[string]bool, prefix string) {
var format string
- for c, shouldRun := range m {
- out, err := d.Cmd("ps")
+ for cont, shouldRun := range m {
+ out, err := s.d.Cmd("ps")
if err != nil {
- t.Fatalf("Could not run ps: err=%v\n%q", err, out)
+ c.Fatalf("Could not run ps: err=%v\n%q", err, out)
}
if shouldRun {
format = "%scontainer %q is not running"
} else {
format = "%scontainer %q is running"
}
- if shouldRun != strings.Contains(out, c) {
- t.Fatalf(format, prefix, c)
+ if shouldRun != strings.Contains(out, cont) {
+ c.Fatalf(format, prefix, cont)
}
}
}
testRun(map[string]bool{"top1": true, "top2": true}, "")
- if err := d.Restart(); err != nil {
- t.Fatalf("Could not restart daemon: %v", err)
+ if err := s.d.Restart(); err != nil {
+ c.Fatalf("Could not restart daemon: %v", err)
}
-
testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ")
-
- logDone("daemon - running containers on daemon restart")
}
-func TestDaemonRestartWithVolumesRefs(t *testing.T) {
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
}
- defer d.Stop()
- if out, err := d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil {
+ c.Fatal(err, out)
}
- if err := d.Restart(); err != nil {
- t.Fatal(err)
+ if err := s.d.Restart(); err != nil {
+ c.Fatal(err)
}
- if _, err := d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil {
- t.Fatal(err)
+ if _, err := s.d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil {
+ c.Fatal(err)
}
- if out, err := d.Cmd("rm", "-fv", "volrestarttest2"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("rm", "-fv", "volrestarttest2"); err != nil {
+ c.Fatal(err, out)
}
- v, err := d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1")
+ v, err := s.d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
volumes := make(map[string]string)
json.Unmarshal([]byte(v), &volumes)
if _, err := os.Stat(volumes["/foo"]); err != nil {
- t.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err)
+ c.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err)
}
-
- logDone("daemon - volume refs are restored")
}
-func TestDaemonStartIptablesFalse(t *testing.T) {
- d := NewDaemon(t)
- if err := d.Start("--iptables=false"); err != nil {
- t.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err)
+func (s *DockerDaemonSuite) TestDaemonStartIptablesFalse(c *check.C) {
+ if err := s.d.Start("--iptables=false"); err != nil {
+ c.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err)
}
- d.Stop()
-
- logDone("daemon - started daemon with iptables=false")
}
// Issue #8444: If docker0 bridge is modified (intentionally or unintentionally) and
// no longer has an IP associated, we should gracefully handle that case and associate
// an IP with it rather than fail daemon start
-func TestDaemonStartBridgeWithoutIPAssociation(t *testing.T) {
- d := NewDaemon(t)
+func (s *DockerDaemonSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) {
// rather than depending on brctl commands to verify docker0 is created and up
// let's start the daemon and stop it, and then make a modification to run the
// actual test
- if err := d.Start(); err != nil {
- t.Fatalf("Could not start daemon: %v", err)
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
}
- if err := d.Stop(); err != nil {
- t.Fatalf("Could not stop daemon: %v", err)
+ if err := s.d.Stop(); err != nil {
+ c.Fatalf("Could not stop daemon: %v", err)
}
// now we will remove the ip from docker0 and then try starting the daemon
ipCmd := exec.Command("ip", "addr", "flush", "dev", "docker0")
stdout, stderr, _, err := runCommandWithStdoutStderr(ipCmd)
if err != nil {
- t.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr)
+ c.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr)
}
- if err := d.Start(); err != nil {
+ if err := s.d.Start(); err != nil {
warning := "**WARNING: Docker bridge network in bad state--delete docker0 bridge interface to fix"
- t.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning)
+ c.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning)
}
-
- // cleanup - stop the daemon if test passed
- if err := d.Stop(); err != nil {
- t.Fatalf("Could not stop daemon: %v", err)
- }
-
- logDone("daemon - successful daemon start when bridge has no IP association")
}
-func TestDaemonIptablesClean(t *testing.T) {
- defer deleteAllContainers()
-
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+func (s *DockerDaemonSuite) TestDaemonIptablesClean(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
}
- defer d.Stop()
- if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil {
- t.Fatalf("Could not run top: %s, %v", out, err)
+ if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil {
+ c.Fatalf("Could not run top: %s, %v", out, err)
}
// get output from iptables with container running
@@ -155,42 +134,36 @@ func TestDaemonIptablesClean(t *testing.T) {
ipTablesCmd := exec.Command("iptables", "-nvL")
out, _, err := runCommandWithOutput(ipTablesCmd)
if err != nil {
- t.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
+ c.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
}
if !strings.Contains(out, ipTablesSearchString) {
- t.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out)
+ c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out)
}
- if err := d.Stop(); err != nil {
- t.Fatalf("Could not stop daemon: %v", err)
+ if err := s.d.Stop(); err != nil {
+ c.Fatalf("Could not stop daemon: %v", err)
}
// get output from iptables after restart
ipTablesCmd = exec.Command("iptables", "-nvL")
out, _, err = runCommandWithOutput(ipTablesCmd)
if err != nil {
- t.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
+ c.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
}
if strings.Contains(out, ipTablesSearchString) {
- t.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out)
+ c.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out)
}
-
- logDone("daemon - run,iptables - iptables rules cleaned after daemon restart")
}
-func TestDaemonIptablesCreate(t *testing.T) {
- defer deleteAllContainers()
-
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+func (s *DockerDaemonSuite) TestDaemonIptablesCreate(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
}
- defer d.Stop()
- if out, err := d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil {
- t.Fatalf("Could not run top: %s, %v", out, err)
+ if out, err := s.d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil {
+ c.Fatalf("Could not run top: %s, %v", out, err)
}
// get output from iptables with container running
@@ -198,101 +171,94 @@ func TestDaemonIptablesCreate(t *testing.T) {
ipTablesCmd := exec.Command("iptables", "-nvL")
out, _, err := runCommandWithOutput(ipTablesCmd)
if err != nil {
- t.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
+ c.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
}
if !strings.Contains(out, ipTablesSearchString) {
- t.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out)
+ c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out)
}
- if err := d.Restart(); err != nil {
- t.Fatalf("Could not restart daemon: %v", err)
+ if err := s.d.Restart(); err != nil {
+ c.Fatalf("Could not restart daemon: %v", err)
}
// make sure the container is not running
- runningOut, err := d.Cmd("inspect", "--format='{{.State.Running}}'", "top")
+ runningOut, err := s.d.Cmd("inspect", "--format='{{.State.Running}}'", "top")
if err != nil {
- t.Fatalf("Could not inspect on container: %s, %v", out, err)
+ c.Fatalf("Could not inspect on container: %s, %v", out, err)
}
if strings.TrimSpace(runningOut) != "true" {
- t.Fatalf("Container should have been restarted after daemon restart. Status running should have been true but was: %q", strings.TrimSpace(runningOut))
+ c.Fatalf("Container should have been restarted after daemon restart. Status running should have been true but was: %q", strings.TrimSpace(runningOut))
}
// get output from iptables after restart
ipTablesCmd = exec.Command("iptables", "-nvL")
out, _, err = runCommandWithOutput(ipTablesCmd)
if err != nil {
- t.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
+ c.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
}
if !strings.Contains(out, ipTablesSearchString) {
- t.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out)
+ c.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out)
}
-
- logDone("daemon - run,iptables - iptables rules for always restarted container created after daemon restart")
}
-func TestDaemonLoggingLevel(t *testing.T) {
- d := NewDaemon(t)
+func (s *DockerDaemonSuite) TestDaemonLogLevelWrong(c *check.C) {
+ c.Assert(s.d.Start("--log-level=bogus"), check.NotNil, check.Commentf("Daemon shouldn't start with wrong log level"))
+}
- if err := d.Start("--log-level=bogus"); err == nil {
- t.Fatal("Daemon should not have been able to start")
+func (s *DockerDaemonSuite) TestDaemonLogLevelDebug(c *check.C) {
+ if err := s.d.Start("--log-level=debug"); err != nil {
+ c.Fatal(err)
}
-
- d = NewDaemon(t)
- if err := d.Start("--log-level=debug"); err != nil {
- t.Fatal(err)
- }
- d.Stop()
- content, _ := ioutil.ReadFile(d.logFile.Name())
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
if !strings.Contains(string(content), `level=debug`) {
- t.Fatalf(`Missing level="debug" in log file:\n%s`, string(content))
+ c.Fatalf(`Missing level="debug" in log file:\n%s`, string(content))
}
+}
- d = NewDaemon(t)
- if err := d.Start("--log-level=fatal"); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonLogLevelFatal(c *check.C) {
+ // we creating new daemons to create new logFile
+ if err := s.d.Start("--log-level=fatal"); err != nil {
+ c.Fatal(err)
}
- d.Stop()
- content, _ = ioutil.ReadFile(d.logFile.Name())
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
if strings.Contains(string(content), `level=debug`) {
- t.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content))
+ c.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content))
}
-
- d = NewDaemon(t)
- if err := d.Start("-D"); err != nil {
- t.Fatal(err)
- }
- d.Stop()
- content, _ = ioutil.ReadFile(d.logFile.Name())
- if !strings.Contains(string(content), `level=debug`) {
- t.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content))
- }
-
- d = NewDaemon(t)
- if err := d.Start("--debug"); err != nil {
- t.Fatal(err)
- }
- d.Stop()
- content, _ = ioutil.ReadFile(d.logFile.Name())
- if !strings.Contains(string(content), `level=debug`) {
- t.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content))
- }
-
- d = NewDaemon(t)
- if err := d.Start("--debug", "--log-level=fatal"); err != nil {
- t.Fatal(err)
- }
- d.Stop()
- content, _ = ioutil.ReadFile(d.logFile.Name())
- if !strings.Contains(string(content), `level=debug`) {
- t.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content))
- }
-
- logDone("daemon - Logging Level")
}
-func TestDaemonAllocatesListeningPort(t *testing.T) {
+func (s *DockerDaemonSuite) TestDaemonFlagD(c *check.C) {
+ if err := s.d.Start("-D"); err != nil {
+ c.Fatal(err)
+ }
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
+ if !strings.Contains(string(content), `level=debug`) {
+ c.Fatalf(`Should have level="debug" in log file using -D:\n%s`, string(content))
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonFlagDebug(c *check.C) {
+ if err := s.d.Start("--debug"); err != nil {
+ c.Fatal(err)
+ }
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
+ if !strings.Contains(string(content), `level=debug`) {
+ c.Fatalf(`Should have level="debug" in log file using --debug:\n%s`, string(content))
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonFlagDebugLogLevelFatal(c *check.C) {
+ if err := s.d.Start("--debug", "--log-level=fatal"); err != nil {
+ c.Fatal(err)
+ }
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
+ if !strings.Contains(string(content), `level=debug`) {
+ c.Fatalf(`Should have level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content))
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonAllocatesListeningPort(c *check.C) {
listeningPorts := [][]string{
{"0.0.0.0", "0.0.0.0", "5678"},
{"127.0.0.1", "127.0.0.1", "1234"},
@@ -304,527 +270,898 @@ func TestDaemonAllocatesListeningPort(t *testing.T) {
cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2]))
}
- d := NewDaemon(t)
- if err := d.StartWithBusybox(cmdArgs...); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+ if err := s.d.StartWithBusybox(cmdArgs...); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
}
- defer d.Stop()
for _, hostDirective := range listeningPorts {
- output, err := d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true")
+ output, err := s.d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true")
if err == nil {
- t.Fatalf("Container should not start, expected port already allocated error: %q", output)
+ c.Fatalf("Container should not start, expected port already allocated error: %q", output)
} else if !strings.Contains(output, "port is already allocated") {
- t.Fatalf("Expected port is already allocated error: %q", output)
+ c.Fatalf("Expected port is already allocated error: %q", output)
}
}
-
- logDone("daemon - daemon listening port is allocated")
}
-// #9629
-func TestDaemonVolumesBindsRefs(t *testing.T) {
- d := NewDaemon(t)
-
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
- }
-
- tmp, err := ioutil.TempDir(os.TempDir(), "")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmp)
-
- if err := ioutil.WriteFile(tmp+"/test", []byte("testing"), 0655); err != nil {
- t.Fatal(err)
- }
-
- if out, err := d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil {
- t.Fatal(err, out)
- }
-
- if err := d.Restart(); err != nil {
- t.Fatal(err)
- }
-
- if out, err := d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil {
- t.Fatal(err, out)
- }
-
- logDone("daemon - bind refs in data-containers survive daemon restart")
-}
-
-func TestDaemonKeyGeneration(t *testing.T) {
+func (s *DockerDaemonSuite) TestDaemonKeyGeneration(c *check.C) {
// TODO: skip or update for Windows daemon
os.Remove("/etc/docker/key.json")
- d := NewDaemon(t)
- if err := d.Start(); err != nil {
- t.Fatalf("Could not start daemon: %v", err)
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
}
- d.Stop()
+ s.d.Stop()
k, err := libtrust.LoadKeyFile("/etc/docker/key.json")
if err != nil {
- t.Fatalf("Error opening key file")
+ c.Fatalf("Error opening key file")
}
kid := k.KeyID()
// Test Key ID is a valid fingerprint (e.g. QQXN:JY5W:TBXI:MK3X:GX6P:PD5D:F56N:NHCS:LVRZ:JA46:R24J:XEFF)
if len(kid) != 59 {
- t.Fatalf("Bad key ID: %s", kid)
+ c.Fatalf("Bad key ID: %s", kid)
}
-
- logDone("daemon - key generation")
}
-func TestDaemonKeyMigration(t *testing.T) {
+func (s *DockerDaemonSuite) TestDaemonKeyMigration(c *check.C) {
// TODO: skip or update for Windows daemon
os.Remove("/etc/docker/key.json")
k1, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
- t.Fatalf("Error generating private key: %s", err)
+ c.Fatalf("Error generating private key: %s", err)
}
if err := os.MkdirAll(filepath.Join(os.Getenv("HOME"), ".docker"), 0755); err != nil {
- t.Fatalf("Error creating .docker directory: %s", err)
+ c.Fatalf("Error creating .docker directory: %s", err)
}
if err := libtrust.SaveKey(filepath.Join(os.Getenv("HOME"), ".docker", "key.json"), k1); err != nil {
- t.Fatalf("Error saving private key: %s", err)
+ c.Fatalf("Error saving private key: %s", err)
}
- d := NewDaemon(t)
- if err := d.Start(); err != nil {
- t.Fatalf("Could not start daemon: %v", err)
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
}
- d.Stop()
+ s.d.Stop()
k2, err := libtrust.LoadKeyFile("/etc/docker/key.json")
if err != nil {
- t.Fatalf("Error opening key file")
+ c.Fatalf("Error opening key file")
}
if k1.KeyID() != k2.KeyID() {
- t.Fatalf("Key not migrated")
+ c.Fatalf("Key not migrated")
}
-
- logDone("daemon - key migration")
-}
-
-// Simulate an older daemon (pre 1.3) coming up with volumes specified in containers
-// without corresponding volume json
-func TestDaemonUpgradeWithVolumes(t *testing.T) {
- d := NewDaemon(t)
-
- graphDir := filepath.Join(os.TempDir(), "docker-test")
- defer os.RemoveAll(graphDir)
- if err := d.StartWithBusybox("-g", graphDir); err != nil {
- t.Fatal(err)
- }
-
- tmpDir := filepath.Join(os.TempDir(), "test")
- defer os.RemoveAll(tmpDir)
-
- if out, err := d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil {
- t.Fatal(err, out)
- }
-
- if err := d.Stop(); err != nil {
- t.Fatal(err)
- }
-
- // Remove this since we're expecting the daemon to re-create it too
- if err := os.RemoveAll(tmpDir); err != nil {
- t.Fatal(err)
- }
-
- configDir := filepath.Join(graphDir, "volumes")
-
- if err := os.RemoveAll(configDir); err != nil {
- t.Fatal(err)
- }
-
- if err := d.Start("-g", graphDir); err != nil {
- t.Fatal(err)
- }
-
- if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
- t.Fatalf("expected volume path %s to exist but it does not", tmpDir)
- }
-
- dir, err := ioutil.ReadDir(configDir)
- if err != nil {
- t.Fatal(err)
- }
- if len(dir) == 0 {
- t.Fatalf("expected volumes config dir to contain data for new volume")
- }
-
- // Now with just removing the volume config and not the volume data
- if err := d.Stop(); err != nil {
- t.Fatal(err)
- }
-
- if err := os.RemoveAll(configDir); err != nil {
- t.Fatal(err)
- }
-
- if err := d.Start("-g", graphDir); err != nil {
- t.Fatal(err)
- }
-
- dir, err = ioutil.ReadDir(configDir)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(dir) == 0 {
- t.Fatalf("expected volumes config dir to contain data for new volume")
- }
-
- logDone("daemon - volumes from old(pre 1.3) daemon work")
}
// GH#11320 - verify that the daemon exits on failure properly
// Note that this explicitly tests the conflict of {-b,--bridge} and {--bip} options as the means
// to get a daemon init failure; no other tests for -b/--bip conflict are therefore required
-func TestDaemonExitOnFailure(t *testing.T) {
- d := NewDaemon(t)
- defer d.Stop()
-
+func (s *DockerDaemonSuite) TestDaemonExitOnFailure(c *check.C) {
//attempt to start daemon with incorrect flags (we know -b and --bip conflict)
- if err := d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil {
+ if err := s.d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil {
//verify we got the right error
if !strings.Contains(err.Error(), "Daemon exited and never started") {
- t.Fatalf("Expected daemon not to start, got %v", err)
+ c.Fatalf("Expected daemon not to start, got %v", err)
}
// look in the log and make sure we got the message that daemon is shutting down
- runCmd := exec.Command("grep", "Shutting down daemon due to", d.LogfileName())
+ runCmd := exec.Command("grep", "Error starting daemon", s.d.LogfileName())
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatalf("Expected 'shutting down daemon due to error' message; but doesn't exist in log: %q, err: %v", out, err)
+ c.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err)
}
} else {
//if we didn't get an error and the daemon is running, this is a failure
- d.Stop()
- t.Fatal("Conflicting options should cause the daemon to error out with a failure")
+ c.Fatal("Conflicting options should cause the daemon to error out with a failure")
}
-
- logDone("daemon - verify no start on daemon init errors")
}
-func TestDaemonUlimitDefaults(t *testing.T) {
- testRequires(t, NativeExecDriver)
- d := NewDaemon(t)
+func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) {
+ d := s.d
+ err := d.Start("--bridge", "nosuchbridge")
+ c.Assert(err, check.NotNil, check.Commentf("--bridge option with an invalid bridge should cause the daemon to fail"))
+ defer d.Restart()
- if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil {
- t.Fatal(err)
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+ _, bridgeIPNet, _ := net.ParseCIDR(bridgeIp)
+
+ out, err := createInterface(c, "bridge", bridgeName, bridgeIp)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+ defer deleteInterface(c, bridgeName)
+
+ err = d.StartWithBusybox("--bridge", bridgeName)
+ c.Assert(err, check.IsNil)
+
+ ipTablesSearchString := bridgeIPNet.String()
+ ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL")
+ out, _, err = runCommandWithOutput(ipTablesCmd)
+ c.Assert(err, check.IsNil)
+
+ c.Assert(strings.Contains(out, ipTablesSearchString), check.Equals, true,
+ check.Commentf("iptables output should have contained %q, but was %q",
+ ipTablesSearchString, out))
+
+ _, err = d.Cmd("run", "-d", "--name", "ExtContainer", "busybox", "top")
+ c.Assert(err, check.IsNil)
+
+ containerIp := d.findContainerIP("ExtContainer")
+ ip := net.ParseIP(containerIp)
+ c.Assert(bridgeIPNet.Contains(ip), check.Equals, true,
+ check.Commentf("Container IP-Address must be in the same subnet range : %s",
+ containerIp))
+}
+
+func createInterface(c *check.C, ifType string, ifName string, ipNet string) (string, error) {
+ args := []string{"link", "add", "name", ifName, "type", ifType}
+ ipLinkCmd := exec.Command("ip", args...)
+ out, _, err := runCommandWithOutput(ipLinkCmd)
+ if err != nil {
+ return out, err
}
- out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)")
+ ifCfgCmd := exec.Command("ifconfig", ifName, ipNet, "up")
+ out, _, err = runCommandWithOutput(ifCfgCmd)
+ return out, err
+}
+
+func deleteInterface(c *check.C, ifName string) {
+ ifCmd := exec.Command("ip", "link", "delete", ifName)
+ out, _, err := runCommandWithOutput(ifCmd)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+
+ flushCmd := exec.Command("iptables", "-t", "nat", "--flush")
+ out, _, err = runCommandWithOutput(flushCmd)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+
+ flushCmd = exec.Command("iptables", "--flush")
+ out, _, err = runCommandWithOutput(flushCmd)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+}
+
+func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) {
+ // TestDaemonBridgeIP Steps
+ // 1. Delete the existing docker0 Bridge
+ // 2. Set --bip daemon configuration and start the new Docker Daemon
+ // 3. Check if the bip config has taken effect using ifconfig and iptables commands
+ // 4. Launch a Container and make sure the IP-Address is in the expected subnet
+ // 5. Delete the docker0 Bridge
+ // 6. Restart the Docker Daemon (via defered action)
+ // This Restart takes care of bringing docker0 interface back to auto-assigned IP
+
+ defaultNetworkBridge := "docker0"
+ deleteInterface(c, defaultNetworkBridge)
+
+ d := s.d
+
+ bridgeIp := "192.169.1.1/24"
+ ip, bridgeIPNet, _ := net.ParseCIDR(bridgeIp)
+
+ err := d.StartWithBusybox("--bip", bridgeIp)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ ifconfigSearchString := ip.String()
+ ifconfigCmd := exec.Command("ifconfig", defaultNetworkBridge)
+ out, _, _, err := runCommandWithStdoutStderr(ifconfigCmd)
+ c.Assert(err, check.IsNil)
+
+ c.Assert(strings.Contains(out, ifconfigSearchString), check.Equals, true,
+ check.Commentf("ifconfig output should have contained %q, but was %q",
+ ifconfigSearchString, out))
+
+ ipTablesSearchString := bridgeIPNet.String()
+ ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL")
+ out, _, err = runCommandWithOutput(ipTablesCmd)
+ c.Assert(err, check.IsNil)
+
+ c.Assert(strings.Contains(out, ipTablesSearchString), check.Equals, true,
+ check.Commentf("iptables output should have contained %q, but was %q",
+ ipTablesSearchString, out))
+
+ out, err = d.Cmd("run", "-d", "--name", "test", "busybox", "top")
+ c.Assert(err, check.IsNil)
+
+ containerIp := d.findContainerIP("test")
+ ip = net.ParseIP(containerIp)
+ c.Assert(bridgeIPNet.Contains(ip), check.Equals, true,
+ check.Commentf("Container IP-Address must be in the same subnet range : %s",
+ containerIp))
+ deleteInterface(c, defaultNetworkBridge)
+}
+
+func (s *DockerDaemonSuite) TestDaemonRestartWithBridgeIPChange(c *check.C) {
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
+ }
+ defer s.d.Restart()
+ if err := s.d.Stop(); err != nil {
+ c.Fatalf("Could not stop daemon: %v", err)
+ }
+
+ // now we will change the docker0's IP and then try starting the daemon
+ bridgeIP := "192.169.100.1/24"
+ _, bridgeIPNet, _ := net.ParseCIDR(bridgeIP)
+
+ ipCmd := exec.Command("ifconfig", "docker0", bridgeIP)
+ stdout, stderr, _, err := runCommandWithStdoutStderr(ipCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatalf("failed to change docker0's IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr)
+ }
+
+ if err := s.d.Start("--bip", bridgeIP); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
+ }
+
+ //check if the iptables contains new bridgeIP MASQUERADE rule
+ ipTablesSearchString := bridgeIPNet.String()
+ ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL")
+ out, _, err := runCommandWithOutput(ipTablesCmd)
+ if err != nil {
+ c.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
+ }
+ if !strings.Contains(out, ipTablesSearchString) {
+ c.Fatalf("iptables output should have contained new MASQUERADE rule with IP %q, but was %q", ipTablesSearchString, out)
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) {
+ d := s.d
+
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ out, err := createInterface(c, "bridge", bridgeName, bridgeIp)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+ defer deleteInterface(c, bridgeName)
+
+ args := []string{"--bridge", bridgeName, "--fixed-cidr", "192.169.1.0/30"}
+ err = d.StartWithBusybox(args...)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ for i := 0; i < 4; i++ {
+ cName := "Container" + strconv.Itoa(i)
+ out, err := d.Cmd("run", "-d", "--name", cName, "busybox", "top")
+ if err != nil {
+ c.Assert(strings.Contains(out, "no available ip addresses"), check.Equals, true,
+ check.Commentf("Could not run a Container : %s %s", err.Error(), out))
+ }
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) {
+ d := s.d
+
+ ipStr := "192.170.1.1/24"
+ ip, _, _ := net.ParseCIDR(ipStr)
+ args := []string{"--ip", ip.String()}
+ err := d.StartWithBusybox(args...)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ out, err := d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top")
+ c.Assert(err, check.NotNil,
+ check.Commentf("Running a container must fail with an invalid --ip option"))
+ c.Assert(strings.Contains(out, "Error starting userland proxy"), check.Equals, true)
+
+ ifName := "dummy"
+ out, err = createInterface(c, "dummy", ifName, ipStr)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+ defer deleteInterface(c, ifName)
+
+ _, err = d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top")
+ c.Assert(err, check.IsNil)
+
+ ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL")
+ out, _, err = runCommandWithOutput(ipTablesCmd)
+ c.Assert(err, check.IsNil)
+
+ regex := fmt.Sprintf("DNAT.*%s.*dpt:8000", ip.String())
+ matched, _ := regexp.MatchString(regex, out)
+ c.Assert(matched, check.Equals, true,
+ check.Commentf("iptables output should have contained %q, but was %q", regex, out))
+}
+
+func (s *DockerDaemonSuite) TestDaemonICCPing(c *check.C) {
+ d := s.d
+
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ out, err := createInterface(c, "bridge", bridgeName, bridgeIp)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+ defer deleteInterface(c, bridgeName)
+
+ args := []string{"--bridge", bridgeName, "--icc=false"}
+ err = d.StartWithBusybox(args...)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ ipTablesCmd := exec.Command("iptables", "-nvL", "FORWARD")
+ out, _, err = runCommandWithOutput(ipTablesCmd)
+ c.Assert(err, check.IsNil)
+
+ regex := fmt.Sprintf("DROP.*all.*%s.*%s", bridgeName, bridgeName)
+ matched, _ := regexp.MatchString(regex, out)
+ c.Assert(matched, check.Equals, true,
+ check.Commentf("iptables output should have contained %q, but was %q", regex, out))
+
+ // Pinging another container must fail with --icc=false
+ pingContainers(c, d, true)
+
+ ipStr := "192.171.1.1/24"
+ ip, _, _ := net.ParseCIDR(ipStr)
+ ifName := "icc-dummy"
+
+ createInterface(c, "dummy", ifName, ipStr)
+
+ // But, Pinging external or a Host interface must succeed
+ pingCmd := fmt.Sprintf("ping -c 1 %s -W 1", ip.String())
+ runArgs := []string{"--rm", "busybox", "sh", "-c", pingCmd}
+ _, err = d.Cmd("run", runArgs...)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) {
+ d := s.d
+
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ out, err := createInterface(c, "bridge", bridgeName, bridgeIp)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+ defer deleteInterface(c, bridgeName)
+
+ args := []string{"--bridge", bridgeName, "--icc=false"}
+ err = d.StartWithBusybox(args...)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ ipTablesCmd := exec.Command("iptables", "-nvL", "FORWARD")
+ out, _, err = runCommandWithOutput(ipTablesCmd)
+ c.Assert(err, check.IsNil)
+
+ regex := fmt.Sprintf("DROP.*all.*%s.*%s", bridgeName, bridgeName)
+ matched, _ := regexp.MatchString(regex, out)
+ c.Assert(matched, check.Equals, true,
+ check.Commentf("iptables output should have contained %q, but was %q", regex, out))
+
+ out, err = d.Cmd("run", "-d", "--expose", "4567", "--name", "icc1", "busybox", "nc", "-l", "-p", "4567")
+ c.Assert(err, check.IsNil, check.Commentf(out))
+
+ out, err = d.Cmd("run", "--link", "icc1:icc1", "busybox", "nc", "icc1", "4567")
+ c.Assert(err, check.IsNil, check.Commentf(out))
+}
+
+func (s *DockerDaemonSuite) TestDaemonLinksIpTablesRulesWhenLinkAndUnlink(c *check.C) {
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ out, err := createInterface(c, "bridge", bridgeName, bridgeIp)
+ c.Assert(err, check.IsNil, check.Commentf(out))
+ defer deleteInterface(c, bridgeName)
+
+ args := []string{"--bridge", bridgeName, "--icc=false"}
+ err = s.d.StartWithBusybox(args...)
+ c.Assert(err, check.IsNil)
+ defer s.d.Restart()
+
+ _, err = s.d.Cmd("run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top")
+ c.Assert(err, check.IsNil)
+ _, err = s.d.Cmd("run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top")
+ c.Assert(err, check.IsNil)
+
+ childIP := s.d.findContainerIP("child")
+ parentIP := s.d.findContainerIP("parent")
+
+ sourceRule := []string{"-i", bridgeName, "-o", bridgeName, "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"}
+ destinationRule := []string{"-i", bridgeName, "-o", bridgeName, "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"}
+ if !iptables.Exists("filter", "DOCKER", sourceRule...) || !iptables.Exists("filter", "DOCKER", destinationRule...) {
+ c.Fatal("Iptables rules not found")
+ }
+
+ s.d.Cmd("rm", "--link", "parent/http")
+ if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) {
+ c.Fatal("Iptables rules should be removed when unlink")
+ }
+
+ s.d.Cmd("kill", "child")
+ s.d.Cmd("kill", "parent")
+}
+
+func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) {
+ testRequires(c, NativeExecDriver)
+
+ if err := s.d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := s.d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)")
+ if err != nil {
+ c.Fatal(out, err)
}
outArr := strings.Split(out, "\n")
if len(outArr) < 2 {
- t.Fatal("got unexpected output: %s", out)
+ c.Fatalf("got unexpected output: %s", out)
}
nofile := strings.TrimSpace(outArr[0])
nproc := strings.TrimSpace(outArr[1])
if nofile != "42" {
- t.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile)
+ c.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile)
}
if nproc != "2048" {
- t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
+ c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
}
// Now restart daemon with a new default
- if err := d.Restart("--default-ulimit", "nofile=43"); err != nil {
- t.Fatal(err)
+ if err := s.d.Restart("--default-ulimit", "nofile=43"); err != nil {
+ c.Fatal(err)
}
- out, err = d.Cmd("start", "-a", "test")
+ out, err = s.d.Cmd("start", "-a", "test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
outArr = strings.Split(out, "\n")
if len(outArr) < 2 {
- t.Fatal("got unexpected output: %s", out)
+ c.Fatalf("got unexpected output: %s", out)
}
nofile = strings.TrimSpace(outArr[0])
nproc = strings.TrimSpace(outArr[1])
if nofile != "43" {
- t.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile)
+ c.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile)
}
if nproc != "2048" {
- t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
+ c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
}
-
- logDone("daemon - default ulimits are applied")
}
// #11315
-func TestDaemonRestartRenameContainer(t *testing.T) {
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonRestartRenameContainer(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
}
- if out, err := d.Cmd("run", "--name=test", "busybox"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("run", "--name=test", "busybox"); err != nil {
+ c.Fatal(err, out)
}
- if out, err := d.Cmd("rename", "test", "test2"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("rename", "test", "test2"); err != nil {
+ c.Fatal(err, out)
}
- if err := d.Restart(); err != nil {
- t.Fatal(err)
+ if err := s.d.Restart(); err != nil {
+ c.Fatal(err)
}
- if out, err := d.Cmd("start", "test2"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("start", "test2"); err != nil {
+ c.Fatal(err, out)
}
-
- logDone("daemon - rename persists through daemon restart")
}
-func TestDaemonLoggingDriverDefault(t *testing.T) {
- d := NewDaemon(t)
-
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefault(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
}
- defer d.Stop()
- out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
+ out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline")
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id := strings.TrimSpace(out)
- if out, err := d.Cmd("wait", id); err != nil {
- t.Fatal(out, err)
+ if out, err := s.d.Cmd("wait", id); err != nil {
+ c.Fatal(out, err)
}
- logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+ logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log")
if _, err := os.Stat(logPath); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
f, err := os.Open(logPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var res struct {
- Log string `json:log`
- Stream string `json:stream`
- Time time.Time `json:time`
+ Log string `json:"log"`
+ Stream string `json:"stream"`
+ Time time.Time `json:"time"`
}
if err := json.NewDecoder(f).Decode(&res); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res.Log != "testline\n" {
- t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
+ c.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
}
if res.Stream != "stdout" {
- t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
+ c.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
}
if !time.Now().After(res.Time) {
- t.Fatalf("Log time %v in future", res.Time)
+ c.Fatalf("Log time %v in future", res.Time)
}
- logDone("daemon - default 'json-file' logging driver")
}
-func TestDaemonLoggingDriverDefaultOverride(t *testing.T) {
- d := NewDaemon(t)
-
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
}
- defer d.Stop()
- out, err := d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline")
+ out, err := s.d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline")
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id := strings.TrimSpace(out)
- if out, err := d.Cmd("wait", id); err != nil {
- t.Fatal(out, err)
+ if out, err := s.d.Cmd("wait", id); err != nil {
+ c.Fatal(out, err)
}
- logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+ logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log")
if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) {
- t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
+ c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
}
- logDone("daemon - default logging driver override in run")
}
-func TestDaemonLoggingDriverNone(t *testing.T) {
- d := NewDaemon(t)
-
- if err := d.StartWithBusybox("--log-driver=none"); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonLoggingDriverNone(c *check.C) {
+ if err := s.d.StartWithBusybox("--log-driver=none"); err != nil {
+ c.Fatal(err)
}
- defer d.Stop()
- out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
+ out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline")
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id := strings.TrimSpace(out)
- if out, err := d.Cmd("wait", id); err != nil {
- t.Fatal(out, err)
+ if out, err := s.d.Cmd("wait", id); err != nil {
+ c.Fatal(out, err)
}
- logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+ logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log")
if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) {
- t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
+ c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
}
- logDone("daemon - 'none' logging driver")
}
-func TestDaemonLoggingDriverNoneOverride(t *testing.T) {
- d := NewDaemon(t)
-
- if err := d.StartWithBusybox("--log-driver=none"); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) {
+ if err := s.d.StartWithBusybox("--log-driver=none"); err != nil {
+ c.Fatal(err)
}
- defer d.Stop()
- out, err := d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline")
+ out, err := s.d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline")
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id := strings.TrimSpace(out)
- if out, err := d.Cmd("wait", id); err != nil {
- t.Fatal(out, err)
+ if out, err := s.d.Cmd("wait", id); err != nil {
+ c.Fatal(out, err)
}
- logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+ logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log")
if _, err := os.Stat(logPath); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
f, err := os.Open(logPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var res struct {
- Log string `json:log`
- Stream string `json:stream`
- Time time.Time `json:time`
+ Log string `json:"log"`
+ Stream string `json:"stream"`
+ Time time.Time `json:"time"`
}
if err := json.NewDecoder(f).Decode(&res); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if res.Log != "testline\n" {
- t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
+ c.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
}
if res.Stream != "stdout" {
- t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
+ c.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
}
if !time.Now().After(res.Time) {
- t.Fatalf("Log time %v in future", res.Time)
+ c.Fatalf("Log time %v in future", res.Time)
}
- logDone("daemon - 'none' logging driver override in run")
}
-func TestDaemonLoggingDriverNoneLogsError(t *testing.T) {
- d := NewDaemon(t)
-
- if err := d.StartWithBusybox("--log-driver=none"); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) {
+ if err := s.d.StartWithBusybox("--log-driver=none"); err != nil {
+ c.Fatal(err)
}
- defer d.Stop()
- out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
+ out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline")
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id := strings.TrimSpace(out)
- out, err = d.Cmd("logs", id)
+ out, err = s.d.Cmd("logs", id)
if err == nil {
- t.Fatalf("Logs should fail with \"none\" driver")
+ c.Fatalf("Logs should fail with \"none\" driver")
}
- if !strings.Contains(out, `\"logs\" command is supported only for \"json-file\" logging driver`) {
- t.Fatalf("There should be error about non-json-file driver, got %s", out)
+ if !strings.Contains(out, `"logs" command is supported only for "json-file" logging driver`) {
+ c.Fatalf("There should be error about non-json-file driver, got: %s", out)
}
- logDone("daemon - logs not available for non-json-file drivers")
}
-func TestDaemonDots(t *testing.T) {
- defer deleteAllContainers()
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonDots(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
}
// Now create 4 containers
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ if _, err := s.d.Cmd("create", "busybox"); err != nil {
+ c.Fatalf("Error creating container: %q", err)
}
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ if _, err := s.d.Cmd("create", "busybox"); err != nil {
+ c.Fatalf("Error creating container: %q", err)
}
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ if _, err := s.d.Cmd("create", "busybox"); err != nil {
+ c.Fatalf("Error creating container: %q", err)
}
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ if _, err := s.d.Cmd("create", "busybox"); err != nil {
+ c.Fatalf("Error creating container: %q", err)
}
- d.Stop()
+ s.d.Stop()
- d.Start("--log-level=debug")
- d.Stop()
- content, _ := ioutil.ReadFile(d.logFile.Name())
+ s.d.Start("--log-level=debug")
+ s.d.Stop()
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
if strings.Contains(string(content), "....") {
- t.Fatalf("Debug level should not have ....\n%s", string(content))
+ c.Fatalf("Debug level should not have ....\n%s", string(content))
}
- d.Start("--log-level=error")
- d.Stop()
- content, _ = ioutil.ReadFile(d.logFile.Name())
+ s.d.Start("--log-level=error")
+ s.d.Stop()
+ content, _ = ioutil.ReadFile(s.d.logFile.Name())
if strings.Contains(string(content), "....") {
- t.Fatalf("Error level should not have ....\n%s", string(content))
+ c.Fatalf("Error level should not have ....\n%s", string(content))
}
- d.Start("--log-level=info")
- d.Stop()
- content, _ = ioutil.ReadFile(d.logFile.Name())
+ s.d.Start("--log-level=info")
+ s.d.Stop()
+ content, _ = ioutil.ReadFile(s.d.logFile.Name())
if !strings.Contains(string(content), "....") {
- t.Fatalf("Info level should have ....\n%s", string(content))
+ c.Fatalf("Info level should have ....\n%s", string(content))
}
-
- logDone("daemon - test dots on INFO")
}
-func TestDaemonUnixSockCleanedUp(t *testing.T) {
- d := NewDaemon(t)
+func (s *DockerDaemonSuite) TestDaemonUnixSockCleanedUp(c *check.C) {
dir, err := ioutil.TempDir("", "socket-cleanup-test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(dir)
sockPath := filepath.Join(dir, "docker.sock")
- if err := d.Start("--host", "unix://"+sockPath); err != nil {
- t.Fatal(err)
+ if err := s.d.Start("--host", "unix://"+sockPath); err != nil {
+ c.Fatal(err)
}
if _, err := os.Stat(sockPath); err != nil {
- t.Fatal("socket does not exist")
+ c.Fatal("socket does not exist")
}
- if err := d.Stop(); err != nil {
- t.Fatal(err)
+ if err := s.d.Stop(); err != nil {
+ c.Fatal(err)
}
if _, err := os.Stat(sockPath); err == nil || !os.IsNotExist(err) {
- t.Fatal("unix socket is not cleaned up")
+ c.Fatal("unix socket is not cleaned up")
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonWithWrongkey(c *check.C) {
+ type Config struct {
+ Crv string `json:"crv"`
+ D string `json:"d"`
+ Kid string `json:"kid"`
+ Kty string `json:"kty"`
+ X string `json:"x"`
+ Y string `json:"y"`
}
- logDone("daemon - unix socket is cleaned up")
+ os.Remove("/etc/docker/key.json")
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Failed to start daemon: %v", err)
+ }
+
+ if err := s.d.Stop(); err != nil {
+ c.Fatalf("Could not stop daemon: %v", err)
+ }
+
+ config := &Config{}
+ bytes, err := ioutil.ReadFile("/etc/docker/key.json")
+ if err != nil {
+ c.Fatalf("Error reading key.json file: %s", err)
+ }
+
+ // byte[] to Data-Struct
+ if err := json.Unmarshal(bytes, &config); err != nil {
+ c.Fatalf("Error Unmarshal: %s", err)
+ }
+
+ //replace config.Kid with the fake value
+ config.Kid = "VSAJ:FUYR:X3H2:B2VZ:KZ6U:CJD5:K7BX:ZXHY:UZXT:P4FT:MJWG:HRJ4"
+
+ // NEW Data-Struct to byte[]
+ newBytes, err := json.Marshal(&config)
+ if err != nil {
+ c.Fatalf("Error Marshal: %s", err)
+ }
+
+ // write back
+ if err := ioutil.WriteFile("/etc/docker/key.json", newBytes, 0400); err != nil {
+ c.Fatalf("Error ioutil.WriteFile: %s", err)
+ }
+
+ defer os.Remove("/etc/docker/key.json")
+
+ if err := s.d.Start(); err == nil {
+ c.Fatalf("It should not be successful to start daemon with wrong key: %v", err)
+ }
+
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
+
+ if !strings.Contains(string(content), "Public Key ID does not match") {
+ c.Fatal("Missing KeyID message from daemon logs")
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonRestartKillWait(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
+ }
+
+ out, err := s.d.Cmd("run", "-id", "busybox", "/bin/cat")
+ if err != nil {
+ c.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out)
+ }
+ containerID := strings.TrimSpace(out)
+
+ if out, err := s.d.Cmd("kill", containerID); err != nil {
+ c.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out)
+ }
+
+ if err := s.d.Restart(); err != nil {
+ c.Fatalf("Could not restart daemon: %v", err)
+ }
+
+ errchan := make(chan error)
+ go func() {
+ if out, err := s.d.Cmd("wait", containerID); err != nil {
+ errchan <- fmt.Errorf("%v:\n%s", err, out)
+ }
+ close(errchan)
+ }()
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("Waiting on a stopped (killed) container timed out")
+ case err := <-errchan:
+ if err != nil {
+ c.Fatal(err)
+ }
+ }
+}
+
+// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint
+func (s *DockerDaemonSuite) TestHttpsInfo(c *check.C) {
+ const (
+ testDaemonHttpsAddr = "localhost:4271"
+ )
+
+ if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem",
+ "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
+ }
+
+ //force tcp protocol
+ host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr)
+ daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-cert.pem", "--tlskey", "fixtures/https/client-key.pem"}
+ out, err := s.d.CmdWithArgs(daemonArgs, "info")
+ if err != nil {
+ c.Fatalf("Error Occurred: %s and output: %s", err, out)
+ }
+}
+
+// TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint
+// by using a rogue client certificate and checks that it fails with the expected error.
+func (s *DockerDaemonSuite) TestHttpsInfoRogueCert(c *check.C) {
+ const (
+ errBadCertificate = "remote error: bad certificate"
+ testDaemonHttpsAddr = "localhost:4271"
+ )
+ if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem",
+ "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
+ }
+
+ //force tcp protocol
+ host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr)
+ daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"}
+ out, err := s.d.CmdWithArgs(daemonArgs, "info")
+ if err == nil || !strings.Contains(out, errBadCertificate) {
+ c.Fatalf("Expected err: %s, got instead: %s and output: %s", errBadCertificate, err, out)
+ }
+}
+
+// TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint
+// which provides a rogue server certificate and checks that it fails with the expected error
+func (s *DockerDaemonSuite) TestHttpsInfoRogueServerCert(c *check.C) {
+ const (
+ errCaUnknown = "x509: certificate signed by unknown authority"
+ testDaemonRogueHttpsAddr = "localhost:4272"
+ )
+ if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-rogue-cert.pem",
+ "--tlskey", "fixtures/https/server-rogue-key.pem", "-H", testDaemonRogueHttpsAddr); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
+ }
+
+ //force tcp protocol
+ host := fmt.Sprintf("tcp://%s", testDaemonRogueHttpsAddr)
+ daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"}
+ out, err := s.d.CmdWithArgs(daemonArgs, "info")
+ if err == nil || !strings.Contains(out, errCaUnknown) {
+ c.Fatalf("Expected err: %s, got instead: %s and output: %s", errCaUnknown, err, out)
+ }
+}
+
+func pingContainers(c *check.C, d *Daemon, expectFailure bool) {
+ var dargs []string
+ if d != nil {
+ dargs = []string{"--host", d.sock()}
+ }
+
+ args := append(dargs, "run", "-d", "--name", "container1", "busybox", "top")
+ _, err := runCommand(exec.Command(dockerBinary, args...))
+ c.Assert(err, check.IsNil)
+
+ args = append(dargs, "run", "--rm", "--link", "container1:alias1", "busybox", "sh", "-c")
+ pingCmd := "ping -c 1 %s -W 1"
+ args = append(args, fmt.Sprintf(pingCmd, "alias1"))
+ _, err = runCommand(exec.Command(dockerBinary, args...))
+
+ if expectFailure {
+ c.Assert(err, check.NotNil)
+ } else {
+ c.Assert(err, check.IsNil)
+ }
+
+ args = append(dargs, "rm", "-f", "container1")
+ runCommand(exec.Command(dockerBinary, args...))
+}
+
+func (s *DockerDaemonSuite) TestDaemonRestartWithSocketAsVolume(c *check.C) {
+ c.Assert(s.d.StartWithBusybox(), check.IsNil)
+
+ socket := filepath.Join(s.d.folder, "docker.sock")
+
+ out, err := s.d.Cmd("run", "-d", "-v", socket+":/sock", "busybox")
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
+ c.Assert(s.d.Restart(), check.IsNil)
+}
+
+func (s *DockerDaemonSuite) TestCleanupMountsAfterCrash(c *check.C) {
+ c.Assert(s.d.StartWithBusybox(), check.IsNil)
+
+ out, err := s.d.Cmd("run", "-d", "busybox", "top")
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
+ id := strings.TrimSpace(out)
+ c.Assert(s.d.cmd.Process.Signal(os.Kill), check.IsNil)
+ c.Assert(s.d.Start(), check.IsNil)
+ mountOut, err := exec.Command("mount").CombinedOutput()
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", mountOut))
+ c.Assert(strings.Contains(string(mountOut), id), check.Equals, false, check.Commentf("Something mounted from older daemon start: %s", mountOut))
+}
+
+func (s *DockerDaemonSuite) TestRunContainerWithBridgeNone(c *check.C) {
+ c.Assert(s.d.StartWithBusybox("-b", "none"), check.IsNil)
+
+ out, err := s.d.Cmd("run", "--rm", "busybox", "ip", "l")
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
+ c.Assert(strings.Contains(out, "eth0"), check.Equals, false,
+ check.Commentf("There shouldn't be eth0 in container when network is disabled: %s", out))
}
diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go
index 36881a572..725b76286 100644
--- a/integration-cli/docker_cli_diff_test.go
+++ b/integration-cli/docker_cli_diff_test.go
@@ -3,24 +3,25 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
// ensure that an added file shows up in docker diff
-func TestDiffFilenameShownInOutput(t *testing.T) {
+func (s *DockerSuite) TestDiffFilenameShownInOutput(c *check.C) {
containerCmd := `echo foo > /root/bar`
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd)
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to start the container: %s, %v", out, err)
+ c.Fatalf("failed to start the container: %s, %v", out, err)
}
- cleanCID := stripTrailingCharacters(out)
+ cleanCID := strings.TrimSpace(out)
diffCmd := exec.Command(dockerBinary, "diff", cleanCID)
out, _, err = runCommandWithOutput(diffCmd)
if err != nil {
- t.Fatalf("failed to run diff: %s %v", out, err)
+ c.Fatalf("failed to run diff: %s %v", out, err)
}
found := false
@@ -31,62 +32,56 @@ func TestDiffFilenameShownInOutput(t *testing.T) {
}
}
if !found {
- t.Errorf("couldn't find the new file in docker diff's output: %v", out)
+ c.Errorf("couldn't find the new file in docker diff's output: %v", out)
}
- deleteContainer(cleanCID)
-
- logDone("diff - check if created file shows up")
}
// test to ensure GH #3840 doesn't occur any more
-func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) {
+func (s *DockerSuite) TestDiffEnsureDockerinitFilesAreIgnored(c *check.C) {
// this is a list of files which shouldn't show up in `docker diff`
dockerinitFiles := []string{"/etc/resolv.conf", "/etc/hostname", "/etc/hosts", "/.dockerinit", "/.dockerenv"}
+ containerCount := 5
// we might not run into this problem from the first run, so start a few containers
- for i := 0; i < 20; i++ {
+ for i := 0; i < containerCount; i++ {
containerCmd := `echo foo > /root/bar`
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd)
out, _, err := runCommandWithOutput(runCmd)
+
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanCID := stripTrailingCharacters(out)
+ cleanCID := strings.TrimSpace(out)
diffCmd := exec.Command(dockerBinary, "diff", cleanCID)
out, _, err = runCommandWithOutput(diffCmd)
if err != nil {
- t.Fatalf("failed to run diff: %s, %v", out, err)
+ c.Fatalf("failed to run diff: %s, %v", out, err)
}
- deleteContainer(cleanCID)
-
for _, filename := range dockerinitFiles {
if strings.Contains(out, filename) {
- t.Errorf("found file which should've been ignored %v in diff output", filename)
+ c.Errorf("found file which should've been ignored %v in diff output", filename)
}
}
}
-
- logDone("diff - check if ignored files show up in diff")
}
-func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) {
+func (s *DockerSuite) TestDiffEnsureOnlyKmsgAndPtmx(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "0")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanCID := stripTrailingCharacters(out)
+ cleanCID := strings.TrimSpace(out)
diffCmd := exec.Command(dockerBinary, "diff", cleanCID)
out, _, err = runCommandWithOutput(diffCmd)
if err != nil {
- t.Fatalf("failed to run diff: %s, %v", out, err)
+ c.Fatalf("failed to run diff: %s, %v", out, err)
}
- deleteContainer(cleanCID)
expected := map[string]bool{
"C /dev": true,
@@ -109,9 +104,7 @@ func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) {
for _, line := range strings.Split(out, "\n") {
if line != "" && !expected[line] {
- t.Errorf("%q is shown in the diff but shouldn't", line)
+ c.Errorf("%q is shown in the diff but shouldn't", line)
}
}
-
- logDone("diff - ensure that only kmsg and ptmx in diff")
}
diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go
index a74ce15fb..d6518ce8d 100644
--- a/integration-cli/docker_cli_events_test.go
+++ b/integration-cli/docker_cli_events_test.go
@@ -1,25 +1,63 @@
package main
import (
+ "bufio"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
- "testing"
+ "sync"
"time"
+
+ "github.com/go-check/check"
)
-func TestEventsUntag(t *testing.T) {
+func (s *DockerSuite) TestEventsTimestampFormats(c *check.C) {
image := "busybox"
- dockerCmd(t, "tag", image, "utest:tag1")
- dockerCmd(t, "tag", image, "utest:tag2")
- dockerCmd(t, "rmi", "utest:tag1")
- dockerCmd(t, "rmi", "utest:tag2")
+
+ // Start stopwatch, generate an event
+ time.Sleep(time.Second) // so that we don't grab events from previous test occured in the same second
+ start := daemonTime(c)
+ time.Sleep(time.Second) // remote API precision is only a second, wait a while before creating an event
+ dockerCmd(c, "tag", image, "timestamptest:1")
+ dockerCmd(c, "rmi", "timestamptest:1")
+ time.Sleep(time.Second) // so that until > since
+ end := daemonTime(c)
+
+ // List of available time formats to --since
+ unixTs := func(t time.Time) string { return fmt.Sprintf("%v", t.Unix()) }
+ rfc3339 := func(t time.Time) string { return t.Format(time.RFC3339) }
+
+ // --since=$start must contain only the 'untag' event
+ for _, f := range []func(time.Time) string{unixTs, rfc3339} {
+ since, until := f(start), f(end)
+ cmd := exec.Command(dockerBinary, "events", "--since="+since, "--until="+until)
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("docker events cmd failed: %v\nout=%s", err, out)
+ }
+ events := strings.Split(strings.TrimSpace(out), "\n")
+ if len(events) != 2 {
+ c.Fatalf("unexpected events, was expecting only 2 events tag/untag (since=%s, until=%s) out=%s", since, until, out)
+ }
+ if !strings.Contains(out, "untag") {
+ c.Fatalf("expected 'untag' event not found (since=%s, until=%s) out=%s", since, until, out)
+ }
+ }
+
+}
+
+func (s *DockerSuite) TestEventsUntag(c *check.C) {
+ image := "busybox"
+ dockerCmd(c, "tag", image, "utest:tag1")
+ dockerCmd(c, "tag", image, "utest:tag2")
+ dockerCmd(c, "rmi", "utest:tag1")
+ dockerCmd(c, "rmi", "utest:tag2")
eventsCmd := exec.Command(dockerBinary, "events", "--since=1")
out, exitCode, _, err := runCommandWithOutputForDuration(eventsCmd, time.Duration(time.Millisecond*200))
if exitCode != 0 || err != nil {
- t.Fatalf("Failed to get events - exit code %d: %s", exitCode, err)
+ c.Fatalf("Failed to get events - exit code %d: %s", exitCode, err)
}
events := strings.Split(out, "\n")
nEvents := len(events)
@@ -28,185 +66,271 @@ func TestEventsUntag(t *testing.T) {
// looking for.
for _, v := range events[nEvents-3 : nEvents-1] {
if !strings.Contains(v, "untag") {
- t.Fatalf("event should be untag, not %#v", v)
+ c.Fatalf("event should be untag, not %#v", v)
}
}
- logDone("events - untags are logged")
}
-func TestEventsContainerFailStartDie(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
- out, _, _ := dockerCmd(t, "images", "-q")
+ out, _ := dockerCmd(c, "images", "-q")
image := strings.Split(out, "\n")[0]
eventsCmd := exec.Command(dockerBinary, "run", "--name", "testeventdie", image, "blerg")
_, _, err := runCommandWithOutput(eventsCmd)
if err == nil {
- t.Fatalf("Container run with command blerg should have failed, but it did not")
+ c.Fatalf("Container run with command blerg should have failed, but it did not")
}
- eventsCmd = exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+ eventsCmd = exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, _, _ = runCommandWithOutput(eventsCmd)
events := strings.Split(out, "\n")
if len(events) <= 1 {
- t.Fatalf("Missing expected event")
+ c.Fatalf("Missing expected event")
}
startEvent := strings.Fields(events[len(events)-3])
dieEvent := strings.Fields(events[len(events)-2])
if startEvent[len(startEvent)-1] != "start" {
- t.Fatalf("event should be start, not %#v", startEvent)
+ c.Fatalf("event should be start, not %#v", startEvent)
}
if dieEvent[len(dieEvent)-1] != "die" {
- t.Fatalf("event should be die, not %#v", dieEvent)
+ c.Fatalf("event should be die, not %#v", dieEvent)
}
- logDone("events - container unwilling to start logs die")
}
-func TestEventsLimit(t *testing.T) {
- defer deleteAllContainers()
- for i := 0; i < 30; i++ {
- dockerCmd(t, "run", "busybox", "echo", strconv.Itoa(i))
+func (s *DockerSuite) TestEventsLimit(c *check.C) {
+
+ var waitGroup sync.WaitGroup
+ errChan := make(chan error, 17)
+
+ args := []string{"run", "--rm", "busybox", "true"}
+ for i := 0; i < 17; i++ {
+ waitGroup.Add(1)
+ go func() {
+ defer waitGroup.Done()
+ errChan <- exec.Command(dockerBinary, args...).Run()
+ }()
}
- eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+
+ waitGroup.Wait()
+ close(errChan)
+
+ for err := range errChan {
+ if err != nil {
+ c.Fatalf("%q failed with error: %v", strings.Join(args, " "), err)
+ }
+ }
+
+ eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, _, _ := runCommandWithOutput(eventsCmd)
events := strings.Split(out, "\n")
nEvents := len(events) - 1
if nEvents != 64 {
- t.Fatalf("events should be limited to 64, but received %d", nEvents)
+ c.Fatalf("events should be limited to 64, but received %d", nEvents)
}
- logDone("events - limited to 64 entries")
}
-func TestEventsContainerEvents(t *testing.T) {
- dockerCmd(t, "run", "--rm", "busybox", "true")
- eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+func (s *DockerSuite) TestEventsContainerEvents(c *check.C) {
+ dockerCmd(c, "run", "--rm", "busybox", "true")
+ eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, exitCode, err := runCommandWithOutput(eventsCmd)
if exitCode != 0 || err != nil {
- t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
+ c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
}
events := strings.Split(out, "\n")
events = events[:len(events)-1]
if len(events) < 4 {
- t.Fatalf("Missing expected event")
+ c.Fatalf("Missing expected event")
}
createEvent := strings.Fields(events[len(events)-4])
startEvent := strings.Fields(events[len(events)-3])
dieEvent := strings.Fields(events[len(events)-2])
destroyEvent := strings.Fields(events[len(events)-1])
if createEvent[len(createEvent)-1] != "create" {
- t.Fatalf("event should be create, not %#v", createEvent)
+ c.Fatalf("event should be create, not %#v", createEvent)
}
if startEvent[len(startEvent)-1] != "start" {
- t.Fatalf("event should be start, not %#v", startEvent)
+ c.Fatalf("event should be start, not %#v", startEvent)
}
if dieEvent[len(dieEvent)-1] != "die" {
- t.Fatalf("event should be die, not %#v", dieEvent)
+ c.Fatalf("event should be die, not %#v", dieEvent)
}
if destroyEvent[len(destroyEvent)-1] != "destroy" {
- t.Fatalf("event should be destroy, not %#v", destroyEvent)
+ c.Fatalf("event should be destroy, not %#v", destroyEvent)
}
- logDone("events - container create, start, die, destroy is logged")
}
-func TestEventsImageUntagDelete(t *testing.T) {
+func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) {
+ dockerCmd(c, "run", "--rm", "busybox", "true")
+ timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano)
+ timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1)
+ eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since='%s'", timeBeginning),
+ fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
+ out, exitCode, err := runCommandWithOutput(eventsCmd)
+ if exitCode != 0 || err != nil {
+ c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
+ }
+ events := strings.Split(out, "\n")
+ events = events[:len(events)-1]
+ if len(events) < 4 {
+ c.Fatalf("Missing expected event")
+ }
+ createEvent := strings.Fields(events[len(events)-4])
+ startEvent := strings.Fields(events[len(events)-3])
+ dieEvent := strings.Fields(events[len(events)-2])
+ destroyEvent := strings.Fields(events[len(events)-1])
+ if createEvent[len(createEvent)-1] != "create" {
+ c.Fatalf("event should be create, not %#v", createEvent)
+ }
+ if startEvent[len(startEvent)-1] != "start" {
+ c.Fatalf("event should be start, not %#v", startEvent)
+ }
+ if dieEvent[len(dieEvent)-1] != "die" {
+ c.Fatalf("event should be die, not %#v", dieEvent)
+ }
+ if destroyEvent[len(destroyEvent)-1] != "destroy" {
+ c.Fatalf("event should be destroy, not %#v", destroyEvent)
+ }
+
+}
+
+func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
name := "testimageevents"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM scratch
MAINTAINER "docker"`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := deleteImages(name); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+ eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, exitCode, err := runCommandWithOutput(eventsCmd)
if exitCode != 0 || err != nil {
- t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
+ c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
}
events := strings.Split(out, "\n")
events = events[:len(events)-1]
if len(events) < 2 {
- t.Fatalf("Missing expected event")
+ c.Fatalf("Missing expected event")
}
untagEvent := strings.Fields(events[len(events)-2])
deleteEvent := strings.Fields(events[len(events)-1])
if untagEvent[len(untagEvent)-1] != "untag" {
- t.Fatalf("untag should be untag, not %#v", untagEvent)
+ c.Fatalf("untag should be untag, not %#v", untagEvent)
}
if deleteEvent[len(deleteEvent)-1] != "delete" {
- t.Fatalf("delete should be delete, not %#v", deleteEvent)
+ c.Fatalf("delete should be delete, not %#v", deleteEvent)
}
- logDone("events - image untag, delete is logged")
}
-func TestEventsImagePull(t *testing.T) {
- since := daemonTime(t).Unix()
+func (s *DockerSuite) TestEventsImageTag(c *check.C) {
+ time.Sleep(time.Second * 2) // because API has seconds granularity
+ since := daemonTime(c).Unix()
+ image := "testimageevents:tag"
+ dockerCmd(c, "tag", "busybox", image)
- defer deleteImages("hello-world")
+ eventsCmd := exec.Command(dockerBinary, "events",
+ fmt.Sprintf("--since=%d", since),
+ fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
+ out, _, err := runCommandWithOutput(eventsCmd)
+ c.Assert(err, check.IsNil)
+
+ events := strings.Split(strings.TrimSpace(out), "\n")
+ if len(events) != 1 {
+ c.Fatalf("was expecting 1 event. out=%s", out)
+ }
+ event := strings.TrimSpace(events[0])
+ expectedStr := image + ": tag"
+
+ if !strings.HasSuffix(event, expectedStr) {
+ c.Fatalf("wrong event format. expected='%s' got=%s", expectedStr, event)
+ }
+
+}
+
+func (s *DockerSuite) TestEventsImagePull(c *check.C) {
+ since := daemonTime(c).Unix()
+ testRequires(c, Network)
pullCmd := exec.Command(dockerBinary, "pull", "hello-world")
if out, _, err := runCommandWithOutput(pullCmd); err != nil {
- t.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err)
+ c.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err)
}
eventsCmd := exec.Command(dockerBinary, "events",
fmt.Sprintf("--since=%d", since),
- fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+ fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, _, _ := runCommandWithOutput(eventsCmd)
events := strings.Split(strings.TrimSpace(out), "\n")
event := strings.TrimSpace(events[len(events)-1])
if !strings.HasSuffix(event, "hello-world:latest: pull") {
- t.Fatalf("Missing pull event - got:%q", event)
+ c.Fatalf("Missing pull event - got:%q", event)
}
- logDone("events - image pull is logged")
}
-func TestEventsImageImport(t *testing.T) {
- defer deleteAllContainers()
- since := daemonTime(t).Unix()
+func (s *DockerSuite) TestEventsImageImport(c *check.C) {
+ since := daemonTime(c).Unix()
+
+ id := make(chan string)
+ eventImport := make(chan struct{})
+ eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(since, 10))
+ stdout, err := eventsCmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ if err := eventsCmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ defer eventsCmd.Process.Kill()
+
+ go func() {
+ containerID := <-id
+
+ matchImport := regexp.MustCompile(containerID + `: import$`)
+ scanner := bufio.NewScanner(stdout)
+ for scanner.Scan() {
+ if matchImport.MatchString(scanner.Text()) {
+ close(eventImport)
+ }
+ }
+ }()
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal("failed to create a container", out, err)
+ c.Fatal("failed to create a container", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "export", cleanedContainerID),
exec.Command(dockerBinary, "import", "-"),
)
if err != nil {
- t.Errorf("import failed with errors: %v, output: %q", err, out)
+ c.Errorf("import failed with errors: %v, output: %q", err, out)
}
+ newContainerID := strings.TrimSpace(out)
+ id <- newContainerID
- eventsCmd := exec.Command(dockerBinary, "events",
- fmt.Sprintf("--since=%d", since),
- fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
- out, _, _ = runCommandWithOutput(eventsCmd)
-
- events := strings.Split(strings.TrimSpace(out), "\n")
- event := strings.TrimSpace(events[len(events)-1])
-
- if !strings.HasSuffix(event, ": import") {
- t.Fatalf("Missing pull event - got:%q", event)
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe image import in timely fashion")
+ case <-eventImport:
+ // ignore, done
}
-
- logDone("events - image import is logged")
}
-func TestEventsFilters(t *testing.T) {
+func (s *DockerSuite) TestEventsFilters(c *check.C) {
parseEvents := func(out, match string) {
events := strings.Split(out, "\n")
events = events[:len(events)-1]
@@ -214,175 +338,228 @@ func TestEventsFilters(t *testing.T) {
eventFields := strings.Fields(event)
eventName := eventFields[len(eventFields)-1]
if ok, err := regexp.MatchString(match, eventName); err != nil || !ok {
- t.Fatalf("event should match %s, got %#v, err: %v", match, eventFields, err)
+ c.Fatalf("event should match %s, got %#v, err: %v", match, eventFields, err)
}
}
}
- since := daemonTime(t).Unix()
+ since := daemonTime(c).Unix()
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", "busybox", "true"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", "busybox", "true"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", "event=die"))
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die"))
if err != nil {
- t.Fatalf("Failed to get events: %s", err)
+ c.Fatalf("Failed to get events: %s", err)
}
parseEvents(out, "die")
- out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", "event=die", "--filter", "event=start"))
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die", "--filter", "event=start"))
if err != nil {
- t.Fatalf("Failed to get events: %s", err)
+ c.Fatalf("Failed to get events: %s", err)
}
parseEvents(out, "((die)|(start))")
// make sure we at least got 2 start events
count := strings.Count(out, "start")
if count < 2 {
- t.Fatalf("should have had 2 start events but had %d, out: %s", count, out)
+ c.Fatalf("should have had 2 start events but had %d, out: %s", count, out)
}
- logDone("events - filters")
}
-func TestEventsFilterImageName(t *testing.T) {
- since := daemonTime(t).Unix()
- defer deleteAllContainers()
+func (s *DockerSuite) TestEventsFilterImageName(c *check.C) {
+ since := daemonTime(c).Unix()
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "-d", "busybox", "true"))
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "-d", "busybox:latest", "true"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- container1 := stripTrailingCharacters(out)
+ container1 := strings.TrimSpace(out)
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_2", "-d", "busybox", "true"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- container2 := stripTrailingCharacters(out)
+ container2 := strings.TrimSpace(out)
- for _, s := range []string{"busybox", "busybox:latest"} {
- eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("image=%s", s))
- out, _, err := runCommandWithOutput(eventsCmd)
- if err != nil {
- t.Fatalf("Failed to get events, error: %s(%s)", err, out)
- }
- events := strings.Split(out, "\n")
- events = events[:len(events)-1]
- if len(events) == 0 {
- t.Fatalf("Expected events but found none for the image busybox:latest")
- }
- count1 := 0
- count2 := 0
- for _, e := range events {
- if strings.Contains(e, container1) {
- count1++
- } else if strings.Contains(e, container2) {
- count2++
- }
- }
- if count1 == 0 || count2 == 0 {
- t.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2)
+ name := "busybox"
+ eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", fmt.Sprintf("image=%s", name))
+ out, _, err = runCommandWithOutput(eventsCmd)
+ if err != nil {
+ c.Fatalf("Failed to get events, error: %s(%s)", err, out)
+ }
+ events := strings.Split(out, "\n")
+ events = events[:len(events)-1]
+ if len(events) == 0 {
+ c.Fatalf("Expected events but found none for the image busybox:latest")
+ }
+ count1 := 0
+ count2 := 0
+
+ for _, e := range events {
+ if strings.Contains(e, container1) {
+ count1++
+ } else if strings.Contains(e, container2) {
+ count2++
}
}
+ if count1 == 0 || count2 == 0 {
+ c.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2)
+ }
- logDone("events - filters using image")
}
-func TestEventsFilterContainerID(t *testing.T) {
- since := daemonTime(t).Unix()
- defer deleteAllContainers()
+func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
+ since := fmt.Sprintf("%d", daemonTime(c).Unix())
+ nameID := make(map[string]string)
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "true"))
- if err != nil {
- t.Fatal(out, err)
+ for _, name := range []string{"container_1", "container_2"} {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", name, "busybox", "true"))
+ if err != nil {
+ c.Fatalf("Error: %v, Output: %s", err, out)
+ }
+ id, err := inspectField(name, "Id")
+ if err != nil {
+ c.Fatal(err)
+ }
+ nameID[name] = id
}
- container1 := stripTrailingCharacters(out)
- out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "true"))
- if err != nil {
- t.Fatal(out, err)
+ until := fmt.Sprintf("%d", daemonTime(c).Unix())
+
+ checkEvents := func(id string, events []string) error {
+ if len(events) != 3 { // create, start, die
+ return fmt.Errorf("expected 3 events, got %v", events)
+ }
+ for _, event := range events {
+ e := strings.Fields(event)
+ if len(e) < 3 {
+ return fmt.Errorf("got malformed event: %s", event)
+ }
+
+ // Check the id
+ parsedID := strings.TrimSuffix(e[1], ":")
+ if parsedID != id {
+ return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, parsedID)
+ }
+ }
+ return nil
}
- container2 := stripTrailingCharacters(out)
- for _, s := range []string{container1, container2, container1[:12], container2[:12]} {
- eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("container=%s", s))
+ for name, ID := range nameID {
+ // filter by names
+ eventsCmd := exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+name)
out, _, err := runCommandWithOutput(eventsCmd)
if err != nil {
- t.Fatalf("Failed to get events, error: %s(%s)", err, out)
+ c.Fatal(err)
}
- events := strings.Split(out, "\n")
- events = events[:len(events)-1]
- if len(events) == 0 || len(events) > 3 {
- t.Fatalf("Expected 3 events, got %d: %v", len(events), events)
+
+ events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
+ if err := checkEvents(ID, events); err != nil {
+ c.Fatal(err)
}
- createEvent := strings.Fields(events[0])
- if createEvent[len(createEvent)-1] != "create" {
- t.Fatalf("first event should be create, not %#v", createEvent)
- }
- if len(events) > 1 {
- startEvent := strings.Fields(events[1])
- if startEvent[len(startEvent)-1] != "start" {
- t.Fatalf("second event should be start, not %#v", startEvent)
- }
- }
- if len(events) == 3 {
- dieEvent := strings.Fields(events[len(events)-1])
- if dieEvent[len(dieEvent)-1] != "die" {
- t.Fatalf("event should be die, not %#v", dieEvent)
- }
- }
- }
- logDone("events - filters using container id")
-}
-
-func TestEventsFilterContainerName(t *testing.T) {
- since := daemonTime(t).Unix()
- defer deleteAllContainers()
-
- _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "busybox", "true"))
- if err != nil {
- t.Fatal(err)
- }
-
- _, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_2", "busybox", "true"))
- if err != nil {
- t.Fatal(err)
- }
-
- for _, s := range []string{"container_1", "container_2"} {
- eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("container=%s", s))
- out, _, err := runCommandWithOutput(eventsCmd)
+ // filter by ID's
+ eventsCmd = exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+ID)
+ out, _, err = runCommandWithOutput(eventsCmd)
if err != nil {
- t.Fatalf("Failed to get events, error : %s(%s)", err, out)
+ c.Fatal(err)
}
- events := strings.Split(out, "\n")
- events = events[:len(events)-1]
- if len(events) == 0 || len(events) > 3 {
- t.Fatalf("Expected 3 events, got %d: %v", len(events), events)
- }
- createEvent := strings.Fields(events[0])
- if createEvent[len(createEvent)-1] != "create" {
- t.Fatalf("first event should be create, not %#v", createEvent)
- }
- if len(events) > 1 {
- startEvent := strings.Fields(events[1])
- if startEvent[len(startEvent)-1] != "start" {
- t.Fatalf("second event should be start, not %#v", startEvent)
- }
- }
- if len(events) == 3 {
- dieEvent := strings.Fields(events[len(events)-1])
- if dieEvent[len(dieEvent)-1] != "die" {
- t.Fatalf("event should be die, not %#v", dieEvent)
- }
+
+ events = strings.Split(strings.TrimSuffix(out, "\n"), "\n")
+ if err := checkEvents(ID, events); err != nil {
+ c.Fatal(err)
}
}
- logDone("events - filters using container name")
+}
+
+func (s *DockerSuite) TestEventsStreaming(c *check.C) {
+ start := daemonTime(c).Unix()
+
+ id := make(chan string)
+ eventCreate := make(chan struct{})
+ eventStart := make(chan struct{})
+ eventDie := make(chan struct{})
+ eventDestroy := make(chan struct{})
+
+ eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10))
+ stdout, err := eventsCmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ if err := eventsCmd.Start(); err != nil {
+ c.Fatalf("failed to start 'docker events': %s", err)
+ }
+ defer eventsCmd.Process.Kill()
+
+ go func() {
+ containerID := <-id
+
+ matchCreate := regexp.MustCompile(containerID + `: \(from busybox:latest\) create$`)
+ matchStart := regexp.MustCompile(containerID + `: \(from busybox:latest\) start$`)
+ matchDie := regexp.MustCompile(containerID + `: \(from busybox:latest\) die$`)
+ matchDestroy := regexp.MustCompile(containerID + `: \(from busybox:latest\) destroy$`)
+
+ scanner := bufio.NewScanner(stdout)
+ for scanner.Scan() {
+ switch {
+ case matchCreate.MatchString(scanner.Text()):
+ close(eventCreate)
+ case matchStart.MatchString(scanner.Text()):
+ close(eventStart)
+ case matchDie.MatchString(scanner.Text()):
+ close(eventDie)
+ case matchDestroy.MatchString(scanner.Text()):
+ close(eventDestroy)
+ }
+ }
+ }()
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox:latest", "true")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+ id <- cleanedContainerID
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe container create in timely fashion")
+ case <-eventCreate:
+ // ignore, done
+ }
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe container start in timely fashion")
+ case <-eventStart:
+ // ignore, done
+ }
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe container die in timely fashion")
+ case <-eventDie:
+ // ignore, done
+ }
+
+ rmCmd := exec.Command(dockerBinary, "rm", cleanedContainerID)
+ out, _, err = runCommandWithOutput(rmCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe container destroy in timely fashion")
+ case <-eventDestroy:
+ // ignore, done
+ }
}
diff --git a/integration-cli/docker_cli_events_unix_test.go b/integration-cli/docker_cli_events_unix_test.go
index 4e5428350..1a08f2b3c 100644
--- a/integration-cli/docker_cli_events_unix_test.go
+++ b/integration-cli/docker_cli_events_unix_test.go
@@ -8,48 +8,46 @@ import (
"io/ioutil"
"os"
"os/exec"
- "testing"
"unicode"
+ "github.com/go-check/check"
"github.com/kr/pty"
)
// #5979
-func TestEventsRedirectStdout(t *testing.T) {
- since := daemonTime(t).Unix()
- dockerCmd(t, "run", "busybox", "true")
- defer deleteAllContainers()
+func (s *DockerSuite) TestEventsRedirectStdout(c *check.C) {
+ since := daemonTime(c).Unix()
+ dockerCmd(c, "run", "busybox", "true")
file, err := ioutil.TempFile("", "")
if err != nil {
- t.Fatalf("could not create temp file: %v", err)
+ c.Fatalf("could not create temp file: %v", err)
}
defer os.Remove(file.Name())
- command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, daemonTime(t).Unix(), file.Name())
+ command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, daemonTime(c).Unix(), file.Name())
_, tty, err := pty.Open()
if err != nil {
- t.Fatalf("Could not open pty: %v", err)
+ c.Fatalf("Could not open pty: %v", err)
}
cmd := exec.Command("sh", "-c", command)
cmd.Stdin = tty
cmd.Stdout = tty
cmd.Stderr = tty
if err := cmd.Run(); err != nil {
- t.Fatalf("run err for command %q: %v", command, err)
+ c.Fatalf("run err for command %q: %v", command, err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
- for _, c := range scanner.Text() {
- if unicode.IsControl(c) {
- t.Fatalf("found control character %v", []byte(string(c)))
+ for _, ch := range scanner.Text() {
+ if unicode.IsControl(ch) {
+ c.Fatalf("found control character %v", []byte(string(ch)))
}
}
}
if err := scanner.Err(); err != nil {
- t.Fatalf("Scan err for command %q: %v", command, err)
+ c.Fatalf("Scan err for command %q: %v", command, err)
}
- logDone("events - redirect stdout")
}
diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go
index 01adc43c0..9abafb8fa 100644
--- a/integration-cli/docker_cli_exec_test.go
+++ b/integration-cli/docker_cli_exec_test.go
@@ -12,224 +12,170 @@ import (
"sort"
"strings"
"sync"
- "testing"
"time"
+
+ "github.com/go-check/check"
)
-func TestExec(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExec(c *check.C) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && sleep 100")
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top")
if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
execCmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/tmp/file")
out, _, err := runCommandWithOutput(execCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
out = strings.Trim(out, "\r\n")
if expected := "test"; out != expected {
- t.Errorf("container exec should've printed %q but printed %q", expected, out)
+ c.Errorf("container exec should've printed %q but printed %q", expected, out)
}
- logDone("exec - basic test")
}
-func TestExecInteractiveStdinClose(t *testing.T) {
- defer deleteAllContainers()
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat"))
- if err != nil {
- t.Fatal(err)
- }
+func (s *DockerSuite) TestExecInteractive(c *check.C) {
- contId := strings.TrimSpace(out)
-
- returnchan := make(chan struct{})
-
- go func() {
- var err error
- cmd := exec.Command(dockerBinary, "exec", "-i", contId, "/bin/ls", "/")
- cmd.Stdin = os.Stdin
- if err != nil {
- t.Fatal(err)
- }
-
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatal(err, string(out))
- }
-
- if string(out) == "" {
- t.Fatalf("Output was empty, likely blocked by standard input")
- }
-
- returnchan <- struct{}{}
- }()
-
- select {
- case <-returnchan:
- case <-time.After(10 * time.Second):
- t.Fatal("timed out running docker exec")
- }
-
- logDone("exec - interactive mode closes stdin after execution")
-}
-
-func TestExecInteractive(t *testing.T) {
- defer deleteAllContainers()
-
- runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && sleep 100")
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top")
if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh")
stdin, err := execCmd.StdinPipe()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
stdout, err := execCmd.StdoutPipe()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := execCmd.Start(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := stdin.Write([]byte("cat /tmp/file\n")); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
r := bufio.NewReader(stdout)
line, err := r.ReadString('\n')
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
line = strings.TrimSpace(line)
if line != "test" {
- t.Fatalf("Output should be 'test', got '%q'", line)
+ c.Fatalf("Output should be 'test', got '%q'", line)
}
if err := stdin.Close(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- finish := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if err := execCmd.Wait(); err != nil {
- t.Fatal(err)
- }
- close(finish)
+ errChan <- execCmd.Wait()
+ close(errChan)
}()
select {
- case <-finish:
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
case <-time.After(1 * time.Second):
- t.Fatal("docker exec failed to exit on stdin close")
+ c.Fatal("docker exec failed to exit on stdin close")
}
- logDone("exec - Interactive test")
}
-func TestExecAfterContainerRestart(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "exec", cleanedContainerID, "echo", "hello")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
outStr := strings.TrimSpace(out)
if outStr != "hello" {
- t.Errorf("container should've printed hello, instead printed %q", outStr)
+ c.Errorf("container should've printed hello, instead printed %q", outStr)
}
- logDone("exec - exec running container after container restart")
}
-func TestExecAfterDaemonRestart(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) {
+ testRequires(c, SameHostDaemon)
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
- }
- defer d.Stop()
-
- if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil {
- t.Fatalf("Could not run top: err=%v\n%s", err, out)
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
}
- if err := d.Restart(); err != nil {
- t.Fatalf("Could not restart daemon: %v", err)
+ if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil {
+ c.Fatalf("Could not run top: err=%v\n%s", err, out)
}
- if out, err := d.Cmd("start", "top"); err != nil {
- t.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out)
+ if err := s.d.Restart(); err != nil {
+ c.Fatalf("Could not restart daemon: %v", err)
}
- out, err := d.Cmd("exec", "top", "echo", "hello")
+ if out, err := s.d.Cmd("start", "top"); err != nil {
+ c.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out)
+ }
+
+ out, err := s.d.Cmd("exec", "top", "echo", "hello")
if err != nil {
- t.Fatalf("Could not exec on container top: err=%v\n%s", err, out)
+ c.Fatalf("Could not exec on container top: err=%v\n%s", err, out)
}
outStr := strings.TrimSpace(string(out))
if outStr != "hello" {
- t.Errorf("container should've printed hello, instead printed %q", outStr)
+ c.Errorf("container should've printed hello, instead printed %q", outStr)
}
-
- logDone("exec - exec running container after daemon restart")
}
// Regression test for #9155, #9044
-func TestExecEnv(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecEnv(c *check.C) {
runCmd := exec.Command(dockerBinary, "run",
"-e", "LALA=value1",
"-e", "LALA=value2",
"-d", "--name", "testing", "busybox", "top")
if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
execCmd := exec.Command(dockerBinary, "exec", "testing", "env")
out, _, err := runCommandWithOutput(execCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if strings.Contains(out, "LALA=value1") ||
!strings.Contains(out, "LALA=value2") ||
!strings.Contains(out, "HOME=/root") {
- t.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root")
+ c.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root")
}
- logDone("exec - exec inherits correct env")
}
-func TestExecExitStatus(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecExitStatus(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top")
if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// Test normal (non-detached) case first
@@ -237,193 +183,191 @@ func TestExecExitStatus(t *testing.T) {
ec, _ := runCommand(cmd)
if ec != 23 {
- t.Fatalf("Should have had an ExitCode of 23, not: %d", ec)
+ c.Fatalf("Should have had an ExitCode of 23, not: %d", ec)
}
- logDone("exec - exec non-zero ExitStatus")
}
-func TestExecPausedContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecPausedContainer(c *check.C) {
defer unpauseAllContainers()
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- ContainerID := stripTrailingCharacters(out)
+ ContainerID := strings.TrimSpace(out)
pausedCmd := exec.Command(dockerBinary, "pause", "testing")
out, _, _, err = runCommandWithStdoutStderr(pausedCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
execCmd := exec.Command(dockerBinary, "exec", "-i", "-t", ContainerID, "echo", "hello")
out, _, err = runCommandWithOutput(execCmd)
if err == nil {
- t.Fatal("container should fail to exec new command if it is paused")
+ c.Fatal("container should fail to exec new command if it is paused")
}
expected := ContainerID + " is paused, unpause the container before exec"
if !strings.Contains(out, expected) {
- t.Fatal("container should not exec new command if it is paused")
+ c.Fatal("container should not exec new command if it is paused")
}
- logDone("exec - exec should not exec a pause container")
}
// regression test for #9476
-func TestExecTtyCloseStdin(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecTtyCloseStdin(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
cmd = exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat")
stdinRw, err := cmd.StdinPipe()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
stdinRw.Write([]byte("test"))
stdinRw.Close()
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
cmd = exec.Command(dockerBinary, "top", "exec_tty_stdin")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
outArr := strings.Split(out, "\n")
if len(outArr) > 3 || strings.Contains(out, "nsenter-exec") {
// This is the really bad part
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rm", "-f", "exec_tty_stdin")); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- t.Fatalf("exec process left running\n\t %s", out)
+ c.Fatalf("exec process left running\n\t %s", out)
}
- logDone("exec - stdin is closed properly with tty enabled")
}
-func TestExecTtyWithoutStdin(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecTtyWithoutStdin(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to start container: %v (%v)", out, err)
+ c.Fatalf("failed to start container: %v (%v)", out, err)
}
id := strings.TrimSpace(out)
if err := waitRun(id); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer func() {
cmd := exec.Command(dockerBinary, "kill", id)
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatalf("failed to kill container: %v (%v)", out, err)
+ c.Fatalf("failed to kill container: %v (%v)", out, err)
}
}()
- done := make(chan struct{})
+ errChan := make(chan error)
go func() {
- defer close(done)
+ defer close(errChan)
cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true")
if _, err := cmd.StdinPipe(); err != nil {
- t.Fatal(err)
+ errChan <- err
+ return
}
expected := "cannot enable tty mode"
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal("exec should have failed")
+ errChan <- fmt.Errorf("exec should have failed")
+ return
} else if !strings.Contains(out, expected) {
- t.Fatalf("exec failed with error %q: expected %q", out, expected)
+ errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected)
+ return
}
}()
select {
- case <-done:
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
case <-time.After(3 * time.Second):
- t.Fatal("exec is running but should have failed")
+ c.Fatal("exec is running but should have failed")
}
- logDone("exec - forbid piped stdin to tty enabled container")
}
-func TestExecParseError(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecParseError(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// Test normal (non-detached) case first
cmd := exec.Command(dockerBinary, "exec", "top")
if _, stderr, code, err := runCommandWithStdoutStderr(cmd); err == nil || !strings.Contains(stderr, "See '"+dockerBinary+" exec --help'") || code == 0 {
- t.Fatalf("Should have thrown error & point to help: %s", stderr)
+ c.Fatalf("Should have thrown error & point to help: %s", stderr)
}
- logDone("exec - error on parseExec should point to help")
}
-func TestExecStopNotHanging(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecStopNotHanging(c *check.C) {
if out, err := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top").CombinedOutput(); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if err := exec.Command(dockerBinary, "exec", "testing", "top").Start(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- wait := make(chan struct{})
+ type dstop struct {
+ out []byte
+ err error
+ }
+
+ ch := make(chan dstop)
go func() {
- if out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput(); err != nil {
- t.Fatal(out, err)
- }
- close(wait)
+ out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput()
+ ch <- dstop{out, err}
+ close(ch)
}()
select {
case <-time.After(3 * time.Second):
- t.Fatal("Container stop timed out")
- case <-wait:
+ c.Fatal("Container stop timed out")
+ case s := <-ch:
+ c.Assert(s.err, check.IsNil)
}
- logDone("exec - container with exec not hanging on stop")
}
-func TestExecCgroup(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestExecCgroup(c *check.C) {
var cmd *exec.Cmd
cmd = exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top")
_, err := runCommand(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/1/cgroup")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerCgroups := sort.StringSlice(strings.Split(string(out), "\n"))
var wg sync.WaitGroup
- var s sync.Mutex
+ var mu sync.Mutex
execCgroups := []sort.StringSlice{}
+ errChan := make(chan error)
// exec a few times concurrently to get consistent failure
for i := 0; i < 5; i++ {
wg.Add(1)
@@ -431,17 +375,23 @@ func TestExecCgroup(t *testing.T) {
cmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/self/cgroup")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ errChan <- err
+ return
}
cg := sort.StringSlice(strings.Split(string(out), "\n"))
- s.Lock()
+ mu.Lock()
execCgroups = append(execCgroups, cg)
- s.Unlock()
+ mu.Unlock()
wg.Done()
}()
}
wg.Wait()
+ close(errChan)
+
+ for err := range errChan {
+ c.Assert(err, check.IsNil)
+ }
for _, cg := range execCgroups {
if !reflect.DeepEqual(cg, containerCgroups) {
@@ -454,86 +404,81 @@ func TestExecCgroup(t *testing.T) {
for _, name := range containerCgroups {
fmt.Printf(" %s\n", name)
}
- t.Fatal("cgroups mismatched")
+ c.Fatal("cgroups mismatched")
}
}
- logDone("exec - exec has the container cgroups")
}
-func TestInspectExecID(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestInspectExecID(c *check.C) {
out, exitCode, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "top"))
if exitCode != 0 || err != nil {
- t.Fatalf("failed to run container: %s, %v", out, err)
+ c.Fatalf("failed to run container: %s, %v", out, err)
}
id := strings.TrimSuffix(out, "\n")
out, err = inspectField(id, "ExecIDs")
if err != nil {
- t.Fatalf("failed to inspect container: %s, %v", out, err)
+ c.Fatalf("failed to inspect container: %s, %v", out, err)
}
- if out != "" {
- t.Fatalf("ExecIDs should be empty, got: %s", out)
+ if out != "[]" {
+ c.Fatalf("ExecIDs should be empty, got: %s", out)
}
exitCode, err = runCommand(exec.Command(dockerBinary, "exec", "-d", id, "ls", "/"))
if exitCode != 0 || err != nil {
- t.Fatalf("failed to exec in container: %s, %v", out, err)
+ c.Fatalf("failed to exec in container: %s, %v", out, err)
}
out, err = inspectField(id, "ExecIDs")
if err != nil {
- t.Fatalf("failed to inspect container: %s, %v", out, err)
+ c.Fatalf("failed to inspect container: %s, %v", out, err)
}
out = strings.TrimSuffix(out, "\n")
if out == "[]" || out == "" {
- t.Fatalf("ExecIDs should not be empty, got: %s", out)
+ c.Fatalf("ExecIDs should not be empty, got: %s", out)
}
- logDone("inspect - inspect a container with ExecIDs")
}
-func TestLinksPingLinkedContainersOnRename(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) {
var out string
- out, _, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10")
- idA := stripTrailingCharacters(out)
+ out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top")
+ idA := strings.TrimSpace(out)
if idA == "" {
- t.Fatal(out, "id should not be nil")
+ c.Fatal(out, "id should not be nil")
}
- out, _, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "sleep", "10")
- idB := stripTrailingCharacters(out)
+ out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top")
+ idB := strings.TrimSpace(out)
if idB == "" {
- t.Fatal(out, "id should not be nil")
+ c.Fatal(out, "id should not be nil")
}
execCmd := exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1")
out, _, err := runCommandWithOutput(execCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- dockerCmd(t, "rename", "container1", "container_new")
+ dockerCmd(c, "rename", "container1", "container_new")
execCmd = exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1")
out, _, err = runCommandWithOutput(execCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- logDone("links - ping linked container upon rename")
}
-func TestRunExecDir(t *testing.T) {
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunExecDir(c *check.C) {
+ testRequires(c, SameHostDaemon)
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
id := strings.TrimSpace(out)
execDir := filepath.Join(execDriverPath, id)
@@ -542,92 +487,90 @@ func TestRunExecDir(t *testing.T) {
{
fi, err := os.Stat(execDir)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !fi.IsDir() {
- t.Fatalf("%q must be a directory", execDir)
+ c.Fatalf("%q must be a directory", execDir)
}
fi, err = os.Stat(stateFile)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}
stopCmd := exec.Command(dockerBinary, "stop", id)
out, _, err = runCommandWithOutput(stopCmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
{
_, err := os.Stat(execDir)
if err == nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err == nil {
- t.Fatalf("Exec directory %q exists for removed container!", execDir)
+ c.Fatalf("Exec directory %q exists for removed container!", execDir)
}
if !os.IsNotExist(err) {
- t.Fatalf("Error should be about non-existing, got %s", err)
+ c.Fatalf("Error should be about non-existing, got %s", err)
}
}
startCmd := exec.Command(dockerBinary, "start", id)
out, _, err = runCommandWithOutput(startCmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
{
fi, err := os.Stat(execDir)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !fi.IsDir() {
- t.Fatalf("%q must be a directory", execDir)
+ c.Fatalf("%q must be a directory", execDir)
}
fi, err = os.Stat(stateFile)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}
rmCmd := exec.Command(dockerBinary, "rm", "-f", id)
out, _, err = runCommandWithOutput(rmCmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
{
_, err := os.Stat(execDir)
if err == nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err == nil {
- t.Fatalf("Exec directory %q is exists for removed container!", execDir)
+ c.Fatalf("Exec directory %q is exists for removed container!", execDir)
}
if !os.IsNotExist(err) {
- t.Fatalf("Error should be about non-existing, got %s", err)
+ c.Fatalf("Error should be about non-existing, got %s", err)
}
}
- logDone("run - check execdriver dir behavior")
}
-func TestRunMutableNetworkFiles(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) {
+ testRequires(c, SameHostDaemon)
for _, fn := range []string{"resolv.conf", "hosts"} {
deleteAllContainers()
content, err := runCommandAndReadContainerFile(fn, exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn)))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.TrimSpace(string(content)) != "success" {
- t.Fatal("Content was not what was modified in the container", string(content))
+ c.Fatal("Content was not what was modified in the container", string(content))
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "c2", "busybox", "top"))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
contID := strings.TrimSpace(out)
@@ -636,32 +579,58 @@ func TestRunMutableNetworkFiles(t *testing.T) {
f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := f.Seek(0, 0); err != nil {
f.Close()
- t.Fatal(err)
+ c.Fatal(err)
}
if err := f.Truncate(0); err != nil {
f.Close()
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := f.Write([]byte("success2\n")); err != nil {
f.Close()
- t.Fatal(err)
+ c.Fatal(err)
}
f.Close()
res, err := exec.Command(dockerBinary, "exec", contID, "cat", "/etc/"+fn).CombinedOutput()
if err != nil {
- t.Fatalf("Output: %s, error: %s", res, err)
+ c.Fatalf("Output: %s, error: %s", res, err)
}
if string(res) != "success2\n" {
- t.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res)
+ c.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res)
}
}
- logDone("run - mutable network files")
+}
+
+func (s *DockerSuite) TestExecWithUser(c *check.C) {
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top")
+ if out, _, err := runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ cmd := exec.Command(dockerBinary, "exec", "-u", "1", "parent", "id")
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") {
+ c.Fatalf("exec with user by id expected daemon user got %s", out)
+ }
+
+ cmd = exec.Command(dockerBinary, "exec", "-u", "root", "parent", "id")
+ out, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ if !strings.Contains(out, "uid=0(root) gid=0(root)") {
+ c.Fatalf("exec with user by root expected root user got %s", out)
+ }
+
}
diff --git a/integration-cli/docker_cli_exec_unix_test.go b/integration-cli/docker_cli_exec_unix_test.go
new file mode 100644
index 000000000..bee44b990
--- /dev/null
+++ b/integration-cli/docker_cli_exec_unix_test.go
@@ -0,0 +1,47 @@
+// +build !windows,!test_no_exec
+
+package main
+
+import (
+ "bytes"
+ "io"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/go-check/check"
+ "github.com/kr/pty"
+)
+
+// regression test for #12546
+func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat"))
+ if err != nil {
+ c.Fatal(err)
+ }
+ contId := strings.TrimSpace(out)
+
+ cmd := exec.Command(dockerBinary, "exec", "-i", contId, "echo", "-n", "hello")
+ p, err := pty.Start(cmd)
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ b := bytes.NewBuffer(nil)
+ go io.Copy(b, p)
+
+ ch := make(chan error)
+ go func() { ch <- cmd.Wait() }()
+
+ select {
+ case err := <-ch:
+ if err != nil {
+ c.Errorf("cmd finished with error %v", err)
+ }
+ if output := b.String(); strings.TrimSpace(output) != "hello" {
+ c.Fatalf("Unexpected output %s", output)
+ }
+ case <-time.After(1 * time.Second):
+ c.Fatal("timed out running docker exec")
+ }
+}
diff --git a/integration-cli/docker_cli_experimental_test.go b/integration-cli/docker_cli_experimental_test.go
new file mode 100644
index 000000000..4cf05c91f
--- /dev/null
+++ b/integration-cli/docker_cli_experimental_test.go
@@ -0,0 +1,24 @@
+// +build experimental
+
+package main
+
+import (
+ "os/exec"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestExperimentalVersion(c *check.C) {
+ versionCmd := exec.Command(dockerBinary, "version")
+ out, _, err := runCommandWithOutput(versionCmd)
+ if err != nil {
+ c.Fatalf("failed to execute docker version: %s, %v", out, err)
+ }
+
+ for _, line := range strings.Split(out, "\n") {
+ if strings.HasPrefix(line, "Client version:") || strings.HasPrefix(line, "Server version:") {
+ c.Assert(line, check.Matches, "*-experimental")
+ }
+ }
+}
diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go
index 5b2a016f1..22772704e 100644
--- a/integration-cli/docker_cli_export_import_test.go
+++ b/integration-cli/docker_cli_export_import_test.go
@@ -4,96 +4,69 @@ import (
"os"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
// export an image and try to import it into a new one
-func TestExportContainerAndImportImage(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) {
+ containerID := "testexportcontainerandimportimage"
+
+ runCmd := exec.Command(dockerBinary, "run", "--name", containerID, "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal("failed to create a container", out, err)
+ c.Fatal("failed to create a container", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
-
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- out, _, err = runCommandWithOutput(inspectCmd)
- if err != nil {
- t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err)
- }
-
- exportCmd := exec.Command(dockerBinary, "export", cleanedContainerID)
+ exportCmd := exec.Command(dockerBinary, "export", containerID)
if out, _, err = runCommandWithOutput(exportCmd); err != nil {
- t.Fatalf("failed to export container: %s, %v", out, err)
+ c.Fatalf("failed to export container: %s, %v", out, err)
}
importCmd := exec.Command(dockerBinary, "import", "-", "repo/testexp:v1")
importCmd.Stdin = strings.NewReader(out)
out, _, err = runCommandWithOutput(importCmd)
if err != nil {
- t.Fatalf("failed to import image: %s, %v", out, err)
+ c.Fatalf("failed to import image: %s, %v", out, err)
}
- cleanedImageID := stripTrailingCharacters(out)
-
- inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID)
- if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("output should've been an image id: %s, %v", out, err)
+ cleanedImageID := strings.TrimSpace(out)
+ if cleanedImageID == "" {
+ c.Fatalf("output should have been an image id, got: %s", out)
}
-
- deleteContainer(cleanedContainerID)
- deleteImages("repo/testexp:v1")
-
- logDone("export - export a container")
- logDone("import - import an image")
}
// Used to test output flag in the export command
-func TestExportContainerWithOutputAndImportImage(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) {
+ containerID := "testexportcontainerwithoutputandimportimage"
+
+ runCmd := exec.Command(dockerBinary, "run", "--name", containerID, "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal("failed to create a container", out, err)
+ c.Fatal("failed to create a container", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ defer os.Remove("testexp.tar")
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- out, _, err = runCommandWithOutput(inspectCmd)
- if err != nil {
- t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err)
- }
-
- exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", cleanedContainerID)
+ exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", containerID)
if out, _, err = runCommandWithOutput(exportCmd); err != nil {
- t.Fatalf("failed to export container: %s, %v", out, err)
+ c.Fatalf("failed to export container: %s, %v", out, err)
}
out, _, err = runCommandWithOutput(exec.Command("cat", "testexp.tar"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
importCmd := exec.Command(dockerBinary, "import", "-", "repo/testexp:v1")
importCmd.Stdin = strings.NewReader(out)
out, _, err = runCommandWithOutput(importCmd)
if err != nil {
- t.Fatalf("failed to import image: %s, %v", out, err)
+ c.Fatalf("failed to import image: %s, %v", out, err)
}
- cleanedImageID := stripTrailingCharacters(out)
-
- inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID)
- if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("output should've been an image id: %s, %v", out, err)
+ cleanedImageID := strings.TrimSpace(out)
+ if cleanedImageID == "" {
+ c.Fatalf("output should have been an image id, got: %s", out)
}
-
- deleteContainer(cleanedContainerID)
- deleteImages("repo/testexp:v1")
-
- os.Remove("/tmp/testexp.tar")
-
- logDone("export - export a container with output flag")
- logDone("import - import an image with output flag")
}
diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go
index 8fc5cd1aa..86b0b3bbc 100644
--- a/integration-cli/docker_cli_help_test.go
+++ b/integration-cli/docker_cli_help_test.go
@@ -5,13 +5,13 @@ import (
"os/exec"
"runtime"
"strings"
- "testing"
"unicode"
"github.com/docker/docker/pkg/homedir"
+ "github.com/go-check/check"
)
-func TestHelpTextVerify(t *testing.T) {
+func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
// Make sure main help text fits within 80 chars and that
// on non-windows system we use ~ when possible (to shorten things).
// Test for HOME set to its default value and set to "/" on linux
@@ -51,26 +51,26 @@ func TestHelpTextVerify(t *testing.T) {
helpCmd.Env = newEnvs
out, ec, err := runCommandWithOutput(helpCmd)
if err != nil || ec != 0 {
- t.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec)
+ c.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec)
}
lines := strings.Split(out, "\n")
for _, line := range lines {
if len(line) > 80 {
- t.Fatalf("Line is too long(%d chars):\n%s", len(line), line)
+ c.Fatalf("Line is too long(%d chars):\n%s", len(line), line)
}
// All lines should not end with a space
if strings.HasSuffix(line, " ") {
- t.Fatalf("Line should not end with a space: %s", line)
+ c.Fatalf("Line should not end with a space: %s", line)
}
if scanForHome && strings.Contains(line, `=`+home) {
- t.Fatalf("Line should use '%q' instead of %q:\n%s", homedir.GetShortcutString(), home, line)
+ c.Fatalf("Line should use '%q' instead of %q:\n%s", homedir.GetShortcutString(), home, line)
}
if runtime.GOOS != "windows" {
i := strings.Index(line, homedir.GetShortcutString())
if i >= 0 && i != len(line)-1 && line[i+1] != '/' {
- t.Fatalf("Main help should not have used home shortcut:\n%s", line)
+ c.Fatalf("Main help should not have used home shortcut:\n%s", line)
}
}
}
@@ -82,11 +82,11 @@ func TestHelpTextVerify(t *testing.T) {
helpCmd.Env = newEnvs
out, ec, err = runCommandWithOutput(helpCmd)
if err != nil || ec != 0 {
- t.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec)
+ c.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec)
}
i := strings.Index(out, "Commands:")
if i < 0 {
- t.Fatalf("Missing 'Commands:' in:\n%s", out)
+ c.Fatalf("Missing 'Commands:' in:\n%s", out)
}
// Grab all chars starting at "Commands:"
@@ -106,39 +106,39 @@ func TestHelpTextVerify(t *testing.T) {
helpCmd.Env = newEnvs
out, ec, err := runCommandWithOutput(helpCmd)
if err != nil || ec != 0 {
- t.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec)
+ c.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec)
}
lines := strings.Split(out, "\n")
for _, line := range lines {
if len(line) > 80 {
- t.Fatalf("Help for %q is too long(%d chars):\n%s", cmd,
+ c.Fatalf("Help for %q is too long(%d chars):\n%s", cmd,
len(line), line)
}
if scanForHome && strings.Contains(line, `"`+home) {
- t.Fatalf("Help for %q should use ~ instead of %q on:\n%s",
+ c.Fatalf("Help for %q should use ~ instead of %q on:\n%s",
cmd, home, line)
}
i := strings.Index(line, "~")
if i >= 0 && i != len(line)-1 && line[i+1] != '/' {
- t.Fatalf("Help for %q should not have used ~:\n%s", cmd, line)
+ c.Fatalf("Help for %q should not have used ~:\n%s", cmd, line)
}
// If a line starts with 4 spaces then assume someone
// added a multi-line description for an option and we need
// to flag it
if strings.HasPrefix(line, " ") {
- t.Fatalf("Help for %q should not have a multi-line option: %s", cmd, line)
+ c.Fatalf("Help for %q should not have a multi-line option: %s", cmd, line)
}
// Options should NOT end with a period
if strings.HasPrefix(line, " -") && strings.HasSuffix(line, ".") {
- t.Fatalf("Help for %q should not end with a period: %s", cmd, line)
+ c.Fatalf("Help for %q should not end with a period: %s", cmd, line)
}
// Options should NOT end with a space
if strings.HasSuffix(line, " ") {
- t.Fatalf("Help for %q should not end with a space: %s", cmd, line)
+ c.Fatalf("Help for %q should not end with a space: %s", cmd, line)
}
}
@@ -146,10 +146,38 @@ func TestHelpTextVerify(t *testing.T) {
expected := 39
if len(cmds) != expected {
- t.Fatalf("Wrong # of cmds(%d), it should be: %d\nThe list:\n%q",
+ c.Fatalf("Wrong # of cmds(%d), it should be: %d\nThe list:\n%q",
len(cmds), expected, cmds)
}
}
- logDone("help - verify text")
+}
+
+func (s *DockerSuite) TestHelpErrorStderr(c *check.C) {
+ // If we had a generic CLI test file this one shoudl go in there
+
+ cmd := exec.Command(dockerBinary, "boogie")
+ out, ec, err := runCommandWithOutput(cmd)
+ if err == nil || ec == 0 {
+ c.Fatalf("Boogie command should have failed")
+ }
+
+ expected := "docker: 'boogie' is not a docker command. See 'docker --help'.\n"
+ if out != expected {
+ c.Fatalf("Bad output from boogie\nGot:%s\nExpected:%s", out, expected)
+ }
+
+ cmd = exec.Command(dockerBinary, "rename", "foo", "bar")
+ out, ec, err = runCommandWithOutput(cmd)
+ if err == nil || ec == 0 {
+ c.Fatalf("Rename should have failed")
+ }
+
+ expected = `Error response from daemon: no such id: foo
+Error: failed to rename container named foo
+`
+ if out != expected {
+ c.Fatalf("Bad output from rename\nGot:%s\nExpected:%s", out, expected)
+ }
+
}
diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go
index ecb0a3a07..d229f1a8c 100644
--- a/integration-cli/docker_cli_history_test.go
+++ b/integration-cli/docker_cli_history_test.go
@@ -3,15 +3,17 @@ package main
import (
"fmt"
"os/exec"
+ "regexp"
+ "strconv"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
// This is a heisen-test. Because the created timestamp of images and the behavior of
// sort is not predictable it doesn't always fail.
-func TestBuildHistory(t *testing.T) {
+func (s *DockerSuite) TestBuildHistory(c *check.C) {
name := "testbuildhistory"
- defer deleteImages(name)
_, err := buildImage(name, `FROM busybox
RUN echo "A"
RUN echo "B"
@@ -42,12 +44,12 @@ RUN echo "Z"`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, exitCode, err := runCommandWithOutput(exec.Command(dockerBinary, "history", "testbuildhistory"))
if err != nil || exitCode != 0 {
- t.Fatalf("failed to get image history: %s, %v", out, err)
+ c.Fatalf("failed to get image history: %s, %v", out, err)
}
actualValues := strings.Split(out, "\n")[1:27]
@@ -58,27 +60,101 @@ RUN echo "Z"`,
actualValue := actualValues[i]
if !strings.Contains(actualValue, echoValue) {
- t.Fatalf("Expected layer \"%s\", but was: %s", expectedValues[i], actualValue)
+ c.Fatalf("Expected layer \"%s\", but was: %s", expectedValues[i], actualValue)
}
}
- logDone("history - build history")
}
-func TestHistoryExistentImage(t *testing.T) {
+func (s *DockerSuite) TestHistoryExistentImage(c *check.C) {
historyCmd := exec.Command(dockerBinary, "history", "busybox")
_, exitCode, err := runCommandWithOutput(historyCmd)
if err != nil || exitCode != 0 {
- t.Fatal("failed to get image history")
+ c.Fatal("failed to get image history")
}
- logDone("history - history on existent image must pass")
}
-func TestHistoryNonExistentImage(t *testing.T) {
+func (s *DockerSuite) TestHistoryNonExistentImage(c *check.C) {
historyCmd := exec.Command(dockerBinary, "history", "testHistoryNonExistentImage")
_, exitCode, err := runCommandWithOutput(historyCmd)
if err == nil || exitCode == 0 {
- t.Fatal("history on a non-existent image didn't result in a non-zero exit status")
+ c.Fatal("history on a non-existent image didn't result in a non-zero exit status")
+ }
+}
+
+func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) {
+ name := "testhistoryimagewithcomment"
+
+ // make a image through docker commit [ -m messages ]
+ //runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("failed to run container: %s, %v", out, err)
+ }
+
+ waitCmd := exec.Command(dockerBinary, "wait", name)
+ if out, _, err := runCommandWithOutput(waitCmd); err != nil {
+ c.Fatalf("error thrown while waiting for container: %s, %v", out, err)
+ }
+
+ comment := "This_is_a_comment"
+
+ commitCmd := exec.Command(dockerBinary, "commit", "-m="+comment, name, name)
+ if out, _, err := runCommandWithOutput(commitCmd); err != nil {
+ c.Fatalf("failed to commit container to image: %s, %v", out, err)
+ }
+
+ // test docker history to check comment messages
+ historyCmd := exec.Command(dockerBinary, "history", name)
+ out, exitCode, err := runCommandWithOutput(historyCmd)
+ if err != nil || exitCode != 0 {
+ c.Fatalf("failed to get image history: %s, %v", out, err)
+ }
+
+ outputTabs := strings.Fields(strings.Split(out, "\n")[1])
+ //outputTabs := regexp.MustCompile(" +").Split(outputLine, -1)
+ actualValue := outputTabs[len(outputTabs)-1]
+
+ if !strings.Contains(actualValue, comment) {
+ c.Fatalf("Expected comments %q, but found %q", comment, actualValue)
+ }
+
+}
+
+func (s *DockerSuite) TestHistoryHumanOptionFalse(c *check.C) {
+ out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "history", "--human=false", "busybox"))
+ lines := strings.Split(out, "\n")
+ sizeColumnRegex, _ := regexp.Compile("SIZE +")
+ indices := sizeColumnRegex.FindStringIndex(lines[0])
+ startIndex := indices[0]
+ endIndex := indices[1]
+ for i := 1; i < len(lines)-1; i++ {
+ if endIndex > len(lines[i]) {
+ endIndex = len(lines[i])
+ }
+ sizeString := lines[i][startIndex:endIndex]
+ if _, err := strconv.Atoi(strings.TrimSpace(sizeString)); err != nil {
+ c.Fatalf("The size '%s' was not an Integer", sizeString)
+ }
+ }
+}
+
+func (s *DockerSuite) TestHistoryHumanOptionTrue(c *check.C) {
+ out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "history", "--human=true", "busybox"))
+ lines := strings.Split(out, "\n")
+ sizeColumnRegex, _ := regexp.Compile("SIZE +")
+ humanSizeRegex, _ := regexp.Compile("^\\d+.*B$") // Matches human sizes like 10 MB, 3.2 KB, etc
+ indices := sizeColumnRegex.FindStringIndex(lines[0])
+ startIndex := indices[0]
+ endIndex := indices[1]
+ for i := 1; i < len(lines)-1; i++ {
+ if endIndex > len(lines[i]) {
+ endIndex = len(lines[i])
+ }
+ sizeString := lines[i][startIndex:endIndex]
+ if matchSuccess := humanSizeRegex.MatchString(strings.TrimSpace(sizeString)); !matchSuccess {
+ c.Fatalf("The size '%s' was not in human format", sizeString)
+ }
}
- logDone("history - history on non-existent image must pass")
}
diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go
index 694971191..0ab646250 100644
--- a/integration-cli/docker_cli_images_test.go
+++ b/integration-cli/docker_cli_images_test.go
@@ -6,137 +6,124 @@ import (
"reflect"
"sort"
"strings"
- "testing"
"time"
- "github.com/docker/docker/pkg/common"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/go-check/check"
)
-func TestImagesEnsureImageIsListed(t *testing.T) {
+func (s *DockerSuite) TestImagesEnsureImageIsListed(c *check.C) {
imagesCmd := exec.Command(dockerBinary, "images")
out, _, err := runCommandWithOutput(imagesCmd)
if err != nil {
- t.Fatalf("listing images failed with errors: %s, %v", out, err)
+ c.Fatalf("listing images failed with errors: %s, %v", out, err)
}
if !strings.Contains(out, "busybox") {
- t.Fatal("images should've listed busybox")
+ c.Fatal("images should've listed busybox")
}
- logDone("images - busybox should be listed")
}
-func TestImagesOrderedByCreationDate(t *testing.T) {
- defer deleteImages("order:test_a")
- defer deleteImages("order:test_c")
- defer deleteImages("order:test_b")
+func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) {
id1, err := buildImage("order:test_a",
`FROM scratch
MAINTAINER dockerio1`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
time.Sleep(time.Second)
id2, err := buildImage("order:test_c",
`FROM scratch
MAINTAINER dockerio2`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
time.Sleep(time.Second)
id3, err := buildImage("order:test_b",
`FROM scratch
MAINTAINER dockerio3`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc"))
if err != nil {
- t.Fatalf("listing images failed with errors: %s, %v", out, err)
+ c.Fatalf("listing images failed with errors: %s, %v", out, err)
}
imgs := strings.Split(out, "\n")
if imgs[0] != id3 {
- t.Fatalf("First image must be %s, got %s", id3, imgs[0])
+ c.Fatalf("First image must be %s, got %s", id3, imgs[0])
}
if imgs[1] != id2 {
- t.Fatalf("Second image must be %s, got %s", id2, imgs[1])
+ c.Fatalf("Second image must be %s, got %s", id2, imgs[1])
}
if imgs[2] != id1 {
- t.Fatalf("Third image must be %s, got %s", id1, imgs[2])
+ c.Fatalf("Third image must be %s, got %s", id1, imgs[2])
}
- logDone("images - ordering by creation date")
}
-func TestImagesErrorWithInvalidFilterNameTest(t *testing.T) {
+func (s *DockerSuite) TestImagesErrorWithInvalidFilterNameTest(c *check.C) {
imagesCmd := exec.Command(dockerBinary, "images", "-f", "FOO=123")
out, _, err := runCommandWithOutput(imagesCmd)
if !strings.Contains(out, "Invalid filter") {
- t.Fatalf("error should occur when listing images with invalid filter name FOO, %s, %v", out, err)
+ c.Fatalf("error should occur when listing images with invalid filter name FOO, %s, %v", out, err)
}
- logDone("images - invalid filter name check working")
}
-func TestImagesFilterLabel(t *testing.T) {
+func (s *DockerSuite) TestImagesFilterLabel(c *check.C) {
imageName1 := "images_filter_test1"
imageName2 := "images_filter_test2"
imageName3 := "images_filter_test3"
- defer deleteAllContainers()
- defer deleteImages(imageName1)
- defer deleteImages(imageName2)
- defer deleteImages(imageName3)
image1ID, err := buildImage(imageName1,
`FROM scratch
LABEL match me`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
image2ID, err := buildImage(imageName2,
`FROM scratch
LABEL match="me too"`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
image3ID, err := buildImage(imageName3,
`FROM scratch
LABEL nomatch me`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
out = strings.TrimSpace(out)
if (!strings.Contains(out, image1ID) && !strings.Contains(out, image2ID)) || strings.Contains(out, image3ID) {
- t.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out)
+ c.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out)
}
cmd = exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match=me too")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
out = strings.TrimSpace(out)
if out != image2ID {
- t.Fatalf("Expected %s got %s", image2ID, out)
+ c.Fatalf("Expected %s got %s", image2ID, out)
}
- logDone("images - filter label")
}
-func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
+func (s *DockerSuite) TestImagesFilterSpaceTrimCase(c *check.C) {
imageName := "images_filter_test"
- defer deleteAllContainers()
- defer deleteImages(imageName)
buildImage(imageName,
`FROM scratch
RUN touch /test/foo
@@ -156,7 +143,7 @@ func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
cmd := exec.Command(dockerBinary, "images", "-q", "-f", filter)
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
listing := strings.Split(out, "\n")
sort.Strings(listing)
@@ -172,50 +159,45 @@ func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
}
fmt.Print("")
}
- t.Fatalf("All output must be the same")
+ c.Fatalf("All output must be the same")
}
}
- logDone("images - white space trimming and lower casing")
}
-func TestImagesEnsureDanglingImageOnlyListedOnce(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) {
// create container 1
- c := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
- out, _, err := runCommandWithOutput(c)
+ cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+ out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error running busybox: %s, %v", out, err)
+ c.Fatalf("error running busybox: %s, %v", out, err)
}
containerId1 := strings.TrimSpace(out)
// tag as foobox
- c = exec.Command(dockerBinary, "commit", containerId1, "foobox")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "commit", containerId1, "foobox")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error tagging foobox: %s", err)
+ c.Fatalf("error tagging foobox: %s", err)
}
- imageId := common.TruncateID(strings.TrimSpace(out))
- defer deleteImages(imageId)
+ imageId := stringid.TruncateID(strings.TrimSpace(out))
// overwrite the tag, making the previous image dangling
- c = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("error tagging foobox: %s", err)
+ c.Fatalf("error tagging foobox: %s", err)
}
- defer deleteImages("foobox")
- c = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true")
- out, _, err = runCommandWithOutput(c)
+ cmd = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("listing images failed with errors: %s, %v", out, err)
+ c.Fatalf("listing images failed with errors: %s, %v", out, err)
}
if e, a := 1, strings.Count(out, imageId); e != a {
- t.Fatalf("expected 1 dangling image, got %d: %s", a, out)
+ c.Fatalf("expected 1 dangling image, got %d: %s", a, out)
}
- logDone("images - dangling image only listed once")
}
diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go
index 1a7ee6f20..f4bd08521 100644
--- a/integration-cli/docker_cli_import_test.go
+++ b/integration-cli/docker_cli_import_test.go
@@ -3,41 +3,50 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestImportDisplay(t *testing.T) {
+func (s *DockerSuite) TestImportDisplay(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal("failed to create a container", out, err)
+ c.Fatal("failed to create a container", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "export", cleanedContainerID),
exec.Command(dockerBinary, "import", "-"),
)
if err != nil {
- t.Errorf("import failed with errors: %v, output: %q", err, out)
+ c.Errorf("import failed with errors: %v, output: %q", err, out)
}
if n := strings.Count(out, "\n"); n != 1 {
- t.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out)
+ c.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out)
}
image := strings.TrimSpace(out)
- defer deleteImages(image)
runCmd = exec.Command(dockerBinary, "run", "--rm", image, "true")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal("failed to create a container", out, err)
+ c.Fatal("failed to create a container", out, err)
}
if out != "" {
- t.Fatalf("command output should've been nothing, was %q", out)
+ c.Fatalf("command output should've been nothing, was %q", out)
}
- logDone("import - display is fine, imported image runs")
+}
+
+func (s *DockerSuite) TestImportBadURL(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "import", "http://nourl/bad")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err == nil {
+ c.Fatal("import was supposed to fail but didn't")
+ }
+ if !strings.Contains(out, "dial tcp") {
+ c.Fatalf("expected an error msg but didn't get one:\n%s", out)
+ }
}
diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go
index 2e8239a4b..cf7738d14 100644
--- a/integration-cli/docker_cli_info_test.go
+++ b/integration-cli/docker_cli_info_test.go
@@ -3,24 +3,40 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/docker/docker/utils"
+ "github.com/go-check/check"
)
// ensure docker info succeeds
-func TestInfoEnsureSucceeds(t *testing.T) {
+func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) {
versionCmd := exec.Command(dockerBinary, "info")
out, exitCode, err := runCommandWithOutput(versionCmd)
if err != nil || exitCode != 0 {
- t.Fatalf("failed to execute docker info: %s, %v", out, err)
+ c.Fatalf("failed to execute docker info: %s, %v", out, err)
}
- stringsToCheck := []string{"Containers:", "Execution Driver:", "Kernel Version:"}
+ // always shown fields
+ stringsToCheck := []string{
+ "ID:",
+ "Containers:",
+ "Images:",
+ "Execution Driver:",
+ "Logging Driver:",
+ "Operating System:",
+ "CPUs:",
+ "Total Memory:",
+ "Kernel Version:",
+ "Storage Driver:",
+ }
+
+ if utils.ExperimentalBuild() {
+ stringsToCheck = append(stringsToCheck, "Experimental: true")
+ }
for _, linePrefix := range stringsToCheck {
if !strings.Contains(out, linePrefix) {
- t.Errorf("couldn't find string %v in output", linePrefix)
+ c.Errorf("couldn't find string %v in output", linePrefix)
}
}
-
- logDone("info - verify that it works")
}
diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go
index cf42217ac..066e014b4 100644
--- a/integration-cli/docker_cli_inspect_test.go
+++ b/integration-cli/docker_cli_inspect_test.go
@@ -1,23 +1,91 @@
package main
import (
+ "fmt"
"os/exec"
+ "strconv"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestInspectImage(t *testing.T) {
+func (s *DockerSuite) TestInspectImage(c *check.C) {
imageTest := "emptyfs"
imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"
- imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Id}}'", imageTest)
+ id, err := inspectField(imageTest, "Id")
+ c.Assert(err, check.IsNil)
+
+ if id != imageTestID {
+ c.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id)
+ }
+
+}
+
+func (s *DockerSuite) TestInspectInt64(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-m=300M", "busybox", "true")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
+ }
+
+ out = strings.TrimSpace(out)
+
+ inspectOut, err := inspectField(out, "HostConfig.Memory")
+ c.Assert(err, check.IsNil)
+
+ if inspectOut != "314572800" {
+ c.Fatalf("inspect got wrong value, got: %q, expected: 314572800", inspectOut)
+ }
+}
+
+func (s *DockerSuite) TestInspectImageFilterInt(c *check.C) {
+ imageTest := "emptyfs"
+ out, err := inspectField(imageTest, "Size")
+ c.Assert(err, check.IsNil)
+
+ size, err := strconv.Atoi(out)
+ if err != nil {
+ c.Fatalf("failed to inspect size of the image: %s, %v", out, err)
+ }
+
+ //now see if the size turns out to be the same
+ formatStr := fmt.Sprintf("--format='{{eq .Size %d}}'", size)
+ imagesCmd := exec.Command(dockerBinary, "inspect", formatStr, imageTest)
out, exitCode, err := runCommandWithOutput(imagesCmd)
if exitCode != 0 || err != nil {
- t.Fatalf("failed to inspect image: %s, %v", out, err)
+ c.Fatalf("failed to inspect image: %s, %v", out, err)
}
+ if result, err := strconv.ParseBool(strings.TrimSuffix(out, "\n")); err != nil || !result {
+ c.Fatalf("Expected size: %d for image: %s but received size: %s", size, imageTest, strings.TrimSuffix(out, "\n"))
+ }
+}
- if id := strings.TrimSuffix(out, "\n"); id != imageTestID {
- t.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id)
+func (s *DockerSuite) TestInspectContainerFilterInt(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "cat")
+ runCmd.Stdin = strings.NewReader("blahblah")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
+ }
+
+ id := strings.TrimSpace(out)
+
+ out, err = inspectField(id, "State.ExitCode")
+ c.Assert(err, check.IsNil)
+
+ exitCode, err := strconv.Atoi(out)
+ if err != nil {
+ c.Fatalf("failed to inspect exitcode of the container: %s, %v", out, err)
+ }
+
+ //now get the exit code to verify
+ formatStr := fmt.Sprintf("--format='{{eq .State.ExitCode %d}}'", exitCode)
+ runCmd = exec.Command(dockerBinary, "inspect", formatStr, id)
+ out, _, err = runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("failed to inspect container: %s, %v", out, err)
+ }
+ if result, err := strconv.ParseBool(strings.TrimSuffix(out, "\n")); err != nil || !result {
+ c.Fatalf("Expected exitcode: %d for container: %s", exitCode, id)
}
-
- logDone("inspect - inspect an image")
}
diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go
index 33135a3be..1371a0be0 100644
--- a/integration-cli/docker_cli_kill_test.go
+++ b/integration-cli/docker_cli_kill_test.go
@@ -3,73 +3,58 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestKillContainer(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10")
+func (s *DockerSuite) TestKillContainer(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
-
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("out should've been a container id: %s, %v", out, err)
- }
+ cleanedContainerID := strings.TrimSpace(out)
+ c.Assert(waitRun(cleanedContainerID), check.IsNil)
killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID)
if out, _, err = runCommandWithOutput(killCmd); err != nil {
- t.Fatalf("failed to kill container: %s, %v", out, err)
+ c.Fatalf("failed to kill container: %s, %v", out, err)
}
listRunningContainersCmd := exec.Command(dockerBinary, "ps", "-q")
out, _, err = runCommandWithOutput(listRunningContainersCmd)
if err != nil {
- t.Fatalf("failed to list running containers: %s, %v", out, err)
+ c.Fatalf("failed to list running containers: %s, %v", out, err)
}
if strings.Contains(out, cleanedContainerID) {
- t.Fatal("killed container is still running")
+ c.Fatal("killed container is still running")
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("kill - kill container running sleep 10")
}
-func TestKillDifferentUserContainer(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-u", "daemon", "-d", "busybox", "sh", "-c", "sleep 10")
+func (s *DockerSuite) TestKillDifferentUserContainer(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-u", "daemon", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
-
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("out should've been a container id: %s, %v", out, err)
- }
+ cleanedContainerID := strings.TrimSpace(out)
+ c.Assert(waitRun(cleanedContainerID), check.IsNil)
killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID)
if out, _, err = runCommandWithOutput(killCmd); err != nil {
- t.Fatalf("failed to kill container: %s, %v", out, err)
+ c.Fatalf("failed to kill container: %s, %v", out, err)
}
listRunningContainersCmd := exec.Command(dockerBinary, "ps", "-q")
out, _, err = runCommandWithOutput(listRunningContainersCmd)
if err != nil {
- t.Fatalf("failed to list running containers: %s, %v", out, err)
+ c.Fatalf("failed to list running containers: %s, %v", out, err)
}
if strings.Contains(out, cleanedContainerID) {
- t.Fatal("killed container is still running")
+ c.Fatal("killed container is still running")
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("kill - kill container running sleep 10 from a different user")
}
diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go
index efee8d04e..1f7432a8b 100644
--- a/integration-cli/docker_cli_links_test.go
+++ b/integration-cli/docker_cli_links_test.go
@@ -8,89 +8,79 @@ import (
"reflect"
"regexp"
"strings"
- "testing"
"time"
- "github.com/docker/docker/pkg/iptables"
+ "github.com/go-check/check"
)
-func TestLinksEtcHostsRegularFile(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestLinksEtcHostsRegularFile(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "ls", "-la", "/etc/hosts")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !strings.HasPrefix(out, "-") {
- t.Errorf("/etc/hosts should be a regular file")
+ c.Errorf("/etc/hosts should be a regular file")
}
- logDone("link - /etc/hosts is a regular file")
}
-func TestLinksEtcHostsContentMatch(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksEtcHostsContentMatch(c *check.C) {
+ testRequires(c, SameHostDaemon)
runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "cat", "/etc/hosts")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
hosts, err := ioutil.ReadFile("/etc/hosts")
if os.IsNotExist(err) {
- t.Skip("/etc/hosts does not exist, skip this test")
+ c.Skip("/etc/hosts does not exist, skip this test")
}
if out != string(hosts) {
- t.Errorf("container")
+ c.Errorf("container")
}
- logDone("link - /etc/hosts matches hosts copy")
}
-func TestLinksPingUnlinkedContainers(t *testing.T) {
+func (s *DockerSuite) TestLinksPingUnlinkedContainers(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--rm", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1")
exitCode, err := runCommand(runCmd)
if exitCode == 0 {
- t.Fatal("run ping did not fail")
+ c.Fatal("run ping did not fail")
} else if exitCode != 1 {
- t.Fatalf("run ping failed with errors: %v", err)
+ c.Fatalf("run ping failed with errors: %v", err)
}
- logDone("links - ping unlinked container")
}
// Test for appropriate error when calling --link with an invalid target container
-func TestLinksInvalidContainerTarget(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksInvalidContainerTarget(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--link", "bogus:alias", "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err == nil {
- t.Fatal("an invalid container target should produce an error")
+ c.Fatal("an invalid container target should produce an error")
}
if !strings.Contains(out, "Could not get container") {
- t.Fatal("error output expected 'Could not get container', but got %q instead; err: %v", out, err)
+ c.Fatalf("error output expected 'Could not get container', but got %q instead; err: %v", out, err)
}
- logDone("links - linking to non-existent container should not work")
}
-func TestLinksPingLinkedContainers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksPingLinkedContainers(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "container1", "--hostname", "fred", "busybox", "top")
if _, err := runCommand(runCmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runCmd = exec.Command(dockerBinary, "run", "-d", "--name", "container2", "--hostname", "wilma", "busybox", "top")
if _, err := runCommand(runCmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runArgs := []string{"run", "--rm", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "sh", "-c"}
@@ -98,74 +88,43 @@ func TestLinksPingLinkedContainers(t *testing.T) {
// test ping by alias, ping by name, and ping by hostname
// 1. Ping by alias
- dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "alias1", "alias2"))...)
+ dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "alias1", "alias2"))...)
// 2. Ping by container name
- dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "container1", "container2"))...)
+ dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "container1", "container2"))...)
// 3. Ping by hostname
- dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "fred", "wilma"))...)
+ dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "fred", "wilma"))...)
- logDone("links - ping linked container")
}
-func TestLinksPingLinkedContainersAfterRename(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksPingLinkedContainersAfterRename(c *check.C) {
- out, _, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10")
- idA := stripTrailingCharacters(out)
- out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10")
- idB := stripTrailingCharacters(out)
- dockerCmd(t, "rename", "container1", "container_new")
- dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1")
- dockerCmd(t, "kill", idA)
- dockerCmd(t, "kill", idB)
+ out, _ := dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top")
+ idA := strings.TrimSpace(out)
+ out, _ = dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top")
+ idB := strings.TrimSpace(out)
+ dockerCmd(c, "rename", "container1", "container_new")
+ dockerCmd(c, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1")
+ dockerCmd(c, "kill", idA)
+ dockerCmd(c, "kill", idB)
- logDone("links - ping linked container after rename")
}
-func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
-
- dockerCmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "sleep", "10")
- dockerCmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "sleep", "10")
-
- childIP := findContainerIP(t, "child")
- parentIP := findContainerIP(t, "parent")
-
- sourceRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"}
- destinationRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"}
- if !iptables.Exists("filter", "DOCKER", sourceRule...) || !iptables.Exists("filter", "DOCKER", destinationRule...) {
- t.Fatal("Iptables rules not found")
- }
-
- dockerCmd(t, "rm", "--link", "parent/http")
- if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) {
- t.Fatal("Iptables rules should be removed when unlink")
- }
-
- dockerCmd(t, "kill", "child")
- dockerCmd(t, "kill", "parent")
-
- logDone("link - verify iptables when link and unlink")
-}
-
-func TestLinksInspectLinksStarted(t *testing.T) {
+func (s *DockerSuite) TestLinksInspectLinksStarted(c *check.C) {
var (
expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}}
result []string
)
- defer deleteAllContainers()
- dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10")
- dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10")
- dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "sleep", "10")
+ dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top")
+ dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top")
+ dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "top")
links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
err = unmarshalJSON([]byte(links), &result)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
output := convertSliceOfStringsToMap(result)
@@ -173,28 +132,26 @@ func TestLinksInspectLinksStarted(t *testing.T) {
equal := reflect.DeepEqual(output, expected)
if !equal {
- t.Fatalf("Links %s, expected %s", result, expected)
+ c.Fatalf("Links %s, expected %s", result, expected)
}
- logDone("link - links in started container inspect")
}
-func TestLinksInspectLinksStopped(t *testing.T) {
+func (s *DockerSuite) TestLinksInspectLinksStopped(c *check.C) {
var (
expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}}
result []string
)
- defer deleteAllContainers()
- dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10")
- dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10")
- dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true")
+ dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top")
+ dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top")
+ dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true")
links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
err = unmarshalJSON([]byte(links), &result)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
output := convertSliceOfStringsToMap(result)
@@ -202,47 +159,42 @@ func TestLinksInspectLinksStopped(t *testing.T) {
equal := reflect.DeepEqual(output, expected)
if !equal {
- t.Fatalf("Links %s, but expected %s", result, expected)
+ c.Fatalf("Links %s, but expected %s", result, expected)
}
- logDone("link - links in stopped container inspect")
}
-func TestLinksNotStartedParentNotFail(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksNotStartedParentNotFail(c *check.C) {
runCmd := exec.Command(dockerBinary, "create", "--name=first", "busybox", "top")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "create", "--name=second", "--link=first:first", "busybox", "top")
out, _, _, err = runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "start", "first")
out, _, _, err = runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- logDone("link - container start successfully updating stopped parent links")
}
-func TestLinksHostsFilesInject(t *testing.T) {
- testRequires(t, SameHostDaemon, ExecSupport)
-
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksHostsFilesInject(c *check.C) {
+ testRequires(c, SameHostDaemon, ExecSupport)
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "--name", "one", "busybox", "top"))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
idOne := strings.TrimSpace(out)
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "--name", "two", "--link", "one:onetwo", "busybox", "top"))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
idTwo := strings.TrimSpace(out)
@@ -251,89 +203,124 @@ func TestLinksHostsFilesInject(t *testing.T) {
contentOne, err := readContainerFileWithExec(idOne, "/etc/hosts")
if err != nil {
- t.Fatal(err, string(contentOne))
+ c.Fatal(err, string(contentOne))
}
contentTwo, err := readContainerFileWithExec(idTwo, "/etc/hosts")
if err != nil {
- t.Fatal(err, string(contentTwo))
+ c.Fatal(err, string(contentTwo))
}
if !strings.Contains(string(contentTwo), "onetwo") {
- t.Fatal("Host is not present in updated hosts file", string(contentTwo))
+ c.Fatal("Host is not present in updated hosts file", string(contentTwo))
}
- logDone("link - ensure containers hosts files are updated with the link alias.")
}
-func TestLinksNetworkHostContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksNetworkHostContainer(c *check.C) {
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--net", "host", "--name", "host_container", "busybox", "top"))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "should_fail", "--link", "host_container:tester", "busybox", "true"))
- if err == nil || !strings.Contains(out, "--net=host can't be used with links. This would result in undefined behavior.") {
- t.Fatalf("Running container linking to a container with --net host should have failed: %s", out)
+ if err == nil || !strings.Contains(out, "--net=host can't be used with links. This would result in undefined behavior") {
+ c.Fatalf("Running container linking to a container with --net host should have failed: %s", out)
}
- logDone("link - error thrown when linking to container with --net host")
}
-func TestLinksUpdateOnRestart(t *testing.T) {
- testRequires(t, SameHostDaemon, ExecSupport)
-
- defer deleteAllContainers()
+func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) {
+ testRequires(c, SameHostDaemon, ExecSupport)
if out, err := exec.Command(dockerBinary, "run", "-d", "--name", "one", "busybox", "top").CombinedOutput(); err != nil {
- t.Fatal(err, string(out))
+ c.Fatal(err, string(out))
}
out, err := exec.Command(dockerBinary, "run", "-d", "--name", "two", "--link", "one:onetwo", "--link", "one:one", "busybox", "top").CombinedOutput()
if err != nil {
- t.Fatal(err, string(out))
+ c.Fatal(err, string(out))
}
id := strings.TrimSpace(string(out))
realIP, err := inspectField("one", "NetworkSettings.IPAddress")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
content, err := readContainerFileWithExec(id, "/etc/hosts")
if err != nil {
- t.Fatal(err, string(content))
+ c.Fatal(err, string(content))
}
getIP := func(hosts []byte, hostname string) string {
re := regexp.MustCompile(fmt.Sprintf(`(\S*)\t%s`, regexp.QuoteMeta(hostname)))
matches := re.FindSubmatch(hosts)
if matches == nil {
- t.Fatalf("Hostname %s have no matches in hosts", hostname)
+ c.Fatalf("Hostname %s have no matches in hosts", hostname)
}
return string(matches[1])
}
if ip := getIP(content, "one"); ip != realIP {
- t.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip)
+ c.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip)
}
if ip := getIP(content, "onetwo"); ip != realIP {
- t.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip)
+ c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip)
}
if out, err := exec.Command(dockerBinary, "restart", "one").CombinedOutput(); err != nil {
- t.Fatal(err, string(out))
+ c.Fatal(err, string(out))
}
realIP, err = inspectField("one", "NetworkSettings.IPAddress")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
content, err = readContainerFileWithExec(id, "/etc/hosts")
if err != nil {
- t.Fatal(err, string(content))
+ c.Fatal(err, string(content))
}
if ip := getIP(content, "one"); ip != realIP {
- t.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip)
+ c.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip)
}
if ip := getIP(content, "onetwo"); ip != realIP {
- t.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip)
+ c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip)
}
- logDone("link - ensure containers hosts files are updated on restart")
+}
+
+func (s *DockerSuite) TestLinksEnvs(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-e", "e1=", "-e", "e2=v2", "-e", "e3=v3=v3", "--name=first", "busybox", "top")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ c.Fatalf("Run of first failed: %s\n%s", out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--name=second", "--link=first:first", "busybox", "env")
+
+ out, stde, rc, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil || rc != 0 {
+ c.Fatalf("run of 2nd failed: rc: %d, out: %s\n err: %s", rc, out, stde)
+ }
+
+ if !strings.Contains(out, "FIRST_ENV_e1=\n") ||
+ !strings.Contains(out, "FIRST_ENV_e2=v2") ||
+ !strings.Contains(out, "FIRST_ENV_e3=v3=v3") {
+ c.Fatalf("Incorrect output: %s", out)
+ }
+}
+
+func (s *DockerSuite) TestLinkShortDefinition(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "shortlinkdef", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ cid := strings.TrimSpace(out)
+ c.Assert(waitRun(cid), check.IsNil)
+
+ runCmd = exec.Command(dockerBinary, "run", "-d", "--name", "link2", "--link", "shortlinkdef", "busybox", "top")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ cid2 := strings.TrimSpace(out)
+ c.Assert(waitRun(cid2), check.IsNil)
+
+ links, err := inspectFieldJSON(cid2, "HostConfig.Links")
+ c.Assert(err, check.IsNil)
+ c.Assert(links, check.Equals, "[\"/shortlinkdef:/link2/shortlinkdef\"]")
}
diff --git a/integration-cli/docker_cli_login_test.go b/integration-cli/docker_cli_login_test.go
index 9bf90f3ad..3b4431d2d 100644
--- a/integration-cli/docker_cli_login_test.go
+++ b/integration-cli/docker_cli_login_test.go
@@ -3,10 +3,11 @@ package main
import (
"bytes"
"os/exec"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestLoginWithoutTTY(t *testing.T) {
+func (s *DockerSuite) TestLoginWithoutTTY(c *check.C) {
cmd := exec.Command(dockerBinary, "login")
// Send to stdin so the process does not get the TTY
@@ -14,8 +15,7 @@ func TestLoginWithoutTTY(t *testing.T) {
// run the command and block until it's done
if err := cmd.Run(); err == nil {
- t.Fatal("Expected non nil err when loginning in & TTY not available")
+ c.Fatal("Expected non nil err when loginning in & TTY not available")
}
- logDone("login - login without TTY")
}
diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go
index b86a50480..2e41f8a19 100644
--- a/integration-cli/docker_cli_logs_test.go
+++ b/integration-cli/docker_cli_logs_test.go
@@ -1,119 +1,110 @@
package main
import (
+ "encoding/json"
"fmt"
+ "io"
"os/exec"
"regexp"
+ "strconv"
"strings"
- "testing"
"time"
"github.com/docker/docker/pkg/timeutils"
+ "github.com/go-check/check"
)
// This used to work, it test a log of PageSize-1 (gh#4851)
-func TestLogsContainerSmallerThanPage(t *testing.T) {
+func (s *DockerSuite) TestLogsContainerSmallerThanPage(c *check.C) {
testLen := 32767
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen))
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
if len(out) != testLen+1 {
- t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out))
+ c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out))
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("logs - logs container running echo smaller than page size")
}
// Regression test: When going over the PageSize, it used to panic (gh#4851)
-func TestLogsContainerBiggerThanPage(t *testing.T) {
+func (s *DockerSuite) TestLogsContainerBiggerThanPage(c *check.C) {
testLen := 32768
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen))
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
if len(out) != testLen+1 {
- t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out))
+ c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out))
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("logs - logs container running echo bigger than page size")
}
// Regression test: When going much over the PageSize, it used to block (gh#4851)
-func TestLogsContainerMuchBiggerThanPage(t *testing.T) {
+func (s *DockerSuite) TestLogsContainerMuchBiggerThanPage(c *check.C) {
testLen := 33000
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen))
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
if len(out) != testLen+1 {
- t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out))
+ c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out))
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("logs - logs container running echo much bigger than page size")
}
-func TestLogsTimestamps(t *testing.T) {
+func (s *DockerSuite) TestLogsTimestamps(c *check.C) {
testLen := 100
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo =; done;", testLen))
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", "-t", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
lines := strings.Split(out, "\n")
if len(lines) != testLen+1 {
- t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines))
+ c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines))
}
ts := regexp.MustCompile(`^.* `)
@@ -122,180 +113,235 @@ func TestLogsTimestamps(t *testing.T) {
if l != "" {
_, err := time.Parse(timeutils.RFC3339NanoFixed+" ", ts.FindString(l))
if err != nil {
- t.Fatalf("Failed to parse timestamp from %v: %v", l, err)
+ c.Fatalf("Failed to parse timestamp from %v: %v", l, err)
}
if l[29] != 'Z' { // ensure we have padded 0's
- t.Fatalf("Timestamp isn't padded properly: %s", l)
+ c.Fatalf("Timestamp isn't padded properly: %s", l)
}
}
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("logs - logs with timestamps")
}
-func TestLogsSeparateStderr(t *testing.T) {
+func (s *DockerSuite) TestLogsSeparateStderr(c *check.C) {
msg := "stderr_log"
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("echo %s 1>&2", msg))
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID)
stdout, stderr, _, err := runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
if stdout != "" {
- t.Fatalf("Expected empty stdout stream, got %v", stdout)
+ c.Fatalf("Expected empty stdout stream, got %v", stdout)
}
stderr = strings.TrimSpace(stderr)
if stderr != msg {
- t.Fatalf("Expected %v in stderr stream, got %v", msg, stderr)
+ c.Fatalf("Expected %v in stderr stream, got %v", msg, stderr)
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("logs - separate stderr (without pseudo-tty)")
}
-func TestLogsStderrInStdout(t *testing.T) {
+func (s *DockerSuite) TestLogsStderrInStdout(c *check.C) {
msg := "stderr_log"
runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "busybox", "sh", "-c", fmt.Sprintf("echo %s 1>&2", msg))
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID)
stdout, stderr, _, err := runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
if stderr != "" {
- t.Fatalf("Expected empty stderr stream, got %v", stdout)
+ c.Fatalf("Expected empty stderr stream, got %v", stdout)
}
stdout = strings.TrimSpace(stdout)
if stdout != msg {
- t.Fatalf("Expected %v in stdout stream, got %v", msg, stdout)
+ c.Fatalf("Expected %v in stdout stream, got %v", msg, stdout)
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("logs - stderr in stdout (with pseudo-tty)")
}
-func TestLogsTail(t *testing.T) {
+func (s *DockerSuite) TestLogsTail(c *check.C) {
testLen := 100
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo =; done;", testLen))
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", "--tail", "5", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
lines := strings.Split(out, "\n")
if len(lines) != 6 {
- t.Fatalf("Expected log %d lines, received %d\n", 6, len(lines))
+ c.Fatalf("Expected log %d lines, received %d\n", 6, len(lines))
}
logsCmd = exec.Command(dockerBinary, "logs", "--tail", "all", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
lines = strings.Split(out, "\n")
if len(lines) != testLen+1 {
- t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines))
+ c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines))
}
logsCmd = exec.Command(dockerBinary, "logs", "--tail", "random", cleanedContainerID)
out, _, _, err = runCommandWithStdoutStderr(logsCmd)
if err != nil {
- t.Fatalf("failed to log container: %s, %v", out, err)
+ c.Fatalf("failed to log container: %s, %v", out, err)
}
lines = strings.Split(out, "\n")
if len(lines) != testLen+1 {
- t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines))
+ c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines))
}
-
- deleteContainer(cleanedContainerID)
- logDone("logs - logs tail")
}
-func TestLogsFollowStopped(t *testing.T) {
+func (s *DockerSuite) TestLogsFollowStopped(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID)
if err := logsCmd.Start(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- c := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if err := logsCmd.Wait(); err != nil {
- t.Fatal(err)
- }
- close(c)
+ errChan <- logsCmd.Wait()
+ close(errChan)
}()
select {
- case <-c:
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
case <-time.After(1 * time.Second):
- t.Fatal("Following logs is hanged")
+ c.Fatal("Following logs is hanged")
+ }
+}
+
+func (s *DockerSuite) TestLogsSince(c *check.C) {
+ name := "testlogssince"
+ runCmd := exec.Command(dockerBinary, "run", "--name="+name, "busybox", "/bin/sh", "-c", "for i in $(seq 1 3); do sleep 2; echo `date +%s` log$i; done")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- deleteContainer(cleanedContainerID)
- logDone("logs - logs follow stopped container")
+ log2Line := strings.Split(strings.Split(out, "\n")[1], " ")
+ t, err := strconv.ParseInt(log2Line[0], 10, 64) // the timestamp log2 is writen
+ c.Assert(err, check.IsNil)
+ since := t + 1 // add 1s so log1 & log2 doesn't show up
+ logsCmd := exec.Command(dockerBinary, "logs", "-t", fmt.Sprintf("--since=%v", since), name)
+
+ out, _, err = runCommandWithOutput(logsCmd)
+ if err != nil {
+ c.Fatalf("failed to log container: %s, %v", out, err)
+ }
+
+ // Skip 2 seconds
+ unexpected := []string{"log1", "log2"}
+ for _, v := range unexpected {
+ if strings.Contains(out, v) {
+ c.Fatalf("unexpected log message returned=%v, since=%v\nout=%v", v, since, out)
+ }
+ }
+
+ // Test with default value specified and parameter omitted
+ expected := []string{"log1", "log2", "log3"}
+ for _, cmd := range []*exec.Cmd{
+ exec.Command(dockerBinary, "logs", "-t", name),
+ exec.Command(dockerBinary, "logs", "-t", "--since=0", name),
+ } {
+ out, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("failed to log container: %s, %v", out, err)
+ }
+ for _, v := range expected {
+ if !strings.Contains(out, v) {
+ c.Fatalf("'%v' does not contain=%v\nout=%s", cmd.Args, v, out)
+ }
+ }
+ }
+}
+
+func (s *DockerSuite) TestLogsSinceFutureFollow(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `for i in $(seq 1 5); do date +%s; sleep 1; done`)
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("run failed with errors: %s, %v", out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+
+ now := daemonTime(c).Unix()
+ since := now + 2
+ logCmd := exec.Command(dockerBinary, "logs", "-f", fmt.Sprintf("--since=%v", since), cleanedContainerID)
+ out, _, err = runCommandWithOutput(logCmd)
+ if err != nil {
+ c.Fatalf("failed to log container: %s, %v", out, err)
+ }
+ lines := strings.Split(strings.TrimSpace(out), "\n")
+ if len(lines) == 0 {
+ c.Fatal("got no log lines")
+ }
+ for _, v := range lines {
+ ts, err := strconv.ParseInt(v, 10, 64)
+ if err != nil {
+ c.Fatalf("cannot parse timestamp output from log: '%v'\nout=%s", v, out)
+ }
+ if ts < since {
+ c.Fatalf("earlier log found. since=%v logdate=%v", since, ts)
+ }
+ }
}
// Regression test for #8832
-func TestLogsFollowSlowStdoutConsumer(t *testing.T) {
+func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `usleep 200000;yes X | head -c 200000`)
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("run failed with errors: %s, %v", out, err)
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
stopSlowRead := make(chan bool)
@@ -307,31 +353,75 @@ func TestLogsFollowSlowStdoutConsumer(t *testing.T) {
logCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID)
stdout, err := logCmd.StdoutPipe()
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if err := logCmd.Start(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// First read slowly
bytes1, err := consumeWithSpeed(stdout, 10, 50*time.Millisecond, stopSlowRead)
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
// After the container has finished we can continue reading fast
bytes2, err := consumeWithSpeed(stdout, 32*1024, 0, nil)
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
actual := bytes1 + bytes2
expected := 200000
if actual != expected {
- t.Fatalf("Invalid bytes read: %d, expected %d", actual, expected)
+ c.Fatalf("Invalid bytes read: %d, expected %d", actual, expected)
}
- logDone("logs - follow slow consumer")
+}
+
+func (s *DockerSuite) TestLogsFollowGoroutinesWithStdout(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true; do echo hello; sleep 2; done")
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ type info struct {
+ NGoroutines int
+ }
+ getNGoroutines := func() int {
+ var i info
+ status, b, err := sockRequest("GET", "/info", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, 200)
+ c.Assert(json.Unmarshal(b, &i), check.IsNil)
+ return i.NGoroutines
+ }
+
+ nroutines := getNGoroutines()
+
+ cmd := exec.Command(dockerBinary, "logs", "-f", id)
+ r, w := io.Pipe()
+ cmd.Stdout = w
+ c.Assert(cmd.Start(), check.IsNil)
+
+ // Make sure pipe is written to
+ chErr := make(chan error)
+ go func() {
+ b := make([]byte, 1)
+ _, err := r.Read(b)
+ chErr <- err
+ }()
+ c.Assert(<-chErr, check.IsNil)
+ c.Assert(cmd.Process.Kill(), check.IsNil)
+
+ // NGoroutines is not updated right away, so we need to wait before failing
+ t := time.After(30 * time.Second)
+ for {
+ select {
+ case <-t:
+ if n := getNGoroutines(); n > nroutines {
+ c.Fatalf("leaked goroutines: expected less than or equal to %d, got: %d", nroutines, n)
+ }
+ default:
+ if n := getNGoroutines(); n <= nroutines {
+ return
+ }
+ time.Sleep(200 * time.Millisecond)
+ }
+ }
}
diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go
index 729977bce..2f8bd662c 100644
--- a/integration-cli/docker_cli_nat_test.go
+++ b/integration-cli/docker_cli_nat_test.go
@@ -5,58 +5,107 @@ import (
"net"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestNetworkNat(t *testing.T) {
- testRequires(t, SameHostDaemon, NativeExecDriver)
- defer deleteAllContainers()
+func startServerContainer(c *check.C, proto string, port int) string {
+ pStr := fmt.Sprintf("%d:%d", port, port)
+ bCmd := fmt.Sprintf("nc -lp %d && echo bye", port)
+ cmd := []string{"-d", "-p", pStr, "busybox", "sh", "-c", bCmd}
+ if proto == "udp" {
+ cmd = append(cmd, "-u")
+ }
+ name := "server"
+ if err := waitForContainer(name, cmd...); err != nil {
+ c.Fatalf("Failed to launch server container: %v", err)
+ }
+ return name
+}
+
+func getExternalAddress(c *check.C) net.IP {
iface, err := net.InterfaceByName("eth0")
if err != nil {
- t.Skipf("Test not running with `make test`. Interface eth0 not found: %s", err)
+ c.Skip(fmt.Sprintf("Test not running with `make test`. Interface eth0 not found: %v", err))
}
ifaceAddrs, err := iface.Addrs()
if err != nil || len(ifaceAddrs) == 0 {
- t.Fatalf("Error retrieving addresses for eth0: %v (%d addresses)", err, len(ifaceAddrs))
+ c.Fatalf("Error retrieving addresses for eth0: %v (%d addresses)", err, len(ifaceAddrs))
}
ifaceIP, _, err := net.ParseCIDR(ifaceAddrs[0].String())
if err != nil {
- t.Fatalf("Error retrieving the up for eth0: %s", err)
+ c.Fatalf("Error retrieving the up for eth0: %s", err)
}
- runCmd := exec.Command(dockerBinary, "run", "-dt", "-p", "8080:8080", "busybox", "nc", "-lp", "8080")
+ return ifaceIP
+}
+
+func getContainerLogs(c *check.C, containerID string) string {
+ runCmd := exec.Command(dockerBinary, "logs", containerID)
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
+ }
+ return strings.Trim(out, "\r\n")
+}
+
+func getContainerStatus(c *check.C, containerID string) string {
+ out, err := inspectField(containerID, "State.Running")
+ c.Assert(err, check.IsNil)
+ return out
+}
+
+func (s *DockerSuite) TestNetworkNat(c *check.C) {
+ testRequires(c, SameHostDaemon, NativeExecDriver)
+
+ srv := startServerContainer(c, "tcp", 8080)
+
+ // Spawn a new container which connects to the server through the
+ // interface address.
+ endpoint := getExternalAddress(c)
+ runCmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", endpoint))
+ if out, _, err := runCommandWithOutput(runCmd); err != nil {
+ c.Fatalf("Failed to connect to server: %v (output: %q)", err, string(out))
+ }
+
+ result := getContainerLogs(c, srv)
+
+ // Ideally we'd like to check for "hello world" but sometimes
+ // nc doesn't show the data it received so instead let's look for
+ // the output of the 'echo bye' that should be printed once
+ // the nc command gets a connection
+ expected := "bye"
+ if !strings.Contains(result, expected) {
+ c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result)
+ }
+}
+
+func (s *DockerSuite) TestNetworkLocalhostTCPNat(c *check.C) {
+ testRequires(c, SameHostDaemon, NativeExecDriver)
+
+ srv := startServerContainer(c, "tcp", 8081)
+
+ // Attempt to connect from the host to the listening container.
+ conn, err := net.Dial("tcp", "localhost:8081")
+ if err != nil {
+ c.Fatalf("Failed to connect to container (%v)", err)
+ }
+ if _, err := conn.Write([]byte("hello world\n")); err != nil {
+ c.Fatal(err)
+ }
+ conn.Close()
+
+ result := getContainerLogs(c, srv)
+
+ // Ideally we'd like to check for "hello world" but sometimes
+ // nc doesn't show the data it received so instead let's look for
+ // the output of the 'echo bye' that should be printed once
+ // the nc command gets a connection
+ expected := "bye"
+ if !strings.Contains(result, expected) {
+ c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result)
}
-
- cleanedContainerID := stripTrailingCharacters(out)
-
- runCmd = exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", ifaceIP))
- out, _, err = runCommandWithOutput(runCmd)
- if err != nil {
- t.Fatal(out, err)
- }
-
- runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID)
- out, _, err = runCommandWithOutput(runCmd)
- if err != nil {
- t.Fatalf("failed to retrieve logs for container: %s, %v", out, err)
- }
-
- out = strings.Trim(out, "\r\n")
-
- if expected := "hello world"; out != expected {
- t.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP)
- }
-
- killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID)
- if out, _, err = runCommandWithOutput(killCmd); err != nil {
- t.Fatalf("failed to kill container: %s, %v", out, err)
- }
-
- logDone("network - make sure nat works through the host")
}
diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go
index f1ccde9cf..0256fb92b 100644
--- a/integration-cli/docker_cli_pause_test.go
+++ b/integration-cli/docker_cli_pause_test.go
@@ -4,83 +4,76 @@ import (
"fmt"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestPause(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPause(c *check.C) {
defer unpauseAllContainers()
name := "testeventpause"
- out, _, _ := dockerCmd(t, "images", "-q")
+ out, _ := dockerCmd(c, "images", "-q")
image := strings.Split(out, "\n")[0]
- dockerCmd(t, "run", "-d", "--name", name, image, "sleep", "2")
+ dockerCmd(c, "run", "-d", "--name", name, image, "top")
- dockerCmd(t, "pause", name)
+ dockerCmd(c, "pause", name)
pausedContainers, err := getSliceOfPausedContainers()
if err != nil {
- t.Fatalf("error thrown while checking if containers were paused: %v", err)
+ c.Fatalf("error thrown while checking if containers were paused: %v", err)
}
if len(pausedContainers) != 1 {
- t.Fatalf("there should be one paused container and not", len(pausedContainers))
+ c.Fatalf("there should be one paused container and not %d", len(pausedContainers))
}
- dockerCmd(t, "unpause", name)
+ dockerCmd(c, "unpause", name)
- eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+ eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, _, _ = runCommandWithOutput(eventsCmd)
events := strings.Split(out, "\n")
if len(events) <= 1 {
- t.Fatalf("Missing expected event")
+ c.Fatalf("Missing expected event")
}
pauseEvent := strings.Fields(events[len(events)-3])
unpauseEvent := strings.Fields(events[len(events)-2])
if pauseEvent[len(pauseEvent)-1] != "pause" {
- t.Fatalf("event should be pause, not %#v", pauseEvent)
+ c.Fatalf("event should be pause, not %#v", pauseEvent)
}
if unpauseEvent[len(unpauseEvent)-1] != "unpause" {
- t.Fatalf("event should be unpause, not %#v", unpauseEvent)
+ c.Fatalf("event should be unpause, not %#v", unpauseEvent)
}
- waitCmd := exec.Command(dockerBinary, "wait", name)
- if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err)
- }
-
- logDone("pause - pause/unpause is logged")
}
-func TestPauseMultipleContainers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
defer unpauseAllContainers()
containers := []string{
"testpausewithmorecontainers1",
"testpausewithmorecontainers2",
}
- out, _, _ := dockerCmd(t, "images", "-q")
+ out, _ := dockerCmd(c, "images", "-q")
image := strings.Split(out, "\n")[0]
for _, name := range containers {
- dockerCmd(t, "run", "-d", "--name", name, image, "sleep", "2")
+ dockerCmd(c, "run", "-d", "--name", name, image, "top")
}
- dockerCmd(t, append([]string{"pause"}, containers...)...)
+ dockerCmd(c, append([]string{"pause"}, containers...)...)
pausedContainers, err := getSliceOfPausedContainers()
if err != nil {
- t.Fatalf("error thrown while checking if containers were paused: %v", err)
+ c.Fatalf("error thrown while checking if containers were paused: %v", err)
}
if len(pausedContainers) != len(containers) {
- t.Fatalf("there should be %d paused container and not %d", len(containers), len(pausedContainers))
+ c.Fatalf("there should be %d paused container and not %d", len(containers), len(pausedContainers))
}
- dockerCmd(t, append([]string{"unpause"}, containers...)...)
+ dockerCmd(c, append([]string{"unpause"}, containers...)...)
- eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+ eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, _, _ = runCommandWithOutput(eventsCmd)
events := strings.Split(out, "\n")
if len(events) <= len(containers)*3-2 {
- t.Fatalf("Missing expected event")
+ c.Fatalf("Missing expected event")
}
pauseEvents := make([][]string, len(containers))
@@ -92,21 +85,13 @@ func TestPauseMultipleContainers(t *testing.T) {
for _, pauseEvent := range pauseEvents {
if pauseEvent[len(pauseEvent)-1] != "pause" {
- t.Fatalf("event should be pause, not %#v", pauseEvent)
+ c.Fatalf("event should be pause, not %#v", pauseEvent)
}
}
for _, unpauseEvent := range unpauseEvents {
if unpauseEvent[len(unpauseEvent)-1] != "unpause" {
- t.Fatalf("event should be unpause, not %#v", unpauseEvent)
+ c.Fatalf("event should be unpause, not %#v", unpauseEvent)
}
}
- for _, name := range containers {
- waitCmd := exec.Command(dockerBinary, "wait", name)
- if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err)
- }
- }
-
- logDone("pause - multi pause/unpause is logged")
}
diff --git a/integration-cli/docker_cli_port_test.go b/integration-cli/docker_cli_port_test.go
index 6b68346d4..f0cb66396 100644
--- a/integration-cli/docker_cli_port_test.go
+++ b/integration-cli/docker_cli_port_test.go
@@ -1,45 +1,46 @@
package main
import (
+ "net"
"os/exec"
"sort"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestPortList(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPortList(c *check.C) {
// one port
runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- firstID := stripTrailingCharacters(out)
+ firstID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "port", firstID, "80")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- if !assertPortList(t, out, []string{"0.0.0.0:9876"}) {
- t.Error("Port list is not correct")
+ if !assertPortList(c, out, []string{"0.0.0.0:9876"}) {
+ c.Error("Port list is not correct")
}
runCmd = exec.Command(dockerBinary, "port", firstID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- if !assertPortList(t, out, []string{"80/tcp -> 0.0.0.0:9876"}) {
- t.Error("Port list is not correct")
+ if !assertPortList(c, out, []string{"80/tcp -> 0.0.0.0:9876"}) {
+ c.Error("Port list is not correct")
}
runCmd = exec.Command(dockerBinary, "rm", "-f", firstID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// three port
@@ -50,36 +51,36 @@ func TestPortList(t *testing.T) {
"busybox", "top")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- ID := stripTrailingCharacters(out)
+ ID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "port", ID, "80")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- if !assertPortList(t, out, []string{"0.0.0.0:9876"}) {
- t.Error("Port list is not correct")
+ if !assertPortList(c, out, []string{"0.0.0.0:9876"}) {
+ c.Error("Port list is not correct")
}
runCmd = exec.Command(dockerBinary, "port", ID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- if !assertPortList(t, out, []string{
+ if !assertPortList(c, out, []string{
"80/tcp -> 0.0.0.0:9876",
"81/tcp -> 0.0.0.0:9877",
"82/tcp -> 0.0.0.0:9878"}) {
- t.Error("Port list is not correct")
+ c.Error("Port list is not correct")
}
runCmd = exec.Command(dockerBinary, "rm", "-f", ID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// more and one port mapped to the same container port
@@ -91,46 +92,45 @@ func TestPortList(t *testing.T) {
"busybox", "top")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- ID = stripTrailingCharacters(out)
+ ID = strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "port", ID, "80")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- if !assertPortList(t, out, []string{"0.0.0.0:9876", "0.0.0.0:9999"}) {
- t.Error("Port list is not correct")
+ if !assertPortList(c, out, []string{"0.0.0.0:9876", "0.0.0.0:9999"}) {
+ c.Error("Port list is not correct")
}
runCmd = exec.Command(dockerBinary, "port", ID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- if !assertPortList(t, out, []string{
+ if !assertPortList(c, out, []string{
"80/tcp -> 0.0.0.0:9876",
"80/tcp -> 0.0.0.0:9999",
"81/tcp -> 0.0.0.0:9877",
"82/tcp -> 0.0.0.0:9878"}) {
- t.Error("Port list is not correct\n", out)
+ c.Error("Port list is not correct\n", out)
}
runCmd = exec.Command(dockerBinary, "rm", "-f", ID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- logDone("port - test port list")
}
-func assertPortList(t *testing.T, out string, expected []string) bool {
+func assertPortList(c *check.C, out string, expected []string) bool {
//lines := strings.Split(out, "\n")
lines := strings.Split(strings.Trim(out, "\n "), "\n")
if len(lines) != len(expected) {
- t.Errorf("different size lists %s, %d, %d", out, len(lines), len(expected))
+ c.Errorf("different size lists %s, %d, %d", out, len(lines), len(expected))
return false
}
sort.Strings(lines)
@@ -138,10 +138,86 @@ func assertPortList(t *testing.T, out string, expected []string) bool {
for i := 0; i < len(expected); i++ {
if lines[i] != expected[i] {
- t.Error("|" + lines[i] + "!=" + expected[i] + "|")
+ c.Error("|" + lines[i] + "!=" + expected[i] + "|")
return false
}
}
return true
}
+
+func (s *DockerSuite) TestPortHostBinding(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox",
+ "nc", "-l", "-p", "80")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ firstID := strings.TrimSpace(out)
+
+ runCmd = exec.Command(dockerBinary, "port", firstID, "80")
+ out, _, err = runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ if !assertPortList(c, out, []string{"0.0.0.0:9876"}) {
+ c.Error("Port list is not correct")
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox",
+ "nc", "localhost", "9876")
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "rm", "-f", firstID)
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox",
+ "nc", "localhost", "9876")
+ if out, _, err = runCommandWithOutput(runCmd); err == nil {
+ c.Error("Port is still bound after the Container is removed")
+ }
+}
+
+func (s *DockerSuite) TestPortExposeHostBinding(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--expose", "80", "busybox",
+ "nc", "-l", "-p", "80")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ firstID := strings.TrimSpace(out)
+
+ runCmd = exec.Command(dockerBinary, "port", firstID, "80")
+ out, _, err = runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ _, exposedPort, err := net.SplitHostPort(out)
+
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox",
+ "nc", "localhost", strings.TrimSpace(exposedPort))
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "rm", "-f", firstID)
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox",
+ "nc", "localhost", strings.TrimSpace(exposedPort))
+ if out, _, err = runCommandWithOutput(runCmd); err == nil {
+ c.Error("Port is still bound after the Container is removed")
+ }
+}
diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go
index b39dd5634..8b55c67d8 100644
--- a/integration-cli/docker_cli_proxy_test.go
+++ b/integration-cli/docker_cli_proxy_test.go
@@ -4,30 +4,30 @@ import (
"net"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestCliProxyDisableProxyUnixSock(t *testing.T) {
- testRequires(t, SameHostDaemon) // test is valid when DOCKER_HOST=unix://..
+func (s *DockerSuite) TestCliProxyDisableProxyUnixSock(c *check.C) {
+ testRequires(c, SameHostDaemon) // test is valid when DOCKER_HOST=unix://..
cmd := exec.Command(dockerBinary, "info")
cmd.Env = appendBaseEnv([]string{"HTTP_PROXY=http://127.0.0.1:9999"})
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- logDone("cli proxy - HTTP_PROXY is not used when connecting to unix sock")
}
// Can't use localhost here since go has a special case to not use proxy if connecting to localhost
-// See http://golang.org/pkg/net/http/#ProxyFromEnvironment
-func TestCliProxyProxyTCPSock(t *testing.T) {
- testRequires(t, SameHostDaemon)
+// See https://golang.org/pkg/net/http/#ProxyFromEnvironment
+func (s *DockerDaemonSuite) TestCliProxyProxyTCPSock(c *check.C) {
+ testRequires(c, SameHostDaemon)
// get the IP to use to connect since we can't use localhost
addrs, err := net.InterfaceAddrs()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var ip string
for _, addr := range addrs {
@@ -40,25 +40,23 @@ func TestCliProxyProxyTCPSock(t *testing.T) {
}
if ip == "" {
- t.Fatal("could not find ip to connect to")
+ c.Fatal("could not find ip to connect to")
}
- d := NewDaemon(t)
- if err := d.Start("-H", "tcp://"+ip+":2375"); err != nil {
- t.Fatal(err)
+ if err := s.d.Start("-H", "tcp://"+ip+":2375"); err != nil {
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "info")
cmd.Env = []string{"DOCKER_HOST=tcp://" + ip + ":2375", "HTTP_PROXY=127.0.0.1:9999"}
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
// Test with no_proxy
cmd.Env = append(cmd.Env, "NO_PROXY="+ip)
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "info")); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- logDone("cli proxy - HTTP_PROXY is used for TCP sock")
}
diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go
index c2e108576..a9d36be49 100644
--- a/integration-cli/docker_cli_ps_test.go
+++ b/integration-cli/docker_cli_ps_test.go
@@ -6,68 +6,78 @@ import (
"reflect"
"strconv"
"strings"
- "testing"
"time"
+
+ "github.com/go-check/check"
)
-func TestPsListContainers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPsListContainers(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- firstID := stripTrailingCharacters(out)
+ firstID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- secondID := stripTrailingCharacters(out)
+ secondID := strings.TrimSpace(out)
// not long running
runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- thirdID := stripTrailingCharacters(out)
+ thirdID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
+ }
+ fourthID := strings.TrimSpace(out)
+
+ // make sure the second is running
+ if err := waitRun(secondID); err != nil {
+ c.Fatalf("waiting for container failed: %v", err)
}
- fourthID := stripTrailingCharacters(out)
// make sure third one is not running
runCmd = exec.Command(dockerBinary, "wait", thirdID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
+ }
+
+ // make sure the forth is running
+ if err := waitRun(fourthID); err != nil {
+ c.Fatalf("waiting for container failed: %v", err)
}
// all
runCmd = exec.Command(dockerBinary, "ps", "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, []string{fourthID, thirdID, secondID, firstID}) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// running
runCmd = exec.Command(dockerBinary, "ps")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, []string{fourthID, secondID, firstID}) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// from here all flag '-a' is ignored
@@ -76,156 +86,155 @@ func TestPsListContainers(t *testing.T) {
runCmd = exec.Command(dockerBinary, "ps", "-n=2", "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
expected := []string{fourthID, thirdID}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
runCmd = exec.Command(dockerBinary, "ps", "-n=2")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// since
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
expected = []string{fourthID, thirdID, secondID}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// before
runCmd = exec.Command(dockerBinary, "ps", "--before", thirdID, "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
expected = []string{secondID, firstID}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
runCmd = exec.Command(dockerBinary, "ps", "--before", thirdID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// since & before
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
expected = []string{thirdID, secondID}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// since & limit
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-n=2", "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
expected = []string{fourthID, thirdID}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-n=2")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// before & limit
runCmd = exec.Command(dockerBinary, "ps", "--before", fourthID, "-n=1", "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
expected = []string{thirdID}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
runCmd = exec.Command(dockerBinary, "ps", "--before", fourthID, "-n=1")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// since & before & limit
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-n=1", "-a")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
expected = []string{thirdID}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-n=1")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if !assertContainerList(out, expected) {
- t.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
- logDone("ps - test ps options")
}
func assertContainerList(out string, expected []string) bool {
@@ -245,30 +254,28 @@ func assertContainerList(out string, expected []string) bool {
return true
}
-func TestPsListContainersSize(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestPsListContainersSize(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello")
runCommandWithOutput(cmd)
cmd = exec.Command(dockerBinary, "ps", "-s", "-n=1")
- base_out, _, err := runCommandWithOutput(cmd)
- base_lines := strings.Split(strings.Trim(base_out, "\n "), "\n")
- base_sizeIndex := strings.Index(base_lines[0], "SIZE")
- base_foundSize := base_lines[1][base_sizeIndex:]
- base_bytes, err := strconv.Atoi(strings.Split(base_foundSize, " ")[0])
+ baseOut, _, err := runCommandWithOutput(cmd)
+ baseLines := strings.Split(strings.Trim(baseOut, "\n "), "\n")
+ baseSizeIndex := strings.Index(baseLines[0], "SIZE")
+ baseFoundsize := baseLines[1][baseSizeIndex:]
+ baseBytes, err := strconv.Atoi(strings.Split(baseFoundsize, " ")[0])
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
name := "test_size"
runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo 1 > test")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id, err := getIDByName(name)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runCmd = exec.Command(dockerBinary, "ps", "-s", "-n=1")
@@ -280,346 +287,351 @@ func TestPsListContainersSize(t *testing.T) {
select {
case <-wait:
case <-time.After(3 * time.Second):
- t.Fatalf("Calling \"docker ps -s\" timed out!")
+ c.Fatalf("Calling \"docker ps -s\" timed out!")
}
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
lines := strings.Split(strings.Trim(out, "\n "), "\n")
+ if len(lines) != 2 {
+ c.Fatalf("Expected 2 lines for 'ps -s -n=1' output, got %d", len(lines))
+ }
sizeIndex := strings.Index(lines[0], "SIZE")
idIndex := strings.Index(lines[0], "CONTAINER ID")
foundID := lines[1][idIndex : idIndex+12]
if foundID != id[:12] {
- t.Fatalf("Expected id %s, got %s", id[:12], foundID)
+ c.Fatalf("Expected id %s, got %s", id[:12], foundID)
}
- expectedSize := fmt.Sprintf("%d B", (2 + base_bytes))
+ expectedSize := fmt.Sprintf("%d B", (2 + baseBytes))
foundSize := lines[1][sizeIndex:]
- if foundSize != expectedSize {
- t.Fatalf("Expected size %q, got %q", expectedSize, foundSize)
+ if !strings.Contains(foundSize, expectedSize) {
+ c.Fatalf("Expected size %q, got %q", expectedSize, foundSize)
}
- logDone("ps - test ps size")
}
-func TestPsListContainersFilterStatus(t *testing.T) {
+func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) {
// FIXME: this should test paused, but it makes things hang and its wonky
// this is because paused containers can't be controlled by signals
- defer deleteAllContainers()
// start exited container
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- firstID := stripTrailingCharacters(out)
+ firstID := strings.TrimSpace(out)
// make sure the exited cintainer is not running
runCmd = exec.Command(dockerBinary, "wait", firstID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// start running container
runCmd = exec.Command(dockerBinary, "run", "-itd", "busybox")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- secondID := stripTrailingCharacters(out)
+ secondID := strings.TrimSpace(out)
// filter containers by exited
runCmd = exec.Command(dockerBinary, "ps", "-q", "--filter=status=exited")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut := strings.TrimSpace(out)
if containerOut != firstID[:12] {
- t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out)
+ c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out)
}
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=status=running")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut = strings.TrimSpace(out)
if containerOut != secondID[:12] {
- t.Fatalf("Expected id %s, got %s for running filter, output: %q", secondID[:12], containerOut, out)
+ c.Fatalf("Expected id %s, got %s for running filter, output: %q", secondID[:12], containerOut, out)
}
- logDone("ps - test ps filter status")
}
-func TestPsListContainersFilterID(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPsListContainersFilterID(c *check.C) {
// start container
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- firstID := stripTrailingCharacters(out)
+ firstID := strings.TrimSpace(out)
// start another container
- runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 360")
+ runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// filter containers by id
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=id="+firstID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut := strings.TrimSpace(out)
if containerOut != firstID[:12] {
- t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out)
+ c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out)
}
- logDone("ps - test ps filter id")
}
-func TestPsListContainersFilterName(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPsListContainersFilterName(c *check.C) {
// start container
runCmd := exec.Command(dockerBinary, "run", "-d", "--name=a_name_to_match", "busybox")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- firstID := stripTrailingCharacters(out)
+ firstID := strings.TrimSpace(out)
// start another container
- runCmd = exec.Command(dockerBinary, "run", "-d", "--name=b_name_to_match", "busybox", "sh", "-c", "sleep 360")
+ runCmd = exec.Command(dockerBinary, "run", "-d", "--name=b_name_to_match", "busybox", "top")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// filter containers by name
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=name=a_name_to_match")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut := strings.TrimSpace(out)
if containerOut != firstID[:12] {
- t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out)
+ c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out)
}
- logDone("ps - test ps filter name")
}
-func TestPsListContainersFilterLabel(t *testing.T) {
+func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) {
// start container
runCmd := exec.Command(dockerBinary, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- firstID := stripTrailingCharacters(out)
+ firstID := strings.TrimSpace(out)
// start another container
runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- secondID := stripTrailingCharacters(out)
+ secondID := strings.TrimSpace(out)
// start third container
runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- thirdID := stripTrailingCharacters(out)
+ thirdID := strings.TrimSpace(out)
// filter containers by exact match
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut := strings.TrimSpace(out)
if containerOut != firstID {
- t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
+ c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
}
// filter containers by two labels
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut = strings.TrimSpace(out)
if containerOut != firstID {
- t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
+ c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
}
// filter containers by two labels, but expect not found because of AND behavior
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag-no")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut = strings.TrimSpace(out)
if containerOut != "" {
- t.Fatalf("Expected nothing, got %s for exited filter, output: %q", containerOut, out)
+ c.Fatalf("Expected nothing, got %s for exited filter, output: %q", containerOut, out)
}
// filter containers by exact key
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match")
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
containerOut = strings.TrimSpace(out)
if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) {
- t.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out)
+ c.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out)
}
-
- deleteAllContainers()
-
- logDone("ps - test ps filter label")
}
-func TestPsListContainersFilterExited(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "run", "--name", "zero1", "busybox", "true")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
firstZero, err := getIDByName("zero1")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runCmd = exec.Command(dockerBinary, "run", "--name", "zero2", "busybox", "true")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
secondZero, err := getIDByName("zero2")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runCmd = exec.Command(dockerBinary, "run", "--name", "nonzero1", "busybox", "false")
if out, _, err := runCommandWithOutput(runCmd); err == nil {
- t.Fatal("Should fail.", out, err)
+ c.Fatal("Should fail.", out, err)
}
firstNonZero, err := getIDByName("nonzero1")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runCmd = exec.Command(dockerBinary, "run", "--name", "nonzero2", "busybox", "false")
if out, _, err := runCommandWithOutput(runCmd); err == nil {
- t.Fatal("Should fail.", out, err)
+ c.Fatal("Should fail.", out, err)
}
secondNonZero, err := getIDByName("nonzero2")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// filter containers by exited=0
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=exited=0")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
ids := strings.Split(strings.TrimSpace(out), "\n")
if len(ids) != 2 {
- t.Fatalf("Should be 2 zero exited containerst got %d", len(ids))
+ c.Fatalf("Should be 2 zero exited containers got %d: %s", len(ids), out)
}
if ids[0] != secondZero {
- t.Fatalf("First in list should be %q, got %q", secondZero, ids[0])
+ c.Fatalf("First in list should be %q, got %q", secondZero, ids[0])
}
if ids[1] != firstZero {
- t.Fatalf("Second in list should be %q, got %q", firstZero, ids[1])
+ c.Fatalf("Second in list should be %q, got %q", firstZero, ids[1])
}
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=exited=1")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
ids = strings.Split(strings.TrimSpace(out), "\n")
if len(ids) != 2 {
- t.Fatalf("Should be 2 zero exited containerst got %d", len(ids))
+ c.Fatalf("Should be 2 zero exited containers got %d", len(ids))
}
if ids[0] != secondNonZero {
- t.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0])
+ c.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0])
}
if ids[1] != firstNonZero {
- t.Fatalf("Second in list should be %q, got %q", firstNonZero, ids[1])
+ c.Fatalf("Second in list should be %q, got %q", firstNonZero, ids[1])
}
- logDone("ps - test ps filter exited")
}
-func TestPsRightTagName(t *testing.T) {
+func (s *DockerSuite) TestPsRightTagName(c *check.C) {
tag := "asybox:shmatest"
- defer deleteAllContainers()
- defer deleteImages(tag)
if out, err := exec.Command(dockerBinary, "tag", "busybox", tag).CombinedOutput(); err != nil {
- t.Fatalf("Failed to tag image: %s, out: %q", err, out)
+ c.Fatalf("Failed to tag image: %s, out: %q", err, out)
}
var id1 string
if out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "top").CombinedOutput(); err != nil {
- t.Fatalf("Failed to run container: %s, out: %q", err, out)
+ c.Fatalf("Failed to run container: %s, out: %q", err, out)
} else {
id1 = strings.TrimSpace(string(out))
}
var id2 string
if out, err := exec.Command(dockerBinary, "run", "-d", tag, "top").CombinedOutput(); err != nil {
- t.Fatalf("Failed to run container: %s, out: %q", err, out)
+ c.Fatalf("Failed to run container: %s, out: %q", err, out)
} else {
id2 = strings.TrimSpace(string(out))
}
+
+ var imageID string
+ if out, err := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox").CombinedOutput(); err != nil {
+ c.Fatalf("failed to get the image ID of busybox: %s, %v", out, err)
+ } else {
+ imageID = strings.TrimSpace(string(out))
+ }
+
+ var id3 string
+ if out, err := exec.Command(dockerBinary, "run", "-d", imageID, "top").CombinedOutput(); err != nil {
+ c.Fatalf("Failed to run container: %s, out: %q", err, out)
+ } else {
+ id3 = strings.TrimSpace(string(out))
+ }
+
out, err := exec.Command(dockerBinary, "ps", "--no-trunc").CombinedOutput()
if err != nil {
- t.Fatalf("Failed to run 'ps': %s, out: %q", err, out)
+ c.Fatalf("Failed to run 'ps': %s, out: %q", err, out)
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
// skip header
lines = lines[1:]
- if len(lines) != 2 {
- t.Fatalf("There should be 2 running container, got %d", len(lines))
+ if len(lines) != 3 {
+ c.Fatalf("There should be 3 running container, got %d", len(lines))
}
for _, line := range lines {
f := strings.Fields(line)
switch f[0] {
case id1:
- if f[1] != "busybox:latest" {
- t.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1])
+ if f[1] != "busybox" {
+ c.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1])
}
case id2:
if f[1] != tag {
- t.Fatalf("Expected %s tag for id %s, got %s", tag, id1, f[1])
+ c.Fatalf("Expected %s tag for id %s, got %s", tag, id2, f[1])
+ }
+ case id3:
+ if f[1] != imageID {
+ c.Fatalf("Expected %s imageID for id %s, got %s", tag, id3, f[1])
}
default:
- t.Fatalf("Unexpected id %s, expected %s and %s", f[0], id1, id2)
+ c.Fatalf("Unexpected id %s, expected %s and %s and %s", f[0], id1, id2, id3)
}
}
- logDone("ps - right tags for containers")
}
-func TestPsLinkedWithNoTrunc(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPsLinkedWithNoTrunc(c *check.C) {
if out, err := exec.Command(dockerBinary, "run", "--name=first", "-d", "busybox", "top").CombinedOutput(); err != nil {
- t.Fatalf("Output: %s, err: %s", out, err)
+ c.Fatalf("Output: %s, err: %s", out, err)
}
if out, err := exec.Command(dockerBinary, "run", "--name=second", "--link=first:first", "-d", "busybox", "top").CombinedOutput(); err != nil {
- t.Fatalf("Output: %s, err: %s", out, err)
+ c.Fatalf("Output: %s, err: %s", out, err)
}
out, err := exec.Command(dockerBinary, "ps", "--no-trunc").CombinedOutput()
if err != nil {
- t.Fatalf("Output: %s, err: %s", out, err)
+ c.Fatalf("Output: %s, err: %s", out, err)
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
// strip header
@@ -631,28 +643,40 @@ func TestPsLinkedWithNoTrunc(t *testing.T) {
names = append(names, fields[len(fields)-1])
}
if !reflect.DeepEqual(expected, names) {
- t.Fatalf("Expected array: %v, got: %v", expected, names)
+ c.Fatalf("Expected array: %v, got: %v", expected, names)
}
}
-func TestPsGroupPortRange(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestPsGroupPortRange(c *check.C) {
- portRange := "3300-3900"
+ portRange := "3800-3900"
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "porttest", "-p", portRange+":"+portRange, "busybox", "top"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "ps"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// check that the port range is in the output
if !strings.Contains(string(out), portRange) {
- t.Fatalf("docker ps output should have had the port range %q: %s", portRange, string(out))
+ c.Fatalf("docker ps output should have had the port range %q: %s", portRange, string(out))
}
- logDone("ps - port range")
+}
+
+func (s *DockerSuite) TestPsWithSize(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "sizetest", "busybox", "top"))
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "ps", "--size"))
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ if !strings.Contains(out, "virtual") {
+ c.Fatalf("docker ps with --size should show virtual size of container")
+ }
}
diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go
index 926e76343..a3ded8f03 100644
--- a/integration-cli/docker_cli_pull_test.go
+++ b/integration-cli/docker_cli_pull_test.go
@@ -4,15 +4,13 @@ import (
"fmt"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
// See issue docker/docker#8141
-func TestPullImageWithAliases(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
- defer deleteImages(repoName)
repos := []string{}
for _, tag := range []string{"recent", "fresh"} {
@@ -22,90 +20,92 @@ func TestPullImageWithAliases(t *testing.T) {
// Tag and push the same image multiple times.
for _, repo := range repos {
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil {
- t.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out)
+ c.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out)
}
- defer deleteImages(repo)
if out, err := exec.Command(dockerBinary, "push", repo).CombinedOutput(); err != nil {
- t.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out))
+ c.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out))
}
}
// Clear local images store.
args := append([]string{"rmi"}, repos...)
if out, err := exec.Command(dockerBinary, args...).CombinedOutput(); err != nil {
- t.Fatalf("Failed to clean images: error %v, output %q", err, string(out))
+ c.Fatalf("Failed to clean images: error %v, output %q", err, string(out))
}
// Pull a single tag and verify it doesn't bring down all aliases.
pullCmd := exec.Command(dockerBinary, "pull", repos[0])
if out, _, err := runCommandWithOutput(pullCmd); err != nil {
- t.Fatalf("Failed to pull %v: error %v, output %q", repoName, err, out)
+ c.Fatalf("Failed to pull %v: error %v, output %q", repoName, err, out)
}
if err := exec.Command(dockerBinary, "inspect", repos[0]).Run(); err != nil {
- t.Fatalf("Image %v was not pulled down", repos[0])
+ c.Fatalf("Image %v was not pulled down", repos[0])
}
for _, repo := range repos[1:] {
if err := exec.Command(dockerBinary, "inspect", repo).Run(); err == nil {
- t.Fatalf("Image %v shouldn't have been pulled down", repo)
+ c.Fatalf("Image %v shouldn't have been pulled down", repo)
}
}
-
- logDone("pull - image with aliases")
}
// pulling library/hello-world should show verified message
-func TestPullVerified(t *testing.T) {
+func (s *DockerSuite) TestPullVerified(c *check.C) {
+ c.Skip("Skipping hub dependent test")
+
// Image must be pulled from central repository to get verified message
// unless keychain is manually updated to contain the daemon's sign key.
verifiedName := "hello-world"
- defer deleteImages(verifiedName)
// pull it
expected := "The image you are pulling has been verified"
pullCmd := exec.Command(dockerBinary, "pull", verifiedName)
if out, exitCode, err := runCommandWithOutput(pullCmd); err != nil || !strings.Contains(out, expected) {
if err != nil || exitCode != 0 {
- t.Skipf("pulling the '%s' image from the registry has failed: %s", verifiedName, err)
+ c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err))
}
- t.Fatalf("pulling a verified image failed. expected: %s\ngot: %s, %v", expected, out, err)
+ c.Fatalf("pulling a verified image failed. expected: %s\ngot: %s, %v", expected, out, err)
}
// pull it again
pullCmd = exec.Command(dockerBinary, "pull", verifiedName)
if out, exitCode, err := runCommandWithOutput(pullCmd); err != nil || strings.Contains(out, expected) {
if err != nil || exitCode != 0 {
- t.Skipf("pulling the '%s' image from the registry has failed: %s", verifiedName, err)
+ c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err))
}
- t.Fatalf("pulling a verified image failed. unexpected verify message\ngot: %s, %v", out, err)
+ c.Fatalf("pulling a verified image failed. unexpected verify message\ngot: %s, %v", out, err)
}
- logDone("pull - pull verified")
}
// pulling an image from the central registry should work
-func TestPullImageFromCentralRegistry(t *testing.T) {
- defer deleteImages("hello-world")
+func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) {
+ testRequires(c, Network)
pullCmd := exec.Command(dockerBinary, "pull", "hello-world")
if out, _, err := runCommandWithOutput(pullCmd); err != nil {
- t.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err)
+ c.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err)
}
- logDone("pull - pull hello-world")
}
// pulling a non-existing image from the central registry should return a non-zero exit code
-func TestPullNonExistingImage(t *testing.T) {
- pullCmd := exec.Command(dockerBinary, "pull", "fooblahblah1234")
- if out, _, err := runCommandWithOutput(pullCmd); err == nil {
- t.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out)
+func (s *DockerSuite) TestPullNonExistingImage(c *check.C) {
+ testRequires(c, Network)
+
+ name := "sadfsadfasdf"
+ pullCmd := exec.Command(dockerBinary, "pull", name)
+ out, _, err := runCommandWithOutput(pullCmd)
+
+ if err == nil || !strings.Contains(out, fmt.Sprintf("Error: image library/%s:latest not found", name)) {
+ c.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out)
}
- logDone("pull - pull fooblahblah1234 (non-existing image)")
}
// pulling an image from the central registry using official names should work
// ensure all pulls result in the same image
-func TestPullImageOfficialNames(t *testing.T) {
+func (s *DockerSuite) TestPullImageOfficialNames(c *check.C) {
+ testRequires(c, Network)
+
names := []string{
"docker.io/hello-world",
"index.docker.io/hello-world",
@@ -117,7 +117,7 @@ func TestPullImageOfficialNames(t *testing.T) {
pullCmd := exec.Command(dockerBinary, "pull", name)
out, exitCode, err := runCommandWithOutput(pullCmd)
if err != nil || exitCode != 0 {
- t.Errorf("pulling the '%s' image from the registry has failed: %s", name, err)
+ c.Errorf("pulling the '%s' image from the registry has failed: %s", name, err)
continue
}
@@ -125,10 +125,28 @@ func TestPullImageOfficialNames(t *testing.T) {
imagesCmd := exec.Command(dockerBinary, "images")
out, _, err = runCommandWithOutput(imagesCmd)
if err != nil {
- t.Errorf("listing images failed with errors: %v", err)
+ c.Errorf("listing images failed with errors: %v", err)
} else if strings.Contains(out, name) {
- t.Errorf("images should not have listed '%s'", name)
+ c.Errorf("images should not have listed '%s'", name)
}
}
- logDone("pull - pull official names")
+}
+
+func (s *DockerSuite) TestPullScratchNotAllowed(c *check.C) {
+ testRequires(c, Network)
+
+ pullCmd := exec.Command(dockerBinary, "pull", "scratch")
+ out, exitCode, err := runCommandWithOutput(pullCmd)
+ if err == nil {
+ c.Fatal("expected pull of scratch to fail, but it didn't")
+ }
+ if exitCode != 1 {
+ c.Fatalf("pulling scratch expected exit code 1, got %d", exitCode)
+ }
+ if strings.Contains(out, "Pulling repository scratch") {
+ c.Fatalf("pulling scratch should not have begun: %s", out)
+ }
+ if !strings.Contains(out, "'scratch' is a reserved name") {
+ c.Fatalf("unexpected output pulling scratch: %s", out)
+ }
}
diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go
index f1274ba70..ca971807f 100644
--- a/integration-cli/docker_cli_push_test.go
+++ b/integration-cli/docker_cli_push_test.go
@@ -1,158 +1,140 @@
package main
import (
+ "archive/tar"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
- "testing"
"time"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/go-check/check"
)
// pulling an image from the central registry should work
-func TestPushBusyboxImage(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
// tag the image to upload it to the private registry
tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName)
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatalf("image tagging failed: %s, %v", out, err)
+ c.Fatalf("image tagging failed: %s, %v", out, err)
}
- defer deleteImages(repoName)
pushCmd := exec.Command(dockerBinary, "push", repoName)
if out, _, err := runCommandWithOutput(pushCmd); err != nil {
- t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err)
+ c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err)
}
- logDone("push - busybox to private registry")
}
// pushing an image without a prefix should throw an error
-func TestPushUnprefixedRepo(t *testing.T) {
+func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) {
pushCmd := exec.Command(dockerBinary, "push", "busybox")
if out, _, err := runCommandWithOutput(pushCmd); err == nil {
- t.Fatalf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out)
+ c.Fatalf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out)
}
- logDone("push - unprefixed busybox repo must not pass")
}
-func TestPushUntagged(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
expected := "Repository does not exist"
pushCmd := exec.Command(dockerBinary, "push", repoName)
if out, _, err := runCommandWithOutput(pushCmd); err == nil {
- t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out)
+ c.Fatalf("pushing the image to the private registry should have failed: output %q", out)
} else if !strings.Contains(out, expected) {
- t.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out)
+ c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out)
}
- logDone("push - untagged image")
}
-func TestPushBadTag(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL)
expected := "does not exist"
pushCmd := exec.Command(dockerBinary, "push", repoName)
if out, _, err := runCommandWithOutput(pushCmd); err == nil {
- t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out)
+ c.Fatalf("pushing the image to the private registry should have failed: output %q", out)
} else if !strings.Contains(out, expected) {
- t.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out)
+ c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out)
}
- logDone("push - image with bad tag")
}
-func TestPushMultipleTags(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL)
repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL)
- // tag the image to upload it tot he private registry
+ // tag the image and upload it to the private registry
tagCmd1 := exec.Command(dockerBinary, "tag", "busybox", repoTag1)
if out, _, err := runCommandWithOutput(tagCmd1); err != nil {
- t.Fatalf("image tagging failed: %s, %v", out, err)
+ c.Fatalf("image tagging failed: %s, %v", out, err)
}
- defer deleteImages(repoTag1)
tagCmd2 := exec.Command(dockerBinary, "tag", "busybox", repoTag2)
if out, _, err := runCommandWithOutput(tagCmd2); err != nil {
- t.Fatalf("image tagging failed: %s, %v", out, err)
+ c.Fatalf("image tagging failed: %s, %v", out, err)
}
- defer deleteImages(repoTag2)
pushCmd := exec.Command(dockerBinary, "push", repoName)
if out, _, err := runCommandWithOutput(pushCmd); err != nil {
- t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err)
+ c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err)
}
- logDone("push - multiple tags to private registry")
}
-func TestPushInterrupt(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPushInterrupt(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
- // tag the image to upload it tot he private registry
- tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName)
- if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatalf("image tagging failed: %s, %v", out, err)
+ // tag the image and upload it to the private registry
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil {
+ c.Fatalf("image tagging failed: %s, %v", out, err)
}
- defer deleteImages(repoName)
pushCmd := exec.Command(dockerBinary, "push", repoName)
if err := pushCmd.Start(); err != nil {
- t.Fatalf("Failed to start pushing to private registry: %v", err)
+ c.Fatalf("Failed to start pushing to private registry: %v", err)
}
// Interrupt push (yes, we have no idea at what point it will get killed).
time.Sleep(200 * time.Millisecond)
if err := pushCmd.Process.Kill(); err != nil {
- t.Fatalf("Failed to kill push process: %v", err)
+ c.Fatalf("Failed to kill push process: %v", err)
}
- // Try agin
- pushCmd = exec.Command(dockerBinary, "push", repoName)
- if err := pushCmd.Start(); err != nil {
- t.Fatalf("Failed to start pushing to private registry: %v", err)
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "push", repoName)); err == nil {
+ str := string(out)
+ if !strings.Contains(str, "already in progress") {
+ c.Fatalf("Push should be continued on daemon side, but seems ok: %v, %s", err, out)
+ }
+ }
+ // now wait until all this pushes will complete
+ // if it failed with timeout - there would be some error,
+ // so no logic about it here
+ for exec.Command(dockerBinary, "push", repoName).Run() != nil {
}
-
- logDone("push - interrupted")
}
-func TestPushEmptyLayer(t *testing.T) {
- defer setupRegistry(t)()
+func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL)
emptyTarball, err := ioutil.TempFile("", "empty_tarball")
if err != nil {
- t.Fatalf("Unable to create test file: %v", err)
+ c.Fatalf("Unable to create test file: %v", err)
}
tw := tar.NewWriter(emptyTarball)
err = tw.Close()
if err != nil {
- t.Fatalf("Error creating empty tarball: %v", err)
+ c.Fatalf("Error creating empty tarball: %v", err)
}
freader, err := os.Open(emptyTarball.Name())
if err != nil {
- t.Fatalf("Could not open test tarball: %v", err)
+ c.Fatalf("Could not open test tarball: %v", err)
}
importCmd := exec.Command(dockerBinary, "import", "-", repoName)
importCmd.Stdin = freader
out, _, err := runCommandWithOutput(importCmd)
if err != nil {
- t.Errorf("import failed with errors: %v, output: %q", err, out)
+ c.Errorf("import failed with errors: %v, output: %q", err, out)
}
// Now verify we can push it
pushCmd := exec.Command(dockerBinary, "push", repoName)
if out, _, err := runCommandWithOutput(pushCmd); err != nil {
- t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err)
+ c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err)
}
- logDone("push - empty layer config to private registry")
}
diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go
index 3aaf79522..156ea6eeb 100644
--- a/integration-cli/docker_cli_rename_test.go
+++ b/integration-cli/docker_cli_rename_test.go
@@ -3,119 +3,110 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/go-check/check"
)
-func TestRenameStoppedContainer(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
name, err := inspectField(cleanedContainerID, "Name")
- runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name")
+ newName := "new_name" + stringid.GenerateRandomID()
+ runCmd = exec.Command(dockerBinary, "rename", "first_name", newName)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
name, err = inspectField(cleanedContainerID, "Name")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if name != "/new_name" {
- t.Fatal("Failed to rename container ", name)
+ if name != "/"+newName {
+ c.Fatal("Failed to rename container ", name)
}
- logDone("rename - stopped container")
}
-func TestRenameRunningContainer(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRenameRunningContainer(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
- runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name")
+ newName := "new_name" + stringid.GenerateRandomID()
+ cleanedContainerID := strings.TrimSpace(out)
+ runCmd = exec.Command(dockerBinary, "rename", "first_name", newName)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
name, err := inspectField(cleanedContainerID, "Name")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if name != "/new_name" {
- t.Fatal("Failed to rename container ")
+ if name != "/"+newName {
+ c.Fatal("Failed to rename container ")
}
-
- logDone("rename - running container")
}
-func TestRenameCheckNames(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRenameCheckNames(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name")
+ newName := "new_name" + stringid.GenerateRandomID()
+ runCmd = exec.Command(dockerBinary, "rename", "first_name", newName)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- name, err := inspectField("new_name", "Name")
+ name, err := inspectField(newName, "Name")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if name != "/new_name" {
- t.Fatal("Failed to rename container ")
+ if name != "/"+newName {
+ c.Fatal("Failed to rename container ")
}
name, err = inspectField("first_name", "Name")
if err == nil && !strings.Contains(err.Error(), "No such image or container: first_name") {
- t.Fatal(err)
+ c.Fatal(err)
}
-
- logDone("rename - running container")
}
-func TestRenameInvalidName(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRenameInvalidName(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "myname", "-d", "busybox", "top")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
runCmd = exec.Command(dockerBinary, "rename", "myname", "new:invalid")
if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Invalid container name") {
- t.Fatalf("Renaming container to invalid name should have failed: %s\n%v", out, err)
+ c.Fatalf("Renaming container to invalid name should have failed: %s\n%v", out, err)
}
runCmd = exec.Command(dockerBinary, "ps", "-a")
if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "myname") {
- t.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err)
+ c.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err)
}
-
- logDone("rename - invalid container name")
}
diff --git a/integration-cli/docker_cli_restart_test.go b/integration-cli/docker_cli_restart_test.go
index 7b97c0725..fd95fd022 100644
--- a/integration-cli/docker_cli_restart_test.go
+++ b/integration-cli/docker_cli_restart_test.go
@@ -3,244 +3,221 @@ package main
import (
"os/exec"
"strings"
- "testing"
"time"
+
+ "github.com/go-check/check"
)
-func TestRestartStoppedContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRestartStoppedContainer(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "foobar")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if out != "foobar\n" {
- t.Errorf("container should've printed 'foobar'")
+ c.Errorf("container should've printed 'foobar'")
}
runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if out != "foobar\nfoobar\n" {
- t.Errorf("container should've printed 'foobar' twice")
+ c.Errorf("container should've printed 'foobar' twice")
}
- logDone("restart - echo foobar for stopped container")
}
-func TestRestartRunningContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRestartRunningContainer(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "echo foobar && sleep 30 && echo 'should not print this'")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
time.Sleep(1 * time.Second)
runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if out != "foobar\n" {
- t.Errorf("container should've printed 'foobar'")
+ c.Errorf("container should've printed 'foobar'")
}
runCmd = exec.Command(dockerBinary, "restart", "-t", "1", cleanedContainerID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
time.Sleep(1 * time.Second)
if out != "foobar\nfoobar\n" {
- t.Errorf("container should've printed 'foobar' twice")
+ c.Errorf("container should've printed 'foobar' twice")
}
- logDone("restart - echo foobar for running container")
}
// Test that restarting a container with a volume does not create a new volume on restart. Regression test for #819.
-func TestRestartWithVolumes(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRestartWithVolumes(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "-v", "/test", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if out = strings.Trim(out, " \n\r"); out != "1" {
- t.Errorf("expect 1 volume received %s", out)
+ c.Errorf("expect 1 volume received %s", out)
}
- runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ .Volumes }}", cleanedContainerID)
- volumes, _, err := runCommandWithOutput(runCmd)
- if err != nil {
- t.Fatal(volumes, err)
- }
+ volumes, err := inspectField(cleanedContainerID, ".Volumes")
+ c.Assert(err, check.IsNil)
runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if out = strings.Trim(out, " \n\r"); out != "1" {
- t.Errorf("expect 1 volume after restart received %s", out)
+ c.Errorf("expect 1 volume after restart received %s", out)
}
- runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ .Volumes }}", cleanedContainerID)
- volumesAfterRestart, _, err := runCommandWithOutput(runCmd)
- if err != nil {
- t.Fatal(volumesAfterRestart, err)
- }
+ volumesAfterRestart, err := inspectField(cleanedContainerID, ".Volumes")
+ c.Assert(err, check.IsNil)
if volumes != volumesAfterRestart {
- volumes = strings.Trim(volumes, " \n\r")
- volumesAfterRestart = strings.Trim(volumesAfterRestart, " \n\r")
- t.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart)
+ c.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart)
}
- logDone("restart - does not create a new volume on restart")
}
-func TestRestartPolicyNO(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRestartPolicyNO(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "--restart=no", "busybox", "false")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
id := strings.TrimSpace(string(out))
name, err := inspectField(id, "HostConfig.RestartPolicy.Name")
- if err != nil {
- t.Fatal(err, out)
- }
+ c.Assert(err, check.IsNil)
if name != "no" {
- t.Fatalf("Container restart policy name is %s, expected %s", name, "no")
+ c.Fatalf("Container restart policy name is %s, expected %s", name, "no")
}
- logDone("restart - recording restart policy name for --restart=no")
}
-func TestRestartPolicyAlways(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRestartPolicyAlways(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "--restart=always", "busybox", "false")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
id := strings.TrimSpace(string(out))
name, err := inspectField(id, "HostConfig.RestartPolicy.Name")
- if err != nil {
- t.Fatal(err, out)
- }
+ c.Assert(err, check.IsNil)
if name != "always" {
- t.Fatalf("Container restart policy name is %s, expected %s", name, "always")
+ c.Fatalf("Container restart policy name is %s, expected %s", name, "always")
+ }
+
+ MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount")
+ c.Assert(err, check.IsNil)
+
+ // MaximumRetryCount=0 if the restart policy is always
+ if MaximumRetryCount != "0" {
+ c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "0")
}
- logDone("restart - recording restart policy name for --restart=always")
}
-func TestRestartPolicyOnFailure(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRestartPolicyOnFailure(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:1", "busybox", "false")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
id := strings.TrimSpace(string(out))
name, err := inspectField(id, "HostConfig.RestartPolicy.Name")
- if err != nil {
- t.Fatal(err, out)
- }
+ c.Assert(err, check.IsNil)
if name != "on-failure" {
- t.Fatalf("Container restart policy name is %s, expected %s", name, "on-failure")
+ c.Fatalf("Container restart policy name is %s, expected %s", name, "on-failure")
}
- logDone("restart - recording restart policy name for --restart=on-failure")
}
// a good container with --restart=on-failure:3
// MaximumRetryCount!=0; RestartCount=0
-func TestContainerRestartwithGoodContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestContainerRestartwithGoodContainer(c *check.C) {
out, err := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:3", "busybox", "true").CombinedOutput()
if err != nil {
- t.Fatal(string(out), err)
+ c.Fatal(string(out), err)
}
id := strings.TrimSpace(string(out))
if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 5); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
count, err := inspectField(id, "RestartCount")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if count != "0" {
- t.Fatalf("Container was restarted %s times, expected %d", count, 0)
+ c.Fatalf("Container was restarted %s times, expected %d", count, 0)
}
MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if MaximumRetryCount != "3" {
- t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3")
+ c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3")
}
- logDone("restart - for a good container with restart policy, MaximumRetryCount is not 0 and RestartCount is 0")
}
diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go
index d01b36d45..f5884dc0f 100644
--- a/integration-cli/docker_cli_rm_test.go
+++ b/integration-cli/docker_cli_rm_test.go
@@ -4,94 +4,68 @@ import (
"os"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestRmContainerWithRemovedVolume(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmContainerWithRemovedVolume(c *check.C) {
+ testRequires(c, SameHostDaemon)
cmd := exec.Command(dockerBinary, "run", "--name", "losemyvolumes", "-v", "/tmp/testing:/test", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := os.Remove("/tmp/testing"); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "rm", "-v", "losemyvolumes")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- logDone("rm - removed volume")
}
-func TestRmContainerWithVolume(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmContainerWithVolume(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "foo", "-v", "/srv", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "rm", "-v", "foo")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("rm - volume")
}
-func TestRmRunningContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmRunningContainer(c *check.C) {
- createRunningContainer(t, "foo")
+ createRunningContainer(c, "foo")
// Test cannot remove running container
cmd := exec.Command(dockerBinary, "rm", "foo")
if _, err := runCommand(cmd); err == nil {
- t.Fatalf("Expected error, can't rm a running container")
+ c.Fatalf("Expected error, can't rm a running container")
}
- logDone("rm - running container")
}
-func TestRmRunningContainerCheckError409(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmForceRemoveRunningContainer(c *check.C) {
- createRunningContainer(t, "foo")
-
- endpoint := "/containers/foo"
- _, err := sockRequest("DELETE", endpoint, nil)
-
- if err == nil {
- t.Fatalf("Expected error, can't rm a running container")
- }
- if !strings.Contains(err.Error(), "409 Conflict") {
- t.Fatalf("Expected error to contain '409 Conflict' but found %s", err)
- }
-
- logDone("rm - running container")
-}
-
-func TestRmForceRemoveRunningContainer(t *testing.T) {
- defer deleteAllContainers()
-
- createRunningContainer(t, "foo")
+ createRunningContainer(c, "foo")
// Stop then remove with -s
cmd := exec.Command(dockerBinary, "rm", "-f", "foo")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- logDone("rm - running container with --force=true")
}
-func TestRmContainerOrphaning(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmContainerOrphaning(c *check.C) {
dockerfile1 := `FROM busybox:latest
ENTRYPOINT ["/bin/true"]`
@@ -102,47 +76,44 @@ func TestRmContainerOrphaning(t *testing.T) {
// build first dockerfile
img1, err := buildImage(img, dockerfile1, true)
- defer deleteImages(img1)
if err != nil {
- t.Fatalf("Could not build image %s: %v", img, err)
+ c.Fatalf("Could not build image %s: %v", img, err)
}
// run container on first image
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", img)); err != nil {
- t.Fatalf("Could not run image %s: %v: %s", img, err, out)
+ c.Fatalf("Could not run image %s: %v: %s", img, err, out)
}
// rebuild dockerfile with a small addition at the end
if _, err := buildImage(img, dockerfile2, true); err != nil {
- t.Fatalf("Could not rebuild image %s: %v", img, err)
+ c.Fatalf("Could not rebuild image %s: %v", img, err)
}
// try to remove the image, should error out.
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", img)); err == nil {
- t.Fatalf("Expected to error out removing the image, but succeeded: %s", out)
+ c.Fatalf("Expected to error out removing the image, but succeeded: %s", out)
}
// check if we deleted the first image
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc"))
if err != nil {
- t.Fatalf("%v: %s", err, out)
+ c.Fatalf("%v: %s", err, out)
}
if !strings.Contains(out, img1) {
- t.Fatalf("Orphaned container (could not find %q in docker images): %s", img1, out)
+ c.Fatalf("Orphaned container (could not find %q in docker images): %s", img1, out)
}
- logDone("rm - container orphaning")
}
-func TestRmInvalidContainer(t *testing.T) {
+func (s *DockerSuite) TestRmInvalidContainer(c *check.C) {
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rm", "unknown")); err == nil {
- t.Fatal("Expected error on rm unknown container, got none")
- } else if !strings.Contains(out, "failed to remove one or more containers") {
- t.Fatalf("Expected output to contain 'failed to remove one or more containers', got %q", out)
+ c.Fatal("Expected error on rm unknown container, got none")
+ } else if !strings.Contains(out, "failed to remove containers") {
+ c.Fatalf("Expected output to contain 'failed to remove containers', got %q", out)
}
- logDone("rm - delete unknown container")
}
-func createRunningContainer(t *testing.T, name string) {
+func createRunningContainer(c *check.C, name string) {
cmd := exec.Command(dockerBinary, "run", "-dt", "--name", name, "busybox", "top")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}
diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go
index fd34c2242..c7d0ca804 100644
--- a/integration-cli/docker_cli_rmi_test.go
+++ b/integration-cli/docker_cli_rmi_test.go
@@ -1,107 +1,145 @@
package main
import (
+ "fmt"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestRmiWithContainerFails(t *testing.T) {
+func (s *DockerSuite) TestRmiWithContainerFails(c *check.C) {
errSubstr := "is using it"
// create a container
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to create a container: %s, %v", out, err)
+ c.Fatalf("failed to create a container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
// try to delete the image
runCmd = exec.Command(dockerBinary, "rmi", "busybox")
out, _, err = runCommandWithOutput(runCmd)
if err == nil {
- t.Fatalf("Container %q is using image, should not be able to rmi: %q", cleanedContainerID, out)
+ c.Fatalf("Container %q is using image, should not be able to rmi: %q", cleanedContainerID, out)
}
if !strings.Contains(out, errSubstr) {
- t.Fatalf("Container %q is using image, error message should contain %q: %v", cleanedContainerID, errSubstr, out)
+ c.Fatalf("Container %q is using image, error message should contain %q: %v", cleanedContainerID, errSubstr, out)
}
// make sure it didn't delete the busybox name
- images, _, _ := dockerCmd(t, "images")
+ images, _ := dockerCmd(c, "images")
if !strings.Contains(images, "busybox") {
- t.Fatalf("The name 'busybox' should not have been removed from images: %q", images)
+ c.Fatalf("The name 'busybox' should not have been removed from images: %q", images)
}
-
- deleteContainer(cleanedContainerID)
-
- logDone("rmi- container using image while rmi, should not remove image name")
}
-func TestRmiTag(t *testing.T) {
- imagesBefore, _, _ := dockerCmd(t, "images", "-a")
- dockerCmd(t, "tag", "busybox", "utest:tag1")
- dockerCmd(t, "tag", "busybox", "utest/docker:tag2")
- dockerCmd(t, "tag", "busybox", "utest:5000/docker:tag3")
+func (s *DockerSuite) TestRmiTag(c *check.C) {
+ imagesBefore, _ := dockerCmd(c, "images", "-a")
+ dockerCmd(c, "tag", "busybox", "utest:tag1")
+ dockerCmd(c, "tag", "busybox", "utest/docker:tag2")
+ dockerCmd(c, "tag", "busybox", "utest:5000/docker:tag3")
{
- imagesAfter, _, _ := dockerCmd(t, "images", "-a")
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+3 {
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
+ c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
}
}
- dockerCmd(t, "rmi", "utest/docker:tag2")
+ dockerCmd(c, "rmi", "utest/docker:tag2")
{
- imagesAfter, _, _ := dockerCmd(t, "images", "-a")
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+2 {
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
+ c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
}
}
- dockerCmd(t, "rmi", "utest:5000/docker:tag3")
+ dockerCmd(c, "rmi", "utest:5000/docker:tag3")
{
- imagesAfter, _, _ := dockerCmd(t, "images", "-a")
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+1 {
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
+ c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
}
}
- dockerCmd(t, "rmi", "utest:tag1")
+ dockerCmd(c, "rmi", "utest:tag1")
{
- imagesAfter, _, _ := dockerCmd(t, "images", "-a")
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+0 {
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
+ c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
}
}
- logDone("rmi - tag,rmi- tagging the same images multiple times then removing tags")
}
-func TestRmiTagWithExistingContainers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmiImgIDForce(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("failed to create a container:%s, %v", out, err)
+ }
+ containerID := strings.TrimSpace(out)
+ runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-test")
+ out, _, err = runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("failed to commit a new busybox-test:%s, %v", out, err)
+ }
+
+ imagesBefore, _ := dockerCmd(c, "images", "-a")
+ dockerCmd(c, "tag", "busybox-test", "utest:tag1")
+ dockerCmd(c, "tag", "busybox-test", "utest:tag2")
+ dockerCmd(c, "tag", "busybox-test", "utest/docker:tag3")
+ dockerCmd(c, "tag", "busybox-test", "utest:5000/docker:tag4")
+ {
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
+ if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 {
+ c.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter)
+ }
+ }
+ imgID, err := inspectField("busybox-test", "Id")
+ c.Assert(err, check.IsNil)
+
+ // first checkout without force it fails
+ runCmd = exec.Command(dockerBinary, "rmi", imgID)
+ out, _, err = runCommandWithOutput(runCmd)
+ if err == nil || !strings.Contains(out, fmt.Sprintf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", imgID)) {
+ c.Fatalf("rmi tagged in multiple repos should have failed without force:%s, %v", out, err)
+ }
+
+ dockerCmd(c, "rmi", "-f", imgID)
+ {
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
+ if strings.Contains(imagesAfter, imgID[:12]) {
+ c.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter)
+ }
+
+ }
+}
+
+func (s *DockerSuite) TestRmiTagWithExistingContainers(c *check.C) {
container := "test-delete-tag"
newtag := "busybox:newtag"
bb := "busybox:latest"
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", bb, newtag)); err != nil {
- t.Fatalf("Could not tag busybox: %v: %s", err, out)
+ c.Fatalf("Could not tag busybox: %v: %s", err, out)
}
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", container, bb, "/bin/true")); err != nil {
- t.Fatalf("Could not run busybox: %v: %s", err, out)
+ c.Fatalf("Could not run busybox: %v: %s", err, out)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", newtag))
if err != nil {
- t.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out)
+ c.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out)
}
if d := strings.Count(out, "Untagged: "); d != 1 {
- t.Fatalf("Expected 1 untagged entry got %d: %q", d, out)
+ c.Fatalf("Expected 1 untagged entry got %d: %q", d, out)
}
- logDone("rmi - delete tag with existing containers")
}
-func TestRmiForceWithExistingContainers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmiForceWithExistingContainers(c *check.C) {
image := "busybox-clone"
@@ -110,64 +148,60 @@ func TestRmiForceWithExistingContainers(t *testing.T) {
MAINTAINER foo`)
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatalf("Could not build %s: %s, %v", image, out, err)
+ c.Fatalf("Could not build %s: %s, %v", image, out, err)
}
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "test-force-rmi", image, "/bin/true")); err != nil {
- t.Fatalf("Could not run container: %s, %v", out, err)
+ c.Fatalf("Could not run container: %s, %v", out, err)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", "-f", image))
if err != nil {
- t.Fatalf("Could not remove image %s: %s, %v", image, out, err)
+ c.Fatalf("Could not remove image %s: %s, %v", image, out, err)
}
- logDone("rmi - force delete with existing containers")
}
-func TestRmiWithMultipleRepositories(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmiWithMultipleRepositories(c *check.C) {
newRepo := "127.0.0.1:5000/busybox"
oldRepo := "busybox"
newTag := "busybox:test"
cmd := exec.Command(dockerBinary, "tag", oldRepo, newRepo)
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("Could not tag busybox: %v: %s", err, out)
+ c.Fatalf("Could not tag busybox: %v: %s", err, out)
}
cmd = exec.Command(dockerBinary, "run", "--name", "test", oldRepo, "touch", "/home/abcd")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %s", err, out)
+ c.Fatalf("failed to run container: %v, output: %s", err, out)
}
cmd = exec.Command(dockerBinary, "commit", "test", newTag)
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to commit container: %v, output: %s", err, out)
+ c.Fatalf("failed to commit container: %v, output: %s", err, out)
}
cmd = exec.Command(dockerBinary, "rmi", newTag)
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to remove image: %v, output: %s", err, out)
+ c.Fatalf("failed to remove image: %v, output: %s", err, out)
}
if !strings.Contains(out, "Untagged: "+newTag) {
- t.Fatalf("Could not remove image %s: %s, %v", newTag, out, err)
+ c.Fatalf("Could not remove image %s: %s, %v", newTag, out, err)
}
- logDone("rmi - delete a image which its dependency tagged to multiple repositories success")
}
-func TestRmiBlank(t *testing.T) {
+func (s *DockerSuite) TestRmiBlank(c *check.C) {
// try to delete a blank image name
runCmd := exec.Command(dockerBinary, "rmi", "")
out, _, err := runCommandWithOutput(runCmd)
if err == nil {
- t.Fatal("Should have failed to delete '' image")
+ c.Fatal("Should have failed to delete '' image")
}
if strings.Contains(out, "No such image") {
- t.Fatalf("Wrong error message generated: %s", out)
+ c.Fatalf("Wrong error message generated: %s", out)
}
- logDone("rmi- blank image name")
}
diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go
index 7fda0a455..20e8c6ee4 100644
--- a/integration-cli/docker_cli_run_test.go
+++ b/integration-cli/docker_cli_run_test.go
@@ -16,688 +16,571 @@ import (
"strconv"
"strings"
"sync"
- "testing"
"time"
"github.com/docker/docker/nat"
- "github.com/docker/docker/pkg/networkfs/resolvconf"
+ "github.com/docker/libnetwork/resolvconf"
+ "github.com/go-check/check"
)
// "test123" should be printed by docker run
-func TestRunEchoStdout(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunEchoStdout(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "busybox", "echo", "test123")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if out != "test123\n" {
- t.Errorf("container should've printed 'test123'")
+ c.Fatalf("container should've printed 'test123'")
}
-
- logDone("run - echo test123")
}
// "test" should be printed
-func TestRunEchoStdoutWithMemoryLimit(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunEchoStdoutWithMemoryLimit(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-m", "16m", "busybox", "echo", "test")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
out = strings.Trim(out, "\r\n")
if expected := "test"; out != expected {
- t.Errorf("container should've printed %q but printed %q", expected, out)
-
+ c.Fatalf("container should've printed %q but printed %q", expected, out)
}
-
- logDone("run - echo with memory limit")
}
// should run without memory swap
-func TestRunWithoutMemoryswapLimit(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWithoutMemoryswapLimit(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-m", "16m", "--memory-swap", "-1", "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to run container, output: %q", out)
+ c.Fatalf("failed to run container, output: %q", out)
}
-
- logDone("run - without memory swap limit")
}
// "test" should be printed
-func TestRunEchoStdoutWitCPULimit(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunEchoStdoutWitCPULimit(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "busybox", "echo", "test")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if out != "test\n" {
- t.Errorf("container should've printed 'test'")
+ c.Errorf("container should've printed 'test'")
}
-
- logDone("run - echo with CPU limit")
}
// "test" should be printed
-func TestRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunEchoStdoutWithCPUAndMemoryLimit(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "-m", "16m", "busybox", "echo", "test")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if out != "test\n" {
- t.Errorf("container should've printed 'test', got %q instead", out)
+ c.Errorf("container should've printed 'test', got %q instead", out)
}
-
- logDone("run - echo with CPU and memory limit")
}
// "test" should be printed
-func TestRunEchoNamedContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunEchoStdoutWithCPUQuota(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
+ }
+ out = strings.TrimSpace(out)
+ if strings.Contains(out, "Your kernel does not support CPU cfs quota") {
+ c.Skip("Your kernel does not support CPU cfs quota, skip this test")
+ }
+ if out != "test" {
+ c.Errorf("container should've printed 'test'")
+ }
+ out, err = inspectField("test", "HostConfig.CpuQuota")
+ c.Assert(err, check.IsNil)
+
+ if out != "8000" {
+ c.Errorf("setting the CPU CFS quota failed")
+ }
+}
+
+// "test" should be printed
+func (s *DockerSuite) TestRunEchoNamedContainer(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "testfoonamedcontainer", "busybox", "echo", "test")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if out != "test\n" {
- t.Errorf("container should've printed 'test'")
+ c.Errorf("container should've printed 'test'")
}
-
- if err := deleteContainer("testfoonamedcontainer"); err != nil {
- t.Errorf("failed to remove the named container: %v", err)
- }
-
- logDone("run - echo with named container")
}
// docker run should not leak file descriptors
-func TestRunLeakyFileDescriptors(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunLeakyFileDescriptors(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "busybox", "ls", "-C", "/proc/self/fd")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
// normally, we should only get 0, 1, and 2, but 3 gets created by "ls" when it does "opendir" on the "fd" directory
if out != "0 1 2 3\n" {
- t.Errorf("container should've printed '0 1 2 3', not: %s", out)
+ c.Errorf("container should've printed '0 1 2 3', not: %s", out)
}
-
- logDone("run - check file descriptor leakage")
}
// it should be possible to lookup Google DNS
// this will fail when Internet access is unavailable
-func TestRunLookupGoogleDns(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunLookupGoogleDns(c *check.C) {
+ testRequires(c, Network)
out, _, _, err := runCommandWithStdoutStderr(exec.Command(dockerBinary, "run", "busybox", "nslookup", "google.com"))
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
-
- logDone("run - nslookup google.com")
}
// the exit code should be 0
// some versions of lxc might make this test fail
-func TestRunExitCodeZero(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunExitCodeZero(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "busybox", "true")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Errorf("container should've exited with exit code 0: %s, %v", out, err)
+ c.Errorf("container should've exited with exit code 0: %s, %v", out, err)
}
-
- logDone("run - exit with 0")
}
// the exit code should be 1
// some versions of lxc might make this test fail
-func TestRunExitCodeOne(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunExitCodeOne(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "busybox", "false")
exitCode, err := runCommand(runCmd)
if err != nil && !strings.Contains("exit status 1", fmt.Sprintf("%s", err)) {
- t.Fatal(err)
+ c.Fatal(err)
}
if exitCode != 1 {
- t.Errorf("container should've exited with exit code 1")
+ c.Errorf("container should've exited with exit code 1")
}
-
- logDone("run - exit with 1")
}
// it should be possible to pipe in data via stdin to a process running in a container
// some versions of lxc might make this test fail
-func TestRunStdinPipe(t *testing.T) {
- defer deleteAllContainers()
-
- runCmd := exec.Command("bash", "-c", `echo "blahblah" | docker run -i -a stdin busybox cat`)
+func (s *DockerSuite) TestRunStdinPipe(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "cat")
+ runCmd.Stdin = strings.NewReader("blahblah")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
- }
-
- out = stripTrailingCharacters(out)
-
- inspectCmd := exec.Command(dockerBinary, "inspect", out)
- if out, _, err := runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("out should've been a container id: %s %v", out, err)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
+ out = strings.TrimSpace(out)
waitCmd := exec.Command(dockerBinary, "wait", out)
if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err)
+ c.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err)
}
logsCmd := exec.Command(dockerBinary, "logs", out)
logsOut, _, err := runCommandWithOutput(logsCmd)
if err != nil {
- t.Fatalf("error thrown while trying to get container logs: %s, %v", logsOut, err)
+ c.Fatalf("error thrown while trying to get container logs: %s, %v", logsOut, err)
}
- containerLogs := stripTrailingCharacters(logsOut)
+ containerLogs := strings.TrimSpace(logsOut)
if containerLogs != "blahblah" {
- t.Errorf("logs didn't print the container's logs %s", containerLogs)
+ c.Errorf("logs didn't print the container's logs %s", containerLogs)
}
rmCmd := exec.Command(dockerBinary, "rm", out)
if out, _, err = runCommandWithOutput(rmCmd); err != nil {
- t.Fatalf("rm failed to remove container: %s, %v", out, err)
+ c.Fatalf("rm failed to remove container: %s, %v", out, err)
}
-
- logDone("run - pipe in with -i -a stdin")
}
// the container's ID should be printed when starting a container in detached mode
-func TestRunDetachedContainerIDPrinting(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunDetachedContainerIDPrinting(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
- }
-
- out = stripTrailingCharacters(out)
-
- inspectCmd := exec.Command(dockerBinary, "inspect", out)
- if inspectOut, _, err := runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("out should've been a container id: %s %v", inspectOut, err)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
+ out = strings.TrimSpace(out)
waitCmd := exec.Command(dockerBinary, "wait", out)
if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err)
+ c.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err)
}
rmCmd := exec.Command(dockerBinary, "rm", out)
rmOut, _, err := runCommandWithOutput(rmCmd)
if err != nil {
- t.Fatalf("rm failed to remove container: %s, %v", rmOut, err)
+ c.Fatalf("rm failed to remove container: %s, %v", rmOut, err)
}
- rmOut = stripTrailingCharacters(rmOut)
+ rmOut = strings.TrimSpace(rmOut)
if rmOut != out {
- t.Errorf("rm didn't print the container ID %s %s", out, rmOut)
+ c.Errorf("rm didn't print the container ID %s %s", out, rmOut)
}
-
- logDone("run - print container ID in detached mode")
}
// the working directory should be set correctly
-func TestRunWorkingDirectory(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWorkingDirectory(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-w", "/root", "busybox", "pwd")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
- out = stripTrailingCharacters(out)
+ out = strings.TrimSpace(out)
if out != "/root" {
- t.Errorf("-w failed to set working directory")
+ c.Errorf("-w failed to set working directory")
}
runCmd = exec.Command(dockerBinary, "run", "--workdir", "/root", "busybox", "pwd")
out, _, _, err = runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- out = stripTrailingCharacters(out)
+ out = strings.TrimSpace(out)
if out != "/root" {
- t.Errorf("--workdir failed to set working directory")
+ c.Errorf("--workdir failed to set working directory")
}
-
- logDone("run - run with working directory set by -w")
- logDone("run - run with working directory set by --workdir")
}
// pinging Google's DNS resolver should fail when we disable the networking
-func TestRunWithoutNetworking(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWithoutNetworking(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ping", "-c", "1", "8.8.8.8")
out, _, exitCode, err := runCommandWithStdoutStderr(runCmd)
if err != nil && exitCode != 1 {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if exitCode != 1 {
- t.Errorf("--net=none should've disabled the network; the container shouldn't have been able to ping 8.8.8.8")
+ c.Errorf("--net=none should've disabled the network; the container shouldn't have been able to ping 8.8.8.8")
}
runCmd = exec.Command(dockerBinary, "run", "-n=false", "busybox", "ping", "-c", "1", "8.8.8.8")
out, _, exitCode, err = runCommandWithStdoutStderr(runCmd)
if err != nil && exitCode != 1 {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
if exitCode != 1 {
- t.Errorf("-n=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8")
+ c.Errorf("-n=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8")
}
-
- logDone("run - disable networking with --net=none")
- logDone("run - disable networking with -n=false")
}
//test --link use container name to link target
-func TestRunLinksContainerWithContainerName(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunLinksContainerWithContainerName(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-i", "-t", "-d", "--name", "parent", "busybox")
out, _, _, err := runCommandWithStdoutStderr(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
- cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", "parent")
- ip, _, _, err := runCommandWithStdoutStderr(cmd)
- if err != nil {
- t.Fatalf("failed to inspect container: %v, output: %q", err, ip)
- }
- ip = strings.TrimSpace(ip)
+ ip, err := inspectField("parent", "NetworkSettings.IPAddress")
+ c.Assert(err, check.IsNil)
cmd = exec.Command(dockerBinary, "run", "--link", "parent:test", "busybox", "/bin/cat", "/etc/hosts")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if !strings.Contains(out, ip+" test") {
- t.Fatalf("use a container name to link target failed")
+ c.Fatalf("use a container name to link target failed")
}
-
- logDone("run - use a container name to link target work")
}
//test --link use container id to link target
-func TestRunLinksContainerWithContainerId(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-i", "-t", "-d", "busybox")
cID, _, _, err := runCommandWithStdoutStderr(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, cID)
+ c.Fatalf("failed to run container: %v, output: %q", err, cID)
}
cID = strings.TrimSpace(cID)
- cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", cID)
- ip, _, _, err := runCommandWithStdoutStderr(cmd)
- if err != nil {
- t.Fatalf("faild to inspect container: %v, output: %q", err, ip)
- }
- ip = strings.TrimSpace(ip)
+ ip, err := inspectField(cID, "NetworkSettings.IPAddress")
+ c.Assert(err, check.IsNil)
cmd = exec.Command(dockerBinary, "run", "--link", cID+":test", "busybox", "/bin/cat", "/etc/hosts")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if !strings.Contains(out, ip+" test") {
- t.Fatalf("use a container id to link target failed")
+ c.Fatalf("use a container id to link target failed")
}
-
- logDone("run - use a container id to link target work")
}
-func TestRunLinkToContainerNetMode(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunLinkToContainerNetMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "test", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
cmd = exec.Command(dockerBinary, "run", "--name", "parent", "-d", "--net=container:test", "busybox", "top")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
cmd = exec.Command(dockerBinary, "run", "-d", "--link=parent:parent", "busybox", "top")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
cmd = exec.Command(dockerBinary, "run", "--name", "child", "-d", "--net=container:parent", "busybox", "top")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
cmd = exec.Command(dockerBinary, "run", "-d", "--link=child:child", "busybox", "top")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
-
- logDone("run - link to a container which net mode is container success")
}
-func TestRunModeNetContainerHostname(t *testing.T) {
- testRequires(t, ExecSupport)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunContainerNetModeWithDnsMacHosts(c *check.C) {
+ cmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top")
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
+ }
+
+ cmd = exec.Command(dockerBinary, "run", "--dns", "1.2.3.4", "--net=container:parent", "busybox")
+ out, _, err = runCommandWithOutput(cmd)
+ if err == nil || !strings.Contains(out, "Conflicting options: --dns and the network mode") {
+ c.Fatalf("run --net=container with --dns should error out")
+ }
+
+ cmd = exec.Command(dockerBinary, "run", "--mac-address", "92:d0:c6:0a:29:33", "--net=container:parent", "busybox")
+ out, _, err = runCommandWithOutput(cmd)
+ if err == nil || !strings.Contains(out, "--mac-address and the network mode") {
+ c.Fatalf("run --net=container with --mac-address should error out")
+ }
+
+ cmd = exec.Command(dockerBinary, "run", "--add-host", "test:192.168.2.109", "--net=container:parent", "busybox")
+ out, _, err = runCommandWithOutput(cmd)
+ if err == nil || !strings.Contains(out, "--add-host and the network mode") {
+ c.Fatalf("run --net=container with --add-host should error out")
+ }
+
+}
+
+func (s *DockerSuite) TestRunModeNetContainerHostname(c *check.C) {
+ testRequires(c, ExecSupport)
cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
cmd = exec.Command(dockerBinary, "exec", "parent", "cat", "/etc/hostname")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to exec command: %v, output: %q", err, out)
+ c.Fatalf("failed to exec command: %v, output: %q", err, out)
}
cmd = exec.Command(dockerBinary, "run", "--net=container:parent", "busybox", "cat", "/etc/hostname")
out1, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out1)
+ c.Fatalf("failed to run container: %v, output: %q", err, out1)
}
if out1 != out {
- t.Fatal("containers with shared net namespace should have same hostname")
+ c.Fatal("containers with shared net namespace should have same hostname")
}
-
- logDone("run - containers with shared net namespace have same hostname")
-}
-
-// Regression test for #4741
-func TestRunWithVolumesAsFiles(t *testing.T) {
- defer deleteAllContainers()
-
- runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/etc/hosts:/target-file", "busybox", "true")
- out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
- if err != nil && exitCode != 0 {
- t.Fatal("1", out, stderr, err)
- }
-
- runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/target-file")
- out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd)
- if err != nil && exitCode != 0 {
- t.Fatal("2", out, stderr, err)
- }
-
- logDone("run - regression test for #4741 - volumes from as files")
}
// Regression test for #4979
-func TestRunWithVolumesFromExited(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWithVolumesFromExited(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/some/dir", "busybox", "touch", "/some/dir/file")
out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
if err != nil && exitCode != 0 {
- t.Fatal("1", out, stderr, err)
+ c.Fatal("1", out, stderr, err)
}
runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/some/dir/file")
out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd)
if err != nil && exitCode != 0 {
- t.Fatal("2", out, stderr, err)
+ c.Fatal("2", out, stderr, err)
}
-
- logDone("run - regression test for #4979 - volumes-from on exited container")
}
// Volume path is a symlink which also exists on the host, and the host side is a file not a dir
// But the volume call is just a normal volume, not a bind mount
-func TestRunCreateVolumesInSymlinkDir(t *testing.T) {
- testRequires(t, SameHostDaemon)
- testRequires(t, NativeExecDriver)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunCreateVolumesInSymlinkDir(c *check.C) {
+ testRequires(c, SameHostDaemon)
+ testRequires(c, NativeExecDriver)
name := "test-volume-symlink"
dir, err := ioutil.TempDir("", name)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(dir)
f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_CREATE, 0700)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
f.Close()
dockerFile := fmt.Sprintf("FROM busybox\nRUN mkdir -p %s\nRUN ln -s %s /test", dir, dir)
if _, err := buildImage(name, dockerFile, false); err != nil {
- t.Fatal(err)
- }
- defer deleteImages(name)
-
- if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err)
}
- logDone("run - create volume in symlink directory")
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/test/test", name))
+ if err != nil {
+ c.Fatalf("Failed with errors: %s, %v", out, err)
+ }
}
-// Regression test for #4830
-func TestRunWithRelativePath(t *testing.T) {
- defer deleteAllContainers()
-
- runCmd := exec.Command(dockerBinary, "run", "-v", "tmp:/other-tmp", "busybox", "true")
- if _, _, _, err := runCommandWithStdoutStderr(runCmd); err == nil {
- t.Fatalf("relative path should result in an error")
- }
-
- logDone("run - volume with relative path")
-}
-
-func TestRunVolumesMountedAsReadonly(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunVolumesMountedAsReadonly(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-v", "/test:/test:ro", "busybox", "touch", "/test/somefile")
if code, err := runCommand(cmd); err == nil || code == 0 {
- t.Fatalf("run should fail because volume is ro: exit code %d", code)
+ c.Fatalf("run should fail because volume is ro: exit code %d", code)
}
-
- logDone("run - volumes as readonly mount")
}
-func TestRunVolumesFromInReadonlyMode(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunVolumesFromInReadonlyMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:ro", "busybox", "touch", "/test/file")
if code, err := runCommand(cmd); err == nil || code == 0 {
- t.Fatalf("run should fail because volume is ro: exit code %d", code)
+ c.Fatalf("run should fail because volume is ro: exit code %d", code)
}
-
- logDone("run - volumes from as readonly mount")
}
// Regression test for #1201
-func TestRunVolumesFromInReadWriteMode(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunVolumesFromInReadWriteMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatalf("running --volumes-from parent:rw failed with output: %q\nerror: %v", out, err)
+ c.Fatalf("running --volumes-from parent:rw failed with output: %q\nerror: %v", out, err)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file")
if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "invalid mode for volumes-from: bar") {
- t.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out)
+ c.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "busybox", "touch", "/test/file")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatalf("running --volumes-from parent failed with output: %q\nerror: %v", out, err)
+ c.Fatalf("running --volumes-from parent failed with output: %q\nerror: %v", out, err)
}
-
- logDone("run - volumes from as read write mount")
}
-func TestVolumesFromGetsProperMode(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestVolumesFromGetsProperMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test:/test:ro", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- // Expect this "rw" mode to be be ignored since the inheritted volume is "ro"
+ // Expect this "rw" mode to be be ignored since the inherited volume is "ro"
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file")
if _, err := runCommand(cmd); err == nil {
- t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `rw`")
+ c.Fatal("Expected volumes-from to inherit read-only volume even when passing in `rw`")
}
cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/test:/test:ro", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Expect this to be read-only since both are "ro"
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent2:ro", "busybox", "touch", "/test/file")
if _, err := runCommand(cmd); err == nil {
- t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`")
+ c.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`")
}
-
- logDone("run - volumes from ignores `rw` if inherrited volume is `ro`")
}
// Test for GH#10618
-func TestRunNoDupVolumes(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunNoDupVolumes(c *check.C) {
mountstr1 := randomUnixTmpDirPath("test1") + ":/someplace"
mountstr2 := randomUnixTmpDirPath("test2") + ":/someplace"
cmd := exec.Command(dockerBinary, "run", "-v", mountstr1, "-v", mountstr2, "busybox", "true")
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal("Expected error about duplicate volume definitions")
+ c.Fatal("Expected error about duplicate volume definitions")
} else {
- if !strings.Contains(out, "Duplicate volume") {
- t.Fatalf("Expected 'duplicate volume' error, got %v", err)
+ if !strings.Contains(out, "Duplicate bind mount") {
+ c.Fatalf("Expected 'duplicate volume' error, got %v", err)
}
}
-
- logDone("run - don't allow multiple (bind) volumes on the same container target")
}
// Test for #1351
-func TestRunApplyVolumesFromBeforeVolumes(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunApplyVolumesFromBeforeVolumes(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "touch", "/test/foo")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "-v", "/test", "busybox", "cat", "/test/foo")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
-
- logDone("run - volumes from mounted first")
}
-func TestRunMultipleVolumesFrom(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunMultipleVolumesFrom(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--name", "parent1", "-v", "/test", "busybox", "touch", "/test/foo")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/other", "busybox", "touch", "/other/bar")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent1", "--volumes-from", "parent2",
"busybox", "sh", "-c", "cat /test/foo && cat /other/bar")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
-
- logDone("run - multiple volumes from")
}
// this tests verifies the ID format for the container
-func TestRunVerifyContainerID(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunVerifyContainerID(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, exit, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if exit != 0 {
- t.Fatalf("expected exit code 0 received %d", exit)
+ c.Fatalf("expected exit code 0 received %d", exit)
}
match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n"))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !match {
- t.Fatalf("Invalid container ID: %s", out)
+ c.Fatalf("Invalid container ID: %s", out)
}
-
- logDone("run - verify container ID")
}
// Test that creating a container with a volume doesn't crash. Regression test for #995.
-func TestRunCreateVolume(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCreateVolume(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
-
- logDone("run - create docker managed volume")
}
// Test that creating a volume with a symlink in its path works correctly. Test for #5152.
// Note that this bug happens only with symlinks with a target that starts with '/'.
-func TestRunCreateVolumeWithSymlink(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCreateVolumeWithSymlink(c *check.C) {
image := "docker-test-createvolumewithsymlink"
- defer deleteImages(image)
buildCmd := exec.Command(dockerBinary, "build", "-t", image, "-")
buildCmd.Stdin = strings.NewReader(`FROM busybox
@@ -705,43 +588,38 @@ func TestRunCreateVolumeWithSymlink(t *testing.T) {
buildCmd.Dir = workingDirectory
err := buildCmd.Run()
if err != nil {
- t.Fatalf("could not build '%s': %v", image, err)
+ c.Fatalf("could not build '%s': %v", image, err)
}
cmd := exec.Command(dockerBinary, "run", "-v", "/bar/foo", "--name", "test-createvolumewithsymlink", image, "sh", "-c", "mount | grep -q /home/foo")
exitCode, err := runCommand(cmd)
if err != nil || exitCode != 0 {
- t.Fatalf("[run] err: %v, exitcode: %d", err, exitCode)
+ c.Fatalf("[run] err: %v, exitcode: %d", err, exitCode)
}
var volPath string
cmd = exec.Command(dockerBinary, "inspect", "-f", "{{range .Volumes}}{{.}}{{end}}", "test-createvolumewithsymlink")
volPath, exitCode, err = runCommandWithOutput(cmd)
if err != nil || exitCode != 0 {
- t.Fatalf("[inspect] err: %v, exitcode: %d", err, exitCode)
+ c.Fatalf("[inspect] err: %v, exitcode: %d", err, exitCode)
}
cmd = exec.Command(dockerBinary, "rm", "-v", "test-createvolumewithsymlink")
exitCode, err = runCommand(cmd)
if err != nil || exitCode != 0 {
- t.Fatalf("[rm] err: %v, exitcode: %d", err, exitCode)
+ c.Fatalf("[rm] err: %v, exitcode: %d", err, exitCode)
}
f, err := os.Open(volPath)
defer f.Close()
if !os.IsNotExist(err) {
- t.Fatalf("[open] (expecting 'file does not exist' error) err: %v, volPath: %s", err, volPath)
+ c.Fatalf("[open] (expecting 'file does not exist' error) err: %v, volPath: %s", err, volPath)
}
-
- logDone("run - create volume with symlink")
}
// Tests that a volume path that has a symlink exists in a container mounting it with `--volumes-from`.
-func TestRunVolumesFromSymlinkPath(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunVolumesFromSymlinkPath(c *check.C) {
name := "docker-test-volumesfromsymlinkpath"
- defer deleteImages(name)
buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-")
buildCmd.Stdin = strings.NewReader(`FROM busybox
@@ -750,166 +628,131 @@ func TestRunVolumesFromSymlinkPath(t *testing.T) {
buildCmd.Dir = workingDirectory
err := buildCmd.Run()
if err != nil {
- t.Fatalf("could not build 'docker-test-volumesfromsymlinkpath': %v", err)
+ c.Fatalf("could not build 'docker-test-volumesfromsymlinkpath': %v", err)
}
cmd := exec.Command(dockerBinary, "run", "--name", "test-volumesfromsymlinkpath", name)
exitCode, err := runCommand(cmd)
if err != nil || exitCode != 0 {
- t.Fatalf("[run] (volume) err: %v, exitcode: %d", err, exitCode)
+ c.Fatalf("[run] (volume) err: %v, exitcode: %d", err, exitCode)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-volumesfromsymlinkpath", "busybox", "sh", "-c", "ls /foo | grep -q bar")
exitCode, err = runCommand(cmd)
if err != nil || exitCode != 0 {
- t.Fatalf("[run] err: %v, exitcode: %d", err, exitCode)
+ c.Fatalf("[run] err: %v, exitcode: %d", err, exitCode)
}
-
- logDone("run - volumes-from symlink path")
}
-func TestRunExitCode(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunExitCode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72")
exit, err := runCommand(cmd)
if err == nil {
- t.Fatal("should not have a non nil error")
+ c.Fatal("should not have a non nil error")
}
if exit != 72 {
- t.Fatalf("expected exit code 72 received %d", exit)
+ c.Fatalf("expected exit code 72 received %d", exit)
}
-
- logDone("run - correct exit code")
}
-func TestRunUserDefaultsToRoot(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUserDefaultsToRoot(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "id")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "uid=0(root) gid=0(root)") {
- t.Fatalf("expected root user got %s", out)
+ c.Fatalf("expected root user got %s", out)
}
-
- logDone("run - default user")
}
-func TestRunUserByName(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUserByName(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "uid=0(root) gid=0(root)") {
- t.Fatalf("expected root user got %s", out)
+ c.Fatalf("expected root user got %s", out)
}
-
- logDone("run - user by name")
}
-func TestRunUserByID(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUserByID(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") {
- t.Fatalf("expected daemon user got %s", out)
+ c.Fatalf("expected daemon user got %s", out)
}
-
- logDone("run - user by id")
}
-func TestRunUserByIDBig(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUserByIDBig(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-u", "2147483648", "busybox", "id")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal("No error, but must be.", out)
+ c.Fatal("No error, but must be.", out)
}
if !strings.Contains(out, "Uids and gids must be in range") {
- t.Fatalf("expected error about uids range, got %s", out)
+ c.Fatalf("expected error about uids range, got %s", out)
}
-
- logDone("run - user by id, id too big")
}
-func TestRunUserByIDNegative(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUserByIDNegative(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-u", "-1", "busybox", "id")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal("No error, but must be.", out)
+ c.Fatal("No error, but must be.", out)
}
if !strings.Contains(out, "Uids and gids must be in range") {
- t.Fatalf("expected error about uids range, got %s", out)
+ c.Fatalf("expected error about uids range, got %s", out)
}
-
- logDone("run - user by id, id negative")
}
-func TestRunUserByIDZero(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUserByIDZero(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-u", "0", "busybox", "id")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "uid=0(root) gid=0(root) groups=10(wheel)") {
- t.Fatalf("expected daemon user got %s", out)
+ c.Fatalf("expected daemon user got %s", out)
}
-
- logDone("run - user by id, zero uid")
}
-func TestRunUserNotFound(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUserNotFound(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id")
_, err := runCommand(cmd)
if err == nil {
- t.Fatal("unknown user should cause container to fail")
+ c.Fatal("unknown user should cause container to fail")
}
-
- logDone("run - user not found")
}
-func TestRunTwoConcurrentContainers(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunTwoConcurrentContainers(c *check.C) {
group := sync.WaitGroup{}
group.Add(2)
+ errChan := make(chan error, 2)
for i := 0; i < 2; i++ {
go func() {
defer group.Done()
cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2")
- if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
- }
+ _, err := runCommand(cmd)
+ errChan <- err
}()
}
group.Wait()
+ close(errChan)
- logDone("run - two concurrent containers")
+ for err := range errChan {
+ c.Assert(err, check.IsNil)
+ }
}
-func TestRunEnvironment(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunEnvironment(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "-e=HOME=", "busybox", "env")
cmd.Env = append(os.Environ(),
"TRUE=false",
@@ -918,7 +761,7 @@ func TestRunEnvironment(t *testing.T) {
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n")
@@ -942,29 +785,26 @@ func TestRunEnvironment(t *testing.T) {
}
sort.Strings(goodEnv)
if len(goodEnv) != len(actualEnv) {
- t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
+ c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
}
for i := range goodEnv {
if actualEnv[i] != goodEnv[i] {
- t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
+ c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
}
}
-
- logDone("run - verify environment")
}
-func TestRunEnvironmentErase(t *testing.T) {
+func (s *DockerSuite) TestRunEnvironmentErase(c *check.C) {
// Test to make sure that when we use -e on env vars that are
// not set in our local env that they're removed (if present) in
// the container
- defer deleteAllContainers()
cmd := exec.Command(dockerBinary, "run", "-e", "FOO", "-e", "HOSTNAME", "busybox", "env")
cmd.Env = appendBaseEnv([]string{})
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n")
@@ -982,28 +822,25 @@ func TestRunEnvironmentErase(t *testing.T) {
}
sort.Strings(goodEnv)
if len(goodEnv) != len(actualEnv) {
- t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
+ c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
}
for i := range goodEnv {
if actualEnv[i] != goodEnv[i] {
- t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
+ c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
}
}
-
- logDone("run - verify environment erase")
}
-func TestRunEnvironmentOverride(t *testing.T) {
+func (s *DockerSuite) TestRunEnvironmentOverride(c *check.C) {
// Test to make sure that when we use -e on env vars that are
// already in the env that we're overriding them
- defer deleteAllContainers()
cmd := exec.Command(dockerBinary, "run", "-e", "HOSTNAME", "-e", "HOME=/root2", "busybox", "env")
cmd.Env = appendBaseEnv([]string{"HOSTNAME=bar"})
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n")
@@ -1022,60 +859,47 @@ func TestRunEnvironmentOverride(t *testing.T) {
}
sort.Strings(goodEnv)
if len(goodEnv) != len(actualEnv) {
- t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
+ c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
}
for i := range goodEnv {
if actualEnv[i] != goodEnv[i] {
- t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
+ c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
}
}
-
- logDone("run - verify environment override")
}
-func TestRunContainerNetwork(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunContainerNetwork(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
-
- logDone("run - test container network via ping")
}
// Issue #4681
-func TestRunLoopbackWhenNetworkDisabled(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunLoopbackWhenNetworkDisabled(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ping", "-c", "1", "127.0.0.1")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
-
- logDone("run - test container loopback when networking disabled")
}
-func TestRunNetHostNotAllowedWithLinks(t *testing.T) {
- defer deleteAllContainers()
-
- _, _, err := dockerCmd(t, "run", "--name", "linked", "busybox", "true")
+func (s *DockerSuite) TestRunNetHostNotAllowedWithLinks(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "linked", "busybox", "true"))
+ if err != nil {
+ c.Fatalf("Failed with errors: %s, %v", out, err)
+ }
cmd := exec.Command(dockerBinary, "run", "--net=host", "--link", "linked:linked", "busybox", "true")
_, _, err = runCommandWithOutput(cmd)
if err == nil {
- t.Fatal("Expected error")
+ c.Fatal("Expected error")
}
-
- logDone("run - don't allow --net=host to be used with links")
}
-func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunLoopbackOnlyExistsWhenNetworkingDisabled(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ip", "-o", "-4", "a", "show", "up")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
var (
@@ -1090,14 +914,12 @@ func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) {
}
if count != 1 {
- t.Fatalf("Wrong interface count in container %d", count)
+ c.Fatalf("Wrong interface count in container %d", count)
}
if !strings.HasPrefix(out, "1: lo") {
- t.Fatalf("Wrong interface in test container: expected [1: lo], got %s", out)
+ c.Fatalf("Wrong interface in test container: expected [1: lo], got %s", out)
}
-
- logDone("run - test loopback only exists when networking disabled")
}
// #7851 hostname outside container shows FQDN, inside only shortname
@@ -1105,293 +927,252 @@ func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) {
// and use "--net=host" (as the original issue submitter did), as the same
// codepath is executed with "docker run -h ". Both were manually
// tested, but this testcase takes the simpler path of using "run -h .."
-func TestRunFullHostnameSet(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunFullHostnameSet(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-h", "foo.bar.baz", "busybox", "hostname")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual != "foo.bar.baz" {
- t.Fatalf("expected hostname 'foo.bar.baz', received %s", actual)
+ c.Fatalf("expected hostname 'foo.bar.baz', received %s", actual)
}
-
- logDone("run - test fully qualified hostname set with -h")
}
-func TestRunPrivilegedCanMknod(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunPrivilegedCanMknod(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if actual := strings.Trim(out, "\r\n"); actual != "ok" {
- t.Fatalf("expected output ok received %s", actual)
+ c.Fatalf("expected output ok received %s", actual)
}
-
- logDone("run - test privileged can mknod")
}
-func TestRunUnPrivilegedCanMknod(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUnprivilegedCanMknod(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if actual := strings.Trim(out, "\r\n"); actual != "ok" {
- t.Fatalf("expected output ok received %s", actual)
+ c.Fatalf("expected output ok received %s", actual)
}
-
- logDone("run - test un-privileged can mknod")
}
-func TestRunCapDropInvalid(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunCapDropInvalid(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-drop=CHPASS", "busybox", "ls")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
-
- logDone("run - test --cap-drop=CHPASS invalid")
}
-func TestRunCapDropCannotMknod(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapDropCannotMknod(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-drop=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual == "ok" {
- t.Fatalf("expected output not ok received %s", actual)
+ c.Fatalf("expected output not ok received %s", actual)
}
-
- logDone("run - test --cap-drop=MKNOD cannot mknod")
}
-func TestRunCapDropCannotMknodLowerCase(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapDropCannotMknodLowerCase(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-drop=mknod", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual == "ok" {
- t.Fatalf("expected output not ok received %s", actual)
+ c.Fatalf("expected output not ok received %s", actual)
}
-
- logDone("run - test --cap-drop=mknod cannot mknod lowercase")
}
-func TestRunCapDropALLCannotMknod(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapDropALLCannotMknod(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "--cap-add=SETGID", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual == "ok" {
- t.Fatalf("expected output not ok received %s", actual)
+ c.Fatalf("expected output not ok received %s", actual)
}
-
- logDone("run - test --cap-drop=ALL cannot mknod")
}
-func TestRunCapDropALLAddMknodCanMknod(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapDropALLAddMknodCanMknod(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "--cap-add=MKNOD", "--cap-add=SETGID", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual != "ok" {
- t.Fatalf("expected output ok received %s", actual)
+ c.Fatalf("expected output ok received %s", actual)
}
-
- logDone("run - test --cap-drop=ALL --cap-add=MKNOD can mknod")
}
-func TestRunCapAddInvalid(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapAddInvalid(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-add=CHPASS", "busybox", "ls")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
-
- logDone("run - test --cap-add=CHPASS invalid")
}
-func TestRunCapAddCanDownInterface(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapAddCanDownInterface(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-add=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual != "ok" {
- t.Fatalf("expected output ok received %s", actual)
+ c.Fatalf("expected output ok received %s", actual)
}
-
- logDone("run - test --cap-add=NET_ADMIN can set eth0 down")
}
-func TestRunCapAddALLCanDownInterface(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapAddALLCanDownInterface(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "busybox", "sh", "-c", "ip link set eth0 down && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual != "ok" {
- t.Fatalf("expected output ok received %s", actual)
+ c.Fatalf("expected output ok received %s", actual)
}
-
- logDone("run - test --cap-add=ALL can set eth0 down")
}
-func TestRunCapAddALLDropNetAdminCanDownInterface(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCapAddALLDropNetAdminCanDownInterface(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "--cap-drop=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual == "ok" {
- t.Fatalf("expected output not ok received %s", actual)
+ c.Fatalf("expected output not ok received %s", actual)
}
-
- logDone("run - test --cap-add=ALL --cap-drop=NET_ADMIN cannot set eth0 down")
}
-func TestRunPrivilegedCanMount(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunPrivilegedCanMount(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if actual := strings.Trim(out, "\r\n"); actual != "ok" {
- t.Fatalf("expected output ok received %s", actual)
+ c.Fatalf("expected output ok received %s", actual)
}
-
- logDone("run - test privileged can mount")
}
-func TestRunUnPrivilegedCannotMount(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUnprivilegedCannotMount(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual == "ok" {
- t.Fatalf("expected output not ok received %s", actual)
+ c.Fatalf("expected output not ok received %s", actual)
}
-
- logDone("run - test un-privileged cannot mount")
}
-func TestRunSysNotWritableInNonPrivilegedContainers(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunSysNotWritableInNonPrivilegedContainers(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "touch", "/sys/kernel/profiling")
if code, err := runCommand(cmd); err == nil || code == 0 {
- t.Fatal("sys should not be writable in a non privileged container")
+ c.Fatal("sys should not be writable in a non privileged container")
}
-
- logDone("run - sys not writable in non privileged container")
}
-func TestRunSysWritableInPrivilegedContainers(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunSysWritableInPrivilegedContainers(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "touch", "/sys/kernel/profiling")
if code, err := runCommand(cmd); err != nil || code != 0 {
- t.Fatalf("sys should be writable in privileged container")
+ c.Fatalf("sys should be writable in privileged container")
}
-
- logDone("run - sys writable in privileged container")
}
-func TestRunProcNotWritableInNonPrivilegedContainers(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunProcNotWritableInNonPrivilegedContainers(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "touch", "/proc/sysrq-trigger")
if code, err := runCommand(cmd); err == nil || code == 0 {
- t.Fatal("proc should not be writable in a non privileged container")
+ c.Fatal("proc should not be writable in a non privileged container")
}
-
- logDone("run - proc not writable in non privileged container")
}
-func TestRunProcWritableInPrivilegedContainers(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunProcWritableInPrivilegedContainers(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "touch", "/proc/sysrq-trigger")
if code, err := runCommand(cmd); err != nil || code != 0 {
- t.Fatalf("proc should be writable in privileged container")
+ c.Fatalf("proc should be writable in privileged container")
}
- logDone("run - proc writable in privileged container")
}
-func TestRunWithCpuset(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWithCpuPeriod(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "--cpu-period", "50000", "--name", "test", "busybox", "true")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
+ }
+ out = strings.TrimSpace(out)
+ if strings.Contains(out, "Your kernel does not support CPU cfs period") {
+ c.Skip("Your kernel does not support CPU cfs period, skip this test")
+ }
+ out, err = inspectField("test", "HostConfig.CpuPeriod")
+ c.Assert(err, check.IsNil)
+ if out != "50000" {
+ c.Errorf("setting the CPU CFS period failed")
+ }
+}
+
+func (s *DockerSuite) TestRunWithCpuset(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cpuset", "0", "busybox", "true")
if code, err := runCommand(cmd); err != nil || code != 0 {
- t.Fatalf("container should run successfuly with cpuset of 0: %s", err)
+ c.Fatalf("container should run successfully with cpuset of 0: %s", err)
}
-
- logDone("run - cpuset 0")
}
-func TestRunWithCpusetCpus(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWithCpusetCpus(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--cpuset-cpus", "0", "busybox", "true")
if code, err := runCommand(cmd); err != nil || code != 0 {
- t.Fatalf("container should run successfuly with cpuset-cpus of 0: %s", err)
+ c.Fatalf("container should run successfully with cpuset-cpus of 0: %s", err)
}
-
- logDone("run - cpuset-cpus 0")
}
-func TestRunDeviceNumbers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWithCpusetMems(c *check.C) {
+ cmd := exec.Command(dockerBinary, "run", "--cpuset-mems", "0", "busybox", "true")
+ if code, err := runCommand(cmd); err != nil || code != 0 {
+ c.Fatalf("container should run successfully with cpuset-mems of 0: %s", err)
+ }
+}
+func (s *DockerSuite) TestRunWithBlkioWeight(c *check.C) {
+ cmd := exec.Command(dockerBinary, "run", "--blkio-weight", "300", "busybox", "true")
+ if code, err := runCommand(cmd); err != nil || code != 0 {
+ c.Fatalf("container should run successfully with blkio-weight of 300: %s", err)
+ }
+}
+
+func (s *DockerSuite) TestRunWithBlkioInvalidWeight(c *check.C) {
+ cmd := exec.Command(dockerBinary, "run", "--blkio-weight", "5", "busybox", "true")
+ if _, err := runCommand(cmd); err == nil {
+ c.Fatalf("run with invalid blkio-weight should failed")
+ }
+}
+
+func (s *DockerSuite) TestRunDeviceNumbers(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "ls -l /dev/null")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
deviceLineFields := strings.Fields(out)
deviceLineFields[6] = ""
@@ -1400,137 +1181,107 @@ func TestRunDeviceNumbers(t *testing.T) {
expected := []string{"crw-rw-rw-", "1", "root", "root", "1,", "3", "", "", "", "/dev/null"}
if !(reflect.DeepEqual(deviceLineFields, expected)) {
- t.Fatalf("expected output\ncrw-rw-rw- 1 root root 1, 3 May 24 13:29 /dev/null\n received\n %s\n", out)
+ c.Fatalf("expected output\ncrw-rw-rw- 1 root root 1, 3 May 24 13:29 /dev/null\n received\n %s\n", out)
}
-
- logDone("run - test device numbers")
}
-func TestRunThatCharacterDevicesActLikeCharacterDevices(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunThatCharacterDevicesActLikeCharacterDevices(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "dd if=/dev/zero of=/zero bs=1k count=5 2> /dev/null ; du -h /zero")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual[0] == '0' {
- t.Fatalf("expected a new file called /zero to be create that is greater than 0 bytes long, but du says: %s", actual)
+ c.Fatalf("expected a new file called /zero to be create that is greater than 0 bytes long, but du says: %s", actual)
}
-
- logDone("run - test that character devices work.")
}
-func TestRunUnprivilegedWithChroot(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunUnprivilegedWithChroot(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "busybox", "chroot", "/", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
-
- logDone("run - unprivileged with chroot")
}
-func TestRunAddingOptionalDevices(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunAddingOptionalDevices(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--device", "/dev/zero:/dev/nulo", "busybox", "sh", "-c", "ls /dev/nulo")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual != "/dev/nulo" {
- t.Fatalf("expected output /dev/nulo, received %s", actual)
+ c.Fatalf("expected output /dev/nulo, received %s", actual)
}
-
- logDone("run - test --device argument")
}
-func TestRunModeHostname(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunModeHostname(c *check.C) {
+ testRequires(c, SameHostDaemon)
cmd := exec.Command(dockerBinary, "run", "-h=testhostname", "busybox", "cat", "/etc/hostname")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actual := strings.Trim(out, "\r\n"); actual != "testhostname" {
- t.Fatalf("expected 'testhostname', but says: %q", actual)
+ c.Fatalf("expected 'testhostname', but says: %q", actual)
}
cmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "cat", "/etc/hostname")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
hostname, err := os.Hostname()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if actual := strings.Trim(out, "\r\n"); actual != hostname {
- t.Fatalf("expected %q, but says: %q", hostname, actual)
+ c.Fatalf("expected %q, but says: %q", hostname, actual)
}
-
- logDone("run - hostname and several network modes")
}
-func TestRunRootWorkdir(t *testing.T) {
- defer deleteAllContainers()
-
- s, _, err := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd")
+func (s *DockerSuite) TestRunRootWorkdir(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--workdir", "/", "busybox", "pwd"))
if err != nil {
- t.Fatal(s, err)
+ c.Fatalf("Failed with errors: %s, %v", out, err)
}
- if s != "/\n" {
- t.Fatalf("pwd returned %q (expected /\\n)", s)
+ if out != "/\n" {
+ c.Fatalf("pwd returned %q (expected /\\n)", s)
}
-
- logDone("run - workdir /")
}
-func TestRunAllowBindMountingRoot(t *testing.T) {
- defer deleteAllContainers()
-
- s, _, err := dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host")
+func (s *DockerSuite) TestRunAllowBindMountingRoot(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/:/host", "busybox", "ls", "/host"))
if err != nil {
- t.Fatal(s, err)
+ c.Fatalf("Failed with errors: %s, %v", out, err)
}
-
- logDone("run - bind mount / as volume")
}
-func TestRunDisallowBindMountingRootToRoot(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunDisallowBindMountingRootToRoot(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-v", "/:/", "busybox", "ls", "/host")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
-
- logDone("run - bind mount /:/ as volume should not work")
}
// Verify that a container gets default DNS when only localhost resolvers exist
-func TestRunDnsDefaultOptions(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunDnsDefaultOptions(c *check.C) {
+ testRequires(c, SameHostDaemon)
// preserve original resolv.conf for restoring after test
origResolvConf, err := ioutil.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) {
- t.Fatalf("/etc/resolv.conf does not exist")
+ c.Fatalf("/etc/resolv.conf does not exist")
}
// defer restored original conf
defer func() {
if err := ioutil.WriteFile("/etc/resolv.conf", origResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}()
@@ -1539,14 +1290,14 @@ func TestRunDnsDefaultOptions(t *testing.T) {
// GetNameservers(), leading to a replacement of nameservers with the default set
tmpResolvConf := []byte("nameserver 127.0.0.1\n#nameserver 127.0.2.1\nnameserver ::1")
if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "busybox", "cat", "/etc/resolv.conf")
actual, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, actual)
+ c.Fatal(err, actual)
}
// check that the actual defaults are appended to the commented out
@@ -1554,54 +1305,47 @@ func TestRunDnsDefaultOptions(t *testing.T) {
// NOTE: if we ever change the defaults from google dns, this will break
expected := "#nameserver 127.0.2.1\n\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
if actual != expected {
- t.Fatalf("expected resolv.conf be: %q, but was: %q", expected, actual)
+ c.Fatalf("expected resolv.conf be: %q, but was: %q", expected, actual)
}
-
- logDone("run - dns default options")
}
-func TestRunDnsOptions(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunDnsOptions(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf")
out, stderr, _, err := runCommandWithStdoutStderr(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
// The client will get a warning on stderr when setting DNS to a localhost address; verify this:
if !strings.Contains(stderr, "Localhost DNS setting") {
- t.Fatalf("Expected warning on stderr about localhost resolver, but got %q", stderr)
+ c.Fatalf("Expected warning on stderr about localhost resolver, but got %q", stderr)
}
actual := strings.Replace(strings.Trim(out, "\r\n"), "\n", " ", -1)
if actual != "nameserver 127.0.0.1 search mydomain" {
- t.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: %q", actual)
+ c.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: %q", actual)
}
cmd = exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=.", "busybox", "cat", "/etc/resolv.conf")
out, _, _, err = runCommandWithStdoutStderr(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
actual = strings.Replace(strings.Trim(strings.Trim(out, "\r\n"), " "), "\n", " ", -1)
if actual != "nameserver 127.0.0.1" {
- t.Fatalf("expected 'nameserver 127.0.0.1', but says: %q", actual)
+ c.Fatalf("expected 'nameserver 127.0.0.1', but says: %q", actual)
}
-
- logDone("run - dns options")
}
-func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunDnsOptionsBasedOnHostResolvConf(c *check.C) {
+ testRequires(c, SameHostDaemon)
origResolvConf, err := ioutil.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) {
- t.Fatalf("/etc/resolv.conf does not exist")
+ c.Fatalf("/etc/resolv.conf does not exist")
}
hostNamservers := resolvconf.GetNameservers(origResolvConf)
@@ -1610,58 +1354,58 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) {
var out string
cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "busybox", "cat", "/etc/resolv.conf")
if out, _, _, err = runCommandWithStdoutStderr(cmd); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actualNameservers := resolvconf.GetNameservers([]byte(out)); string(actualNameservers[0]) != "127.0.0.1" {
- t.Fatalf("expected '127.0.0.1', but says: %q", string(actualNameservers[0]))
+ c.Fatalf("expected '127.0.0.1', but says: %q", string(actualNameservers[0]))
}
actualSearch := resolvconf.GetSearchDomains([]byte(out))
if len(actualSearch) != len(hostSearch) {
- t.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch))
+ c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch))
}
for i := range actualSearch {
if actualSearch[i] != hostSearch[i] {
- t.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i])
+ c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i])
}
}
cmd = exec.Command(dockerBinary, "run", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf")
if out, _, err = runCommandWithOutput(cmd); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
actualNameservers := resolvconf.GetNameservers([]byte(out))
if len(actualNameservers) != len(hostNamservers) {
- t.Fatalf("expected %q nameserver(s), but it has: %q", len(hostNamservers), len(actualNameservers))
+ c.Fatalf("expected %q nameserver(s), but it has: %q", len(hostNamservers), len(actualNameservers))
}
for i := range actualNameservers {
if actualNameservers[i] != hostNamservers[i] {
- t.Fatalf("expected %q nameserver, but says: %q", actualNameservers[i], hostNamservers[i])
+ c.Fatalf("expected %q nameserver, but says: %q", actualNameservers[i], hostNamservers[i])
}
}
if actualSearch = resolvconf.GetSearchDomains([]byte(out)); string(actualSearch[0]) != "mydomain" {
- t.Fatalf("expected 'mydomain', but says: %q", string(actualSearch[0]))
+ c.Fatalf("expected 'mydomain', but says: %q", string(actualSearch[0]))
}
// test with file
tmpResolvConf := []byte("search example.com\nnameserver 12.34.56.78\nnameserver 127.0.0.1")
if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// put the old resolvconf back
defer func() {
if err := ioutil.WriteFile("/etc/resolv.conf", origResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}()
resolvConf, err := ioutil.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) {
- t.Fatalf("/etc/resolv.conf does not exist")
+ c.Fatalf("/etc/resolv.conf does not exist")
}
hostNamservers = resolvconf.GetNameservers(resolvConf)
@@ -1670,35 +1414,56 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) {
cmd = exec.Command(dockerBinary, "run", "busybox", "cat", "/etc/resolv.conf")
if out, _, err = runCommandWithOutput(cmd); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if actualNameservers = resolvconf.GetNameservers([]byte(out)); string(actualNameservers[0]) != "12.34.56.78" || len(actualNameservers) != 1 {
- t.Fatalf("expected '12.34.56.78', but has: %v", actualNameservers)
+ c.Fatalf("expected '12.34.56.78', but has: %v", actualNameservers)
}
actualSearch = resolvconf.GetSearchDomains([]byte(out))
if len(actualSearch) != len(hostSearch) {
- t.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch))
+ c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch))
}
for i := range actualSearch {
if actualSearch[i] != hostSearch[i] {
- t.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i])
+ c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i])
}
}
- defer deleteAllContainers()
-
- logDone("run - dns options based on host resolv.conf")
}
-// Test the file watch notifier on docker host's /etc/resolv.conf
-// A go-routine is responsible for auto-updating containers which are
-// stopped and have an unmodified copy of resolv.conf, as well as
-// marking running containers as requiring an update on next restart
-func TestRunResolvconfUpdater(t *testing.T) {
- // Because overlay doesn't support inotify properly, we need to skip
- // this test if the docker daemon has Storage Driver == overlay
- testRequires(t, SameHostDaemon, NotOverlay)
+// Test to see if a non-root user can resolve a DNS name and reach out to it. Also
+// check if the container resolv.conf file has atleast 0644 perm.
+func (s *DockerSuite) TestRunNonRootUserResolvName(c *check.C) {
+ testRequires(c, SameHostDaemon)
+ testRequires(c, Network)
+
+ cmd := exec.Command(dockerBinary, "run", "--name=testperm", "--user=default", "busybox", "ping", "-c", "1", "www.docker.io")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatal(err, out)
+ }
+
+ cID, err := getIDByName("testperm")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ fmode := (os.FileMode)(0644)
+ finfo, err := os.Stat(containerStorageFile(cID, "resolv.conf"))
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ if (finfo.Mode() & fmode) != fmode {
+ c.Fatalf("Expected container resolv.conf mode to be atleast %s, instead got %s", fmode.String(), finfo.Mode().String())
+ }
+}
+
+// Test if container resolv.conf gets updated the next time it restarts
+// if host /etc/resolv.conf has changed. This only applies if the container
+// uses the host's /etc/resolv.conf and does not have any dns options provided.
+func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) {
+ testRequires(c, SameHostDaemon)
tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78")
tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1")
@@ -1706,125 +1471,128 @@ func TestRunResolvconfUpdater(t *testing.T) {
//take a copy of resolv.conf for restoring after test completes
resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// This test case is meant to test monitoring resolv.conf when it is
- // a regular file not a bind mount. So we unmount resolv.conf and replace
+ // a regular file not a bind mounc. So we unmount resolv.conf and replace
// it with a file containing the original settings.
cmd := exec.Command("umount", "/etc/resolv.conf")
if _, err = runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
//cleanup
defer func() {
- deleteAllContainers()
if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}()
- //1. test that a non-running container gets an updated resolv.conf
+ //1. test that a restarting container gets an updated resolv.conf
cmd = exec.Command(dockerBinary, "run", "--name='first'", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
containerID1, err := getIDByName("first")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// replace resolv.conf with our temporary copy
bytesResolvConf := []byte(tmpResolvConf)
if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
+ }
+
+ // start the container again to pickup changes
+ cmd = exec.Command(dockerBinary, "start", "first")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), err)
}
- time.Sleep(time.Second / 2)
// check for update in container
containerResolv, err := readContainerFile(containerID1, "resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !bytes.Equal(containerResolv, bytesResolvConf) {
- t.Fatalf("Stopped container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv))
+ c.Fatalf("Restarted container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv))
}
- //2. test that a non-running container does not receive resolv.conf updates
+ /* //make a change to resolv.conf (in this case replacing our tmp copy with orig copy)
+ if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+ c.Fatal(err)
+ } */
+ //2. test that a restarting container does not receive resolv.conf updates
// if it modified the container copy of the starting point resolv.conf
cmd = exec.Command(dockerBinary, "run", "--name='second'", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf")
if _, err = runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
containerID2, err := getIDByName("second")
if err != nil {
- t.Fatal(err)
- }
- containerResolvHashBefore, err := readContainerFile(containerID2, "resolv.conf.hash")
- if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
//make a change to resolv.conf (in this case replacing our tmp copy with orig copy)
if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- time.Sleep(time.Second / 2)
- containerResolvHashAfter, err := readContainerFile(containerID2, "resolv.conf.hash")
+ // start the container again
+ cmd = exec.Command(dockerBinary, "start", "second")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), err)
+ }
+
+ // check for update in container
+ containerResolv, err = readContainerFile(containerID2, "resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) {
- t.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter)
+ if bytes.Equal(containerResolv, resolvConfSystem) {
+ c.Fatalf("Restarting a container after container updated resolv.conf should not pick up host changes; expected %q, got %q", string(containerResolv), string(resolvConfSystem))
}
//3. test that a running container's resolv.conf is not modified while running
cmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runningContainerID := strings.TrimSpace(out)
- containerResolvHashBefore, err = readContainerFile(runningContainerID, "resolv.conf.hash")
- if err != nil {
- t.Fatal(err)
- }
-
// replace resolv.conf
if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- // make sure the updater has time to run to validate we really aren't
- // getting updated
- time.Sleep(time.Second / 2)
- containerResolvHashAfter, err = readContainerFile(runningContainerID, "resolv.conf.hash")
+ // check for update in container
+ containerResolv, err = readContainerFile(runningContainerID, "resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) {
- t.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter)
+ if bytes.Equal(containerResolv, bytesResolvConf) {
+ c.Fatalf("Running container should not have updated resolv.conf; expected %q, got %q", string(resolvConfSystem), string(containerResolv))
}
//4. test that a running container's resolv.conf is updated upon restart
// (the above container is still running..)
cmd = exec.Command(dockerBinary, "restart", runningContainerID)
if _, err = runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// check for update in container
containerResolv, err = readContainerFile(runningContainerID, "resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !bytes.Equal(containerResolv, bytesResolvConf) {
- t.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv))
+ c.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", string(bytesResolvConf), string(containerResolv))
}
//5. test that additions of a localhost resolver are cleaned from
@@ -1833,20 +1601,25 @@ func TestRunResolvconfUpdater(t *testing.T) {
// replace resolv.conf with a localhost-only nameserver copy
bytesResolvConf = []byte(tmpLocalhostResolvConf)
if err = ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
+ }
+
+ // start the container again to pickup changes
+ cmd = exec.Command(dockerBinary, "start", "first")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), err)
}
- time.Sleep(time.Second / 2)
// our first exited container ID should have been updated, but with default DNS
// after the cleanup of resolv.conf found only a localhost nameserver:
containerResolv, err = readContainerFile(containerID1, "resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := "\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
if !bytes.Equal(containerResolv, []byte(expected)) {
- t.Fatalf("Container does not have cleaned/replaced DNS in resolv.conf; expected %q, got %q", expected, string(containerResolv))
+ c.Fatalf("Container does not have cleaned/replaced DNS in resolv.conf; expected %q, got %q", expected, string(containerResolv))
}
//6. Test that replacing (as opposed to modifying) resolv.conf triggers an update
@@ -1854,194 +1627,163 @@ func TestRunResolvconfUpdater(t *testing.T) {
// Restore the original resolv.conf
if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Run the container so it picks up the old settings
cmd = exec.Command(dockerBinary, "run", "--name='third'", "busybox", "true")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
containerID3, err := getIDByName("third")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Create a modified resolv.conf.aside and override resolv.conf with it
bytesResolvConf = []byte(tmpResolvConf)
if err := ioutil.WriteFile("/etc/resolv.conf.aside", bytesResolvConf, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
err = os.Rename("/etc/resolv.conf.aside", "/etc/resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
+ }
+
+ // start the container again to pickup changes
+ cmd = exec.Command(dockerBinary, "start", "third")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), err)
}
- time.Sleep(time.Second / 2)
// check for update in container
containerResolv, err = readContainerFile(containerID3, "resolv.conf")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if !bytes.Equal(containerResolv, bytesResolvConf) {
- t.Fatalf("Stopped container does not have updated resolv.conf; expected\n%q\n got\n%q", tmpResolvConf, string(containerResolv))
+ c.Fatalf("Stopped container does not have updated resolv.conf; expected\n%q\n got\n%q", tmpResolvConf, string(containerResolv))
}
//cleanup, restore original resolv.conf happens in defer func()
- logDone("run - resolv.conf updater")
}
-func TestRunAddHost(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunAddHost(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--add-host=extra:86.75.30.9", "busybox", "grep", "extra", "/etc/hosts")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
actual := strings.Trim(out, "\r\n")
if actual != "86.75.30.9\textra" {
- t.Fatalf("expected '86.75.30.9\textra', but says: %q", actual)
+ c.Fatalf("expected '86.75.30.9\textra', but says: %q", actual)
}
-
- logDone("run - add-host option")
}
// Regression test for #6983
-func TestRunAttachStdErrOnlyTTYMode(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunAttachStdErrOnlyTTYMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stderr", "busybox", "true")
exitCode, err := runCommand(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
} else if exitCode != 0 {
- t.Fatalf("Container should have exited with error code 0")
+ c.Fatalf("Container should have exited with error code 0")
}
-
- logDone("run - Attach stderr only with -t")
}
// Regression test for #6983
-func TestRunAttachStdOutOnlyTTYMode(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunAttachStdOutOnlyTTYMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stdout", "busybox", "true")
exitCode, err := runCommand(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
} else if exitCode != 0 {
- t.Fatalf("Container should have exited with error code 0")
+ c.Fatalf("Container should have exited with error code 0")
}
-
- logDone("run - Attach stdout only with -t")
}
// Regression test for #6983
-func TestRunAttachStdOutAndErrTTYMode(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunAttachStdOutAndErrTTYMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stdout", "-a", "stderr", "busybox", "true")
exitCode, err := runCommand(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
} else if exitCode != 0 {
- t.Fatalf("Container should have exited with error code 0")
+ c.Fatalf("Container should have exited with error code 0")
}
-
- logDone("run - Attach stderr and stdout with -t")
}
// Test for #10388 - this will run the same test as TestRunAttachStdOutAndErrTTYMode
// but using --attach instead of -a to make sure we read the flag correctly
-func TestRunAttachWithDettach(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunAttachWithDetach(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "--attach", "stdout", "busybox", "true")
_, stderr, _, err := runCommandWithStdoutStderr(cmd)
if err == nil {
- t.Fatalf("Container should have exited with error code different than 0", err)
+ c.Fatal("Container should have exited with error code different than 0")
} else if !strings.Contains(stderr, "Conflicting options: -a and -d") {
- t.Fatalf("Should have been returned an error with conflicting options -a and -d")
+ c.Fatal("Should have been returned an error with conflicting options -a and -d")
}
-
- logDone("run - Attach stdout with -d")
}
-func TestRunState(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunState(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
id := strings.TrimSpace(out)
state, err := inspectField(id, "State.Running")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if state != "true" {
- t.Fatal("Container state is 'not running'")
+ c.Fatal("Container state is 'not running'")
}
pid1, err := inspectField(id, "State.Pid")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if pid1 == "0" {
- t.Fatal("Container state Pid 0")
+ c.Fatal("Container state Pid 0")
}
cmd = exec.Command(dockerBinary, "stop", id)
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
state, err = inspectField(id, "State.Running")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if state != "false" {
- t.Fatal("Container state is 'running'")
+ c.Fatal("Container state is 'running'")
}
pid2, err := inspectField(id, "State.Pid")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if pid2 == pid1 {
- t.Fatalf("Container state Pid %s, but expected %s", pid2, pid1)
+ c.Fatalf("Container state Pid %s, but expected %s", pid2, pid1)
}
cmd = exec.Command(dockerBinary, "start", id)
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
state, err = inspectField(id, "State.Running")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if state != "true" {
- t.Fatal("Container state is 'not running'")
+ c.Fatal("Container state is 'not running'")
}
pid3, err := inspectField(id, "State.Pid")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if pid3 == pid1 {
- t.Fatalf("Container state Pid %s, but expected %s", pid2, pid1)
+ c.Fatalf("Container state Pid %s, but expected %s", pid2, pid1)
}
- logDone("run - test container state.")
}
// Test for #1737
-func TestRunCopyVolumeUidGid(t *testing.T) {
+func (s *DockerSuite) TestRunCopyVolumeUidGid(c *check.C) {
name := "testrunvolumesuidgid"
- defer deleteImages(name)
- defer deleteAllContainers()
_, err := buildImage(name,
`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
@@ -2049,184 +1791,166 @@ func TestRunCopyVolumeUidGid(t *testing.T) {
RUN mkdir -p /hello && touch /hello/test && chown dockerio.dockerio /hello`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Test that the uid and gid is copied from the image to the volume
cmd := exec.Command(dockerBinary, "run", "--rm", "-v", "/hello", name, "sh", "-c", "ls -l / | grep hello | awk '{print $3\":\"$4}'")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
out = strings.TrimSpace(out)
if out != "dockerio:dockerio" {
- t.Fatalf("Wrong /hello ownership: %s, expected dockerio:dockerio", out)
+ c.Fatalf("Wrong /hello ownership: %s, expected dockerio:dockerio", out)
}
-
- logDone("run - copy uid/gid for volume")
}
// Test for #1582
-func TestRunCopyVolumeContent(t *testing.T) {
+func (s *DockerSuite) TestRunCopyVolumeContent(c *check.C) {
name := "testruncopyvolumecontent"
- defer deleteImages(name)
- defer deleteAllContainers()
_, err := buildImage(name,
`FROM busybox
RUN mkdir -p /hello/local && echo hello > /hello/local/world`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Test that the content is copied from the image to the volume
cmd := exec.Command(dockerBinary, "run", "--rm", "-v", "/hello", name, "find", "/hello")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !(strings.Contains(out, "/hello/local/world") && strings.Contains(out, "/hello/local")) {
- t.Fatal("Container failed to transfer content to volume")
+ c.Fatal("Container failed to transfer content to volume")
}
- logDone("run - copy volume content")
}
-func TestRunCleanupCmdOnEntrypoint(t *testing.T) {
+func (s *DockerSuite) TestRunCleanupCmdOnEntrypoint(c *check.C) {
name := "testrunmdcleanuponentrypoint"
- defer deleteImages(name)
- defer deleteAllContainers()
if _, err := buildImage(name,
`FROM busybox
ENTRYPOINT ["echo"]
CMD ["testingpoint"]`,
true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
runCmd := exec.Command(dockerBinary, "run", "--entrypoint", "whoami", name)
out, exit, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("Error: %v, out: %q", err, out)
+ c.Fatalf("Error: %v, out: %q", err, out)
}
if exit != 0 {
- t.Fatalf("expected exit code 0 received %d, out: %q", exit, out)
+ c.Fatalf("expected exit code 0 received %d, out: %q", exit, out)
}
out = strings.TrimSpace(out)
if out != "root" {
- t.Fatalf("Expected output root, got %q", out)
+ c.Fatalf("Expected output root, got %q", out)
}
- logDone("run - cleanup cmd on --entrypoint")
}
// TestRunWorkdirExistsAndIsFile checks that if 'docker run -w' with existing file can be detected
-func TestRunWorkdirExistsAndIsFile(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWorkdirExistsAndIsFile(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-w", "/bin/cat", "busybox")
out, exit, err := runCommandWithOutput(runCmd)
if !(err != nil && exit == 1 && strings.Contains(out, "Cannot mkdir: /bin/cat is not a directory")) {
- t.Fatalf("Docker must complains about making dir, but we got out: %s, exit: %d, err: %s", out, exit, err)
+ c.Fatalf("Docker must complains about making dir, but we got out: %s, exit: %d, err: %s", out, exit, err)
}
- logDone("run - error on existing file for workdir")
}
-func TestRunExitOnStdinClose(t *testing.T) {
+func (s *DockerSuite) TestRunExitOnStdinClose(c *check.C) {
name := "testrunexitonstdinclose"
- defer deleteAllContainers()
runCmd := exec.Command(dockerBinary, "run", "--name", name, "-i", "busybox", "/bin/cat")
stdin, err := runCmd.StdinPipe()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
stdout, err := runCmd.StdoutPipe()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := runCmd.Start(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if _, err := stdin.Write([]byte("hello\n")); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
r := bufio.NewReader(stdout)
line, err := r.ReadString('\n')
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
line = strings.TrimSpace(line)
if line != "hello" {
- t.Fatalf("Output should be 'hello', got '%q'", line)
+ c.Fatalf("Output should be 'hello', got '%q'", line)
}
if err := stdin.Close(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- finish := make(chan struct{})
+ finish := make(chan error)
go func() {
- if err := runCmd.Wait(); err != nil {
- t.Fatal(err)
- }
+ finish <- runCmd.Wait()
close(finish)
}()
select {
- case <-finish:
+ case err := <-finish:
+ c.Assert(err, check.IsNil)
case <-time.After(1 * time.Second):
- t.Fatal("docker run failed to exit on stdin close")
+ c.Fatal("docker run failed to exit on stdin close")
}
state, err := inspectField(name, "State.Running")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
+
if state != "false" {
- t.Fatal("Container must be stopped after stdin closing")
+ c.Fatal("Container must be stopped after stdin closing")
}
- logDone("run - exit on stdin closing")
}
// Test for #2267
-func TestRunWriteHostsFileAndNotCommit(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWriteHostsFileAndNotCommit(c *check.C) {
name := "writehosts"
cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/hosts && cat /etc/hosts")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "test2267") {
- t.Fatal("/etc/hosts should contain 'test2267'")
+ c.Fatal("/etc/hosts should contain 'test2267'")
}
cmd = exec.Command(dockerBinary, "diff", name)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) {
- t.Fatal("diff should be empty")
+ if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) {
+ c.Fatal("diff should be empty")
}
-
- logDone("run - write to /etc/hosts and not commited")
}
-func eqToBaseDiff(out string, t *testing.T) bool {
+func eqToBaseDiff(out string, c *check.C) bool {
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello")
out1, _, err := runCommandWithOutput(cmd)
- cID := stripTrailingCharacters(out1)
+ cID := strings.TrimSpace(out1)
cmd = exec.Command(dockerBinary, "diff", cID)
- base_diff, _, err := runCommandWithOutput(cmd)
+ baseDiff, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, base_diff)
+ c.Fatal(err, baseDiff)
}
- base_arr := strings.Split(base_diff, "\n")
- sort.Strings(base_arr)
- out_arr := strings.Split(out, "\n")
- sort.Strings(out_arr)
- return sliceEq(base_arr, out_arr)
+ baseArr := strings.Split(baseDiff, "\n")
+ sort.Strings(baseArr)
+ outArr := strings.Split(out, "\n")
+ sort.Strings(outArr)
+ return sliceEq(baseArr, outArr)
}
func sliceEq(a, b []string) bool {
@@ -2244,859 +1968,769 @@ func sliceEq(a, b []string) bool {
}
// Test for #2267
-func TestRunWriteHostnameFileAndNotCommit(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWriteHostnameFileAndNotCommit(c *check.C) {
name := "writehostname"
cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/hostname && cat /etc/hostname")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "test2267") {
- t.Fatal("/etc/hostname should contain 'test2267'")
+ c.Fatal("/etc/hostname should contain 'test2267'")
}
cmd = exec.Command(dockerBinary, "diff", name)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) {
- t.Fatal("diff should be empty")
+ if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) {
+ c.Fatal("diff should be empty")
}
-
- logDone("run - write to /etc/hostname and not commited")
}
// Test for #2267
-func TestRunWriteResolvFileAndNotCommit(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWriteResolvFileAndNotCommit(c *check.C) {
name := "writeresolv"
cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/resolv.conf && cat /etc/resolv.conf")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "test2267") {
- t.Fatal("/etc/resolv.conf should contain 'test2267'")
+ c.Fatal("/etc/resolv.conf should contain 'test2267'")
}
cmd = exec.Command(dockerBinary, "diff", name)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) {
- t.Fatal("diff should be empty")
+ if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) {
+ c.Fatal("diff should be empty")
}
-
- logDone("run - write to /etc/resolv.conf and not commited")
}
-func TestRunWithBadDevice(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWithBadDevice(c *check.C) {
name := "baddevice"
cmd := exec.Command(dockerBinary, "run", "--name", name, "--device", "/etc", "busybox", "true")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatal("Run should fail with bad device")
+ c.Fatal("Run should fail with bad device")
}
- expected := `\"/etc\": not a device node`
+ expected := `"/etc": not a device node`
if !strings.Contains(out, expected) {
- t.Fatalf("Output should contain %q, actual out: %q", expected, out)
+ c.Fatalf("Output should contain %q, actual out: %q", expected, out)
}
- logDone("run - error with bad device")
}
-func TestRunEntrypoint(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunEntrypoint(c *check.C) {
name := "entrypoint"
cmd := exec.Command(dockerBinary, "run", "--name", name, "--entrypoint", "/bin/echo", "busybox", "-n", "foobar")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
expected := "foobar"
if out != expected {
- t.Fatalf("Output should be %q, actual out: %q", expected, out)
+ c.Fatalf("Output should be %q, actual out: %q", expected, out)
}
- logDone("run - entrypoint")
}
-func TestRunBindMounts(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunBindMounts(c *check.C) {
+ testRequires(c, SameHostDaemon)
tmpDir, err := ioutil.TempDir("", "docker-test-container")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpDir)
- writeFile(path.Join(tmpDir, "touch-me"), "", t)
+ writeFile(path.Join(tmpDir, "touch-me"), "", c)
// Test reading from a read-only bind mount
cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp:ro", tmpDir), "busybox", "ls", "/tmp")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if !strings.Contains(out, "touch-me") {
- t.Fatal("Container failed to read from bind mount")
+ c.Fatal("Container failed to read from bind mount")
}
// test writing to bind mount
cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp:rw", tmpDir), "busybox", "touch", "/tmp/holla")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist
+ readFile(path.Join(tmpDir, "holla"), c) // Will fail if the file doesn't exist
// test mounting to an illegal destination directory
cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:.", tmpDir), "busybox", "ls", ".")
_, err = runCommand(cmd)
if err == nil {
- t.Fatal("Container bind mounted illegal directory")
+ c.Fatal("Container bind mounted illegal directory")
}
// test mount a file
cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s/holla:/tmp/holla:rw", tmpDir), "busybox", "sh", "-c", "echo -n 'yotta' > /tmp/holla")
_, err = runCommand(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
- content := readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist
+ content := readFile(path.Join(tmpDir, "holla"), c) // Will fail if the file doesn't exist
expected := "yotta"
if content != expected {
- t.Fatalf("Output should be %q, actual out: %q", expected, content)
+ c.Fatalf("Output should be %q, actual out: %q", expected, content)
}
-
- logDone("run - bind mounts")
}
// Ensure that CIDFile gets deleted if it's empty
// Perform this test by making `docker run` fail
-func TestRunCidFileCleanupIfEmpty(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCidFileCleanupIfEmpty(c *check.C) {
tmpDir, err := ioutil.TempDir("", "TestRunCidFile")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpDir)
tmpCidFile := path.Join(tmpDir, "cid")
cmd := exec.Command(dockerBinary, "run", "--cidfile", tmpCidFile, "emptyfs")
out, _, err := runCommandWithOutput(cmd)
if err == nil {
- t.Fatalf("Run without command must fail. out=%s", out)
+ c.Fatalf("Run without command must fail. out=%s", out)
} else if !strings.Contains(out, "No command specified") {
- t.Fatalf("Run without command failed with wrong output. out=%s\nerr=%v", out, err)
+ c.Fatalf("Run without command failed with wrong output. out=%s\nerr=%v", out, err)
}
if _, err := os.Stat(tmpCidFile); err == nil {
- t.Fatalf("empty CIDFile %q should've been deleted", tmpCidFile)
+ c.Fatalf("empty CIDFile %q should've been deleted", tmpCidFile)
}
- logDone("run - cleanup empty cidfile on error")
}
// #2098 - Docker cidFiles only contain short version of the containerId
-//sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test"
+//sudo docker run --cidfile /tmp/docker_tesc.cid ubuntu echo "test"
// TestRunCidFile tests that run --cidfile returns the longid
-func TestRunCidFileCheckIDLength(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCidFileCheckIDLength(c *check.C) {
tmpDir, err := ioutil.TempDir("", "TestRunCidFile")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tmpCidFile := path.Join(tmpDir, "cid")
defer os.RemoveAll(tmpDir)
cmd := exec.Command(dockerBinary, "run", "-d", "--cidfile", tmpCidFile, "busybox", "true")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id := strings.TrimSpace(out)
buffer, err := ioutil.ReadFile(tmpCidFile)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cid := string(buffer)
if len(cid) != 64 {
- t.Fatalf("--cidfile should be a long id, not %q", id)
+ c.Fatalf("--cidfile should be a long id, not %q", id)
}
if cid != id {
- t.Fatalf("cid must be equal to %s, got %s", id, cid)
+ c.Fatalf("cid must be equal to %s, got %s", id, cid)
}
-
- logDone("run - cidfile contains long id")
}
-func TestRunNetworkNotInitializedNoneMode(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunNetworkNotInitializedNoneMode(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "--net=none", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id := strings.TrimSpace(out)
res, err := inspectField(id, "NetworkSettings.IPAddress")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if res != "" {
- t.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res)
+ c.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res)
}
-
- logDone("run - network must not be initialized in 'none' mode")
}
-func TestRunSetMacAddress(t *testing.T) {
+func (s *DockerSuite) TestRunSetMacAddress(c *check.C) {
mac := "12:34:56:78:9a:bc"
- defer deleteAllContainers()
cmd := exec.Command(dockerBinary, "run", "-i", "--rm", fmt.Sprintf("--mac-address=%s", mac), "busybox", "/bin/sh", "-c", "ip link show eth0 | tail -1 | awk '{print $2}'")
out, ec, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("exec failed:\nexit code=%v\noutput=%s", ec, out)
+ c.Fatalf("exec failed:\nexit code=%v\noutput=%s", ec, out)
}
actualMac := strings.TrimSpace(out)
if actualMac != mac {
- t.Fatalf("Set MAC address with --mac-address failed. The container has an incorrect MAC address: %q, expected: %q", actualMac, mac)
+ c.Fatalf("Set MAC address with --mac-address failed. The container has an incorrect MAC address: %q, expected: %q", actualMac, mac)
}
-
- logDone("run - setting MAC address with --mac-address")
}
-func TestRunInspectMacAddress(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunInspectMacAddress(c *check.C) {
mac := "12:34:56:78:9a:bc"
cmd := exec.Command(dockerBinary, "run", "-d", "--mac-address="+mac, "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id := strings.TrimSpace(out)
inspectedMac, err := inspectField(id, "NetworkSettings.MacAddress")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if inspectedMac != mac {
- t.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac)
+ c.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac)
}
-
- logDone("run - inspecting MAC address")
}
// test docker run use a invalid mac address
-func TestRunWithInvalidMacAddress(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWithInvalidMacAddress(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--mac-address", "92:d0:c6:0a:29", "busybox")
out, _, err := runCommandWithOutput(runCmd)
//use a invalid mac address should with a error out
if err == nil || !strings.Contains(out, "is not a valid mac address") {
- t.Fatalf("run with an invalid --mac-address should with error out")
+ c.Fatalf("run with an invalid --mac-address should with error out")
}
-
- logDone("run - can't use an invalid mac address")
}
-func TestRunDeallocatePortOnMissingIptablesRule(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunDeallocatePortOnMissingIptablesRule(c *check.C) {
+ testRequires(c, SameHostDaemon)
cmd := exec.Command(dockerBinary, "run", "-d", "-p", "23:23", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id := strings.TrimSpace(out)
ip, err := inspectField(id, "NetworkSettings.IPAddress")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
iptCmd := exec.Command("iptables", "-D", "DOCKER", "-d", fmt.Sprintf("%s/32", ip),
"!", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-m", "tcp", "--dport", "23", "-j", "ACCEPT")
out, _, err = runCommandWithOutput(iptCmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
if err := deleteContainer(id); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", "-d", "-p", "23:23", "busybox", "top")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
-
- logDone("run - port should be deallocated even on iptables error")
}
-func TestRunPortInUse(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunPortInUse(c *check.C) {
+ testRequires(c, SameHostDaemon)
port := "1234"
- l, err := net.Listen("tcp", ":"+port)
- if err != nil {
- t.Fatal(err)
- }
- defer l.Close()
cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
- if err == nil {
- t.Fatalf("Binding on used port must fail")
- }
- if !strings.Contains(out, "address already in use") {
- t.Fatalf("Out must be about \"address already in use\", got %s", out)
+ if err != nil {
+ c.Fatalf("Fail to run listening container")
}
- logDone("run - error out if port already in use")
+ cmd = exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
+ out, _, err = runCommandWithOutput(cmd)
+ if err == nil {
+ c.Fatalf("Binding on used port must fail")
+ }
+ if !strings.Contains(out, "port is already allocated") {
+ c.Fatalf("Out must be about \"port is already allocated\", got %s", out)
+ }
}
-// https://github.com/docker/docker/issues/8428
-func TestRunPortProxy(t *testing.T) {
- testRequires(t, SameHostDaemon)
-
- defer deleteAllContainers()
-
- port := "12345"
- cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
-
+// https://github.com/docker/docker/issues/12148
+func (s *DockerSuite) TestRunAllocatePortInReservedRange(c *check.C) {
+ // allocate a dynamic port to get the most recent
+ cmd := exec.Command(dockerBinary, "run", "-d", "-P", "-p", "80", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err)
+ c.Fatalf("Failed to run, output: %s, error: %s", out, err)
}
+ id := strings.TrimSpace(out)
- // connect for 10 times here. This will trigger 10 EPIPES in the child
- // process and kill it when it writes to a closed stdout/stderr
- for i := 0; i < 10; i++ {
- net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port))
- }
-
- listPs := exec.Command("sh", "-c", "ps ax | grep docker")
- out, _, err = runCommandWithOutput(listPs)
+ cmd = exec.Command(dockerBinary, "port", id, "80")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Errorf("list docker process failed with output %s, error %s", out, err)
+ c.Fatalf("Failed to get port, output: %s, error: %s", out, err)
}
- if strings.Contains(out, "docker ") {
- t.Errorf("Unexpected defunct docker process")
- }
- if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") {
- t.Errorf("Failed to find docker-proxy process, got %s", out)
+ strPort := strings.Split(strings.TrimSpace(out), ":")[1]
+ port, err := strconv.ParseInt(strPort, 10, 64)
+ if err != nil {
+ c.Fatalf("invalid port, got: %s, error: %s", strPort, err)
}
- logDone("run - proxy should work with unavailable port")
+ // allocate a static port and a dynamic port together, with static port
+ // takes the next recent port in dynamic port range.
+ cmd = exec.Command(dockerBinary, "run", "-d", "-P",
+ "-p", "80",
+ "-p", fmt.Sprintf("%d:8080", port+1),
+ "busybox", "top")
+ out, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("Failed to run, output: %s, error: %s", out, err)
+ }
}
// Regression test for #7792
-func TestRunMountOrdering(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunMountOrdering(c *check.C) {
+ testRequires(c, SameHostDaemon)
tmpDir, err := ioutil.TempDir("", "docker_nested_mount_test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpDir)
tmpDir2, err := ioutil.TempDir("", "docker_nested_mount_test2")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpDir2)
- // Create a temporary tmpfs mount.
+ // Create a temporary tmpfs mounc.
fooDir := filepath.Join(tmpDir, "foo")
if err := os.MkdirAll(filepath.Join(tmpDir, "foo"), 0755); err != nil {
- t.Fatalf("failed to mkdir at %s - %s", fooDir, err)
+ c.Fatalf("failed to mkdir at %s - %s", fooDir, err)
}
if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", fooDir), []byte{}, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", tmpDir), []byte{}, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", tmpDir2), []byte{}, 0644); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp", tmpDir), "-v", fmt.Sprintf("%s:/tmp/foo", fooDir), "-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2), "-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir), "busybox:latest", "sh", "-c", "ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me")
+ cmd := exec.Command(dockerBinary, "run",
+ "-v", fmt.Sprintf("%s:/tmp", tmpDir),
+ "-v", fmt.Sprintf("%s:/tmp/foo", fooDir),
+ "-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2),
+ "-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir),
+ "busybox:latest", "sh", "-c",
+ "ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
-
- logDone("run - volumes are mounted in the correct order")
}
// Regression test for https://github.com/docker/docker/issues/8259
-func TestRunReuseBindVolumeThatIsSymlink(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunReuseBindVolumeThatIsSymlink(c *check.C) {
+ testRequires(c, SameHostDaemon)
tmpDir, err := ioutil.TempDir(os.TempDir(), "testlink")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpDir)
linkPath := os.TempDir() + "/testlink2"
if err := os.Symlink(tmpDir, linkPath); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(linkPath)
// Create first container
cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp/test", linkPath), "busybox", "ls", "-lh", "/tmp/test")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Create second container with same symlinked path
// This will fail if the referenced issue is hit with a "Volume exists" error
cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp/test", linkPath), "busybox", "ls", "-lh", "/tmp/test")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
-
- logDone("run - can remount old bindmount volume")
}
//GH#10604: Test an "/etc" volume doesn't overlay special bind mounts in container
-func TestRunCreateVolumeEtc(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunCreateVolumeEtc(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "-v", "/etc", "busybox", "cat", "/etc/resolv.conf")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if !strings.Contains(out, "nameserver 127.0.0.1") {
- t.Fatal("/etc volume mount hides /etc/resolv.conf")
+ c.Fatal("/etc volume mount hides /etc/resolv.conf")
}
cmd = exec.Command(dockerBinary, "run", "-h=test123", "-v", "/etc", "busybox", "cat", "/etc/hostname")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
if !strings.Contains(out, "test123") {
- t.Fatal("/etc volume mount hides /etc/hostname")
+ c.Fatal("/etc volume mount hides /etc/hostname")
}
cmd = exec.Command(dockerBinary, "run", "--add-host=test:192.168.0.1", "-v", "/etc", "busybox", "cat", "/etc/hosts")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
out = strings.Replace(out, "\n", " ", -1)
if !strings.Contains(out, "192.168.0.1\ttest") || !strings.Contains(out, "127.0.0.1\tlocalhost") {
- t.Fatal("/etc volume mount hides /etc/hosts")
+ c.Fatal("/etc volume mount hides /etc/hosts")
}
-
- logDone("run - verify /etc volume doesn't hide special bind mounts")
}
-func TestVolumesNoCopyData(t *testing.T) {
- defer deleteImages("dataimage")
- defer deleteAllContainers()
+func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) {
if _, err := buildImage("dataimage",
`FROM busybox
RUN mkdir -p /foo
RUN touch /foo/bar`,
true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "--name", "test", "-v", "/foo", "busybox")
if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", "--volumes-from", "test", "dataimage", "ls", "-lh", "/foo/bar")
if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "No such file or directory") {
- t.Fatalf("Data was copied on volumes-from but shouldn't be:\n%q", out)
+ c.Fatalf("Data was copied on volumes-from but shouldn't be:\n%q", out)
}
tmpDir := randomUnixTmpDirPath("docker_test_bind_mount_copy_data")
cmd = exec.Command(dockerBinary, "run", "-v", tmpDir+":/foo", "dataimage", "ls", "-lh", "/foo/bar")
if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "No such file or directory") {
- t.Fatalf("Data was copied on bind-mount but shouldn't be:\n%q", out)
+ c.Fatalf("Data was copied on bind-mount but shouldn't be:\n%q", out)
}
-
- logDone("run - volumes do not copy data for volumes-from and bindmounts")
}
-func TestRunVolumesNotRecreatedOnStart(t *testing.T) {
- testRequires(t, SameHostDaemon)
-
- // Clear out any remnants from other tests
- deleteAllContainers()
- info, err := ioutil.ReadDir(volumesConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if len(info) > 0 {
- for _, f := range info {
- if err := os.RemoveAll(volumesConfigPath + "/" + f.Name()); err != nil {
- t.Fatal(err)
- }
- }
- }
-
- defer deleteAllContainers()
- cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "--name", "lone_starr", "busybox")
- if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
- }
-
- cmd = exec.Command(dockerBinary, "start", "lone_starr")
- if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
- }
-
- info, err = ioutil.ReadDir(volumesConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if len(info) != 1 {
- t.Fatalf("Expected only 1 volume have %v", len(info))
- }
-
- logDone("run - volumes not recreated on start")
-}
-
-func TestRunNoOutputFromPullInStdout(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunNoOutputFromPullInStdout(c *check.C) {
// just run with unknown image
cmd := exec.Command(dockerBinary, "run", "asdfsg")
stdout := bytes.NewBuffer(nil)
cmd.Stdout = stdout
if err := cmd.Run(); err == nil {
- t.Fatal("Run with unknown image should fail")
+ c.Fatal("Run with unknown image should fail")
}
if stdout.Len() != 0 {
- t.Fatalf("Stdout contains output from pull: %s", stdout)
+ c.Fatalf("Stdout contains output from pull: %s", stdout)
}
- logDone("run - no output from pull in stdout")
}
-func TestRunVolumesCleanPaths(t *testing.T) {
+func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
if _, err := buildImage("run_volumes_clean_paths",
`FROM busybox
VOLUME /foo/`,
true); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- defer deleteImages("run_volumes_clean_paths")
- defer deleteAllContainers()
cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "-v", "/bar/", "--name", "dark_helmet", "run_volumes_clean_paths")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
out, err := inspectFieldMap("dark_helmet", "Volumes", "/foo/")
- if err != nil {
- t.Fatal(err)
- }
- if out != "" {
- t.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out)
+ c.Assert(err, check.IsNil)
+ if out != "" {
+ c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out)
}
out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo")
- if err != nil {
- t.Fatal(err)
- }
- if !strings.Contains(out, volumesStoragePath) {
- t.Fatalf("Volume was not defined for /foo\n%q", out)
+ c.Assert(err, check.IsNil)
+ if !strings.Contains(out, volumesConfigPath) {
+ c.Fatalf("Volume was not defined for /foo\n%q", out)
}
out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar/")
- if err != nil {
- t.Fatal(err)
- }
- if out != "" {
- t.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out)
+ c.Assert(err, check.IsNil)
+ if out != "" {
+ c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out)
}
out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar")
- if err != nil {
- t.Fatal(err)
+ c.Assert(err, check.IsNil)
+ if !strings.Contains(out, volumesConfigPath) {
+ c.Fatalf("Volume was not defined for /bar\n%q", out)
}
- if !strings.Contains(out, volumesStoragePath) {
- t.Fatalf("Volume was not defined for /bar\n%q", out)
- }
-
- logDone("run - volume paths are cleaned")
}
// Regression test for #3631
-func TestRunSlowStdoutConsumer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunSlowStdoutConsumer(c *check.C) {
+ cont := exec.Command(dockerBinary, "run", "--rm", "busybox", "/bin/sh", "-c", "dd if=/dev/zero of=/dev/stdout bs=1024 count=2000 | catv")
- c := exec.Command(dockerBinary, "run", "--rm", "busybox", "/bin/sh", "-c", "dd if=/dev/zero of=/dev/stdout bs=1024 count=2000 | catv")
-
- stdout, err := c.StdoutPipe()
+ stdout, err := cont.StdoutPipe()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if err := c.Start(); err != nil {
- t.Fatal(err)
+ if err := cont.Start(); err != nil {
+ c.Fatal(err)
}
n, err := consumeWithSpeed(stdout, 10000, 5*time.Millisecond, nil)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
expected := 2 * 1024 * 2000
if n != expected {
- t.Fatalf("Expected %d, got %d", expected, n)
+ c.Fatalf("Expected %d, got %d", expected, n)
}
-
- logDone("run - slow consumer")
}
-func TestRunAllowPortRangeThroughExpose(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunAllowPortRangeThroughExpose(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-P", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id := strings.TrimSpace(out)
portstr, err := inspectFieldJSON(id, "NetworkSettings.Ports")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
var ports nat.PortMap
- err = unmarshalJSON([]byte(portstr), &ports)
+ if err = unmarshalJSON([]byte(portstr), &ports); err != nil {
+ c.Fatal(err)
+ }
for port, binding := range ports {
portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0])
if portnum < 3000 || portnum > 3003 {
- t.Fatalf("Port is out of range ", portnum, binding, out)
+ c.Fatalf("Port %d is out of range ", portnum)
}
if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 {
- t.Fatal("Port is not mapped for the port "+port, out)
+ c.Fatalf("Port is not mapped for the port %d", port)
}
}
- if err := deleteContainer(id); err != nil {
- t.Fatal(err)
- }
- logDone("run - allow port range through --expose flag")
}
// test docker run expose a invalid port
-func TestRunExposePort(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunExposePort(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--expose", "80000", "busybox")
out, _, err := runCommandWithOutput(runCmd)
//expose a invalid port should with a error out
if err == nil || !strings.Contains(out, "Invalid range format for --expose") {
- t.Fatalf("run --expose a invalid port should with error out")
+ c.Fatalf("run --expose a invalid port should with error out")
}
-
- logDone("run - can't expose a invalid port")
}
-func TestRunUnknownCommand(t *testing.T) {
- testRequires(t, NativeExecDriver)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunUnknownCommand(c *check.C) {
+ testRequires(c, NativeExecDriver)
runCmd := exec.Command(dockerBinary, "create", "busybox", "/bin/nada")
cID, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("Failed to create container: %v, output: %q", err, cID)
+ c.Fatalf("Failed to create container: %v, output: %q", err, cID)
}
cID = strings.TrimSpace(cID)
runCmd = exec.Command(dockerBinary, "start", cID)
_, _, _, _ = runCommandWithStdoutStderr(runCmd)
- runCmd = exec.Command(dockerBinary, "inspect", "--format={{.State.ExitCode}}", cID)
- rc, _, _, err2 := runCommandWithStdoutStderr(runCmd)
- rc = strings.TrimSpace(rc)
-
- if err2 != nil {
- t.Fatalf("Error getting status of container: %v", err2)
- }
-
+ rc, err := inspectField(cID, "State.ExitCode")
+ c.Assert(err, check.IsNil)
if rc == "0" {
- t.Fatalf("ExitCode(%v) cannot be 0", rc)
+ c.Fatalf("ExitCode(%v) cannot be 0", rc)
}
-
- logDone("run - Unknown Command")
}
-func TestRunModeIpcHost(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunModeIpcHost(c *check.C) {
+ testRequires(c, SameHostDaemon)
hostIpc, err := os.Readlink("/proc/1/ns/ipc")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "--ipc=host", "busybox", "readlink", "/proc/self/ns/ipc")
out2, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if hostIpc != out2 {
- t.Fatalf("IPC different with --ipc=host %s != %s\n", hostIpc, out2)
+ c.Fatalf("IPC different with --ipc=host %s != %s\n", hostIpc, out2)
}
cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/ipc")
out2, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if hostIpc == out2 {
- t.Fatalf("IPC should be different without --ipc=host %s == %s\n", hostIpc, out2)
+ c.Fatalf("IPC should be different without --ipc=host %s == %s\n", hostIpc, out2)
}
-
- logDone("run - ipc host mode")
}
-func TestRunModeIpcContainer(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunModeIpcContainer(c *check.C) {
+ testRequires(c, SameHostDaemon)
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
id := strings.TrimSpace(out)
state, err := inspectField(id, "State.Running")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if state != "true" {
- t.Fatal("Container state is 'not running'")
+ c.Fatal("Container state is 'not running'")
}
pid1, err := inspectField(id, "State.Pid")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
parentContainerIpc, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/ipc", pid1))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--ipc=container:%s", id), "busybox", "readlink", "/proc/self/ns/ipc")
out2, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if parentContainerIpc != out2 {
- t.Fatalf("IPC different with --ipc=container:%s %s != %s\n", id, parentContainerIpc, out2)
+ c.Fatalf("IPC different with --ipc=container:%s %s != %s\n", id, parentContainerIpc, out2)
}
-
- logDone("run - ipc container mode")
}
-func TestContainerNetworkMode(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+func (s *DockerSuite) TestRunModeIpcContainerNotExists(c *check.C) {
+ cmd := exec.Command(dockerBinary, "run", "-d", "--ipc", "container:abcd1234", "busybox", "top")
+ out, _, err := runCommandWithOutput(cmd)
+ if !strings.Contains(out, "abcd1234") || err == nil {
+ c.Fatalf("run IPC from a non exists container should with correct error out")
+ }
+}
+
+func (s *DockerSuite) TestRunModeIpcContainerNotRunning(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ cmd := exec.Command(dockerBinary, "create", "busybox")
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ id := strings.TrimSpace(out)
+
+ cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--ipc=container:%s", id), "busybox")
+ out, _, err = runCommandWithOutput(cmd)
+ if err == nil {
+ c.Fatalf("Run container with ipc mode container should fail with non running container: %s\n%s", out, err)
+ }
+}
+
+func (s *DockerSuite) TestContainerNetworkMode(c *check.C) {
+ testRequires(c, SameHostDaemon)
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
id := strings.TrimSpace(out)
if err := waitRun(id); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
pid1, err := inspectField(id, "State.Pid")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
parentContainerNet, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/net", pid1))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--net=container:%s", id), "busybox", "readlink", "/proc/self/ns/net")
out2, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if parentContainerNet != out2 {
- t.Fatalf("NET different with --net=container:%s %s != %s\n", id, parentContainerNet, out2)
+ c.Fatalf("NET different with --net=container:%s %s != %s\n", id, parentContainerNet, out2)
}
-
- logDone("run - container shared network namespace")
}
-func TestRunModePidHost(t *testing.T) {
- testRequires(t, NativeExecDriver, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestContainerNetworkModeToSelf(c *check.C) {
+ cmd := exec.Command(dockerBinary, "run", "--name=me", "--net=container:me", "busybox", "true")
+ out, _, err := runCommandWithOutput(cmd)
+ if err == nil || !strings.Contains(out, "cannot join own network") {
+ c.Fatalf("using container net mode to self should result in an error")
+ }
+}
+
+func (s *DockerSuite) TestRunModePidHost(c *check.C) {
+ testRequires(c, NativeExecDriver, SameHostDaemon)
hostPid, err := os.Readlink("/proc/1/ns/pid")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "--pid=host", "busybox", "readlink", "/proc/self/ns/pid")
out2, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if hostPid != out2 {
- t.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out2)
+ c.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out2)
}
cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/pid")
out2, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if hostPid == out2 {
- t.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out2)
+ c.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out2)
}
-
- logDone("run - pid host mode")
}
-func TestRunTLSverify(t *testing.T) {
+func (s *DockerSuite) TestRunModeUTSHost(c *check.C) {
+ testRequires(c, NativeExecDriver, SameHostDaemon)
+
+ hostUTS, err := os.Readlink("/proc/1/ns/uts")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ cmd := exec.Command(dockerBinary, "run", "--uts=host", "busybox", "readlink", "/proc/self/ns/uts")
+ out2, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out2)
+ }
+
+ out2 = strings.Trim(out2, "\n")
+ if hostUTS != out2 {
+ c.Fatalf("UTS different with --uts=host %s != %s\n", hostUTS, out2)
+ }
+
+ cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/uts")
+ out2, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out2)
+ }
+
+ out2 = strings.Trim(out2, "\n")
+ if hostUTS == out2 {
+ c.Fatalf("UTS should be different without --uts=host %s == %s\n", hostUTS, out2)
+ }
+}
+
+func (s *DockerSuite) TestRunTLSverify(c *check.C) {
cmd := exec.Command(dockerBinary, "ps")
out, ec, err := runCommandWithOutput(cmd)
if err != nil || ec != 0 {
- t.Fatalf("Should have worked: %v:\n%v", err, out)
+ c.Fatalf("Should have worked: %v:\n%v", err, out)
}
// Regardless of whether we specify true or false we need to
@@ -3105,318 +2739,354 @@ func TestRunTLSverify(t *testing.T) {
cmd = exec.Command(dockerBinary, "--tlsverify=false", "ps")
out, ec, err = runCommandWithOutput(cmd)
if err == nil || ec == 0 || !strings.Contains(out, "trying to connect") {
- t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err)
+ c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", ec, out, err)
}
cmd = exec.Command(dockerBinary, "--tlsverify=true", "ps")
out, ec, err = runCommandWithOutput(cmd)
if err == nil || ec == 0 || !strings.Contains(out, "cert") {
- t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err)
+ c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", ec, out, err)
}
-
- logDone("run - verify tls is set for --tlsverify")
}
-func TestRunPortFromDockerRangeInUse(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunPortFromDockerRangeInUse(c *check.C) {
// first find allocator current position
cmd := exec.Command(dockerBinary, "run", "-d", "-p", ":80", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id := strings.TrimSpace(out)
cmd = exec.Command(dockerBinary, "port", id)
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
out = strings.TrimSpace(out)
if out == "" {
- t.Fatal("docker port command output is empty")
+ c.Fatal("docker port command output is empty")
}
out = strings.Split(out, ":")[1]
lastPort, err := strconv.Atoi(out)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
port := lastPort + 1
l, err := net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer l.Close()
cmd = exec.Command(dockerBinary, "run", "-d", "-p", ":80", "busybox", "top")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
id = strings.TrimSpace(out)
cmd = exec.Command(dockerBinary, "port", id)
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
-
- logDone("run - find another port if port from autorange already bound")
}
-func TestRunTtyWithPipe(t *testing.T) {
- defer deleteAllContainers()
-
- done := make(chan struct{})
+func (s *DockerSuite) TestRunTtyWithPipe(c *check.C) {
+ errChan := make(chan error)
go func() {
- defer close(done)
+ defer close(errChan)
cmd := exec.Command(dockerBinary, "run", "-ti", "busybox", "true")
if _, err := cmd.StdinPipe(); err != nil {
- t.Fatal(err)
+ errChan <- err
+ return
}
expected := "cannot enable tty mode"
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal("run should have failed")
+ errChan <- fmt.Errorf("run should have failed")
+ return
} else if !strings.Contains(out, expected) {
- t.Fatalf("run failed with error %q: expected %q", out, expected)
+ errChan <- fmt.Errorf("run failed with error %q: expected %q", out, expected)
+ return
}
}()
select {
- case <-done:
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
case <-time.After(3 * time.Second):
- t.Fatal("container is running but should have failed")
+ c.Fatal("container is running but should have failed")
}
-
- logDone("run - forbid piped stdin with tty")
}
-func TestRunNonLocalMacAddress(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunNonLocalMacAddress(c *check.C) {
addr := "00:16:3E:08:00:50"
cmd := exec.Command(dockerBinary, "run", "--mac-address", addr, "busybox", "ifconfig")
if out, _, err := runCommandWithOutput(cmd); err != nil || !strings.Contains(out, addr) {
- t.Fatalf("Output should have contained %q: %s, %v", addr, out, err)
+ c.Fatalf("Output should have contained %q: %s, %v", addr, out, err)
}
-
- logDone("run - use non-local mac-address")
}
-func TestRunNetHost(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunNetHost(c *check.C) {
+ testRequires(c, SameHostDaemon)
hostNet, err := os.Readlink("/proc/1/ns/net")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "readlink", "/proc/self/ns/net")
out2, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if hostNet != out2 {
- t.Fatalf("Net namespace different with --net=host %s != %s\n", hostNet, out2)
+ c.Fatalf("Net namespace different with --net=host %s != %s\n", hostNet, out2)
}
cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/net")
out2, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out2)
+ c.Fatal(err, out2)
}
out2 = strings.Trim(out2, "\n")
if hostNet == out2 {
- t.Fatalf("Net namespace should be different without --net=host %s == %s\n", hostNet, out2)
+ c.Fatalf("Net namespace should be different without --net=host %s == %s\n", hostNet, out2)
}
-
- logDone("run - net host mode")
}
-func TestRunNetContainerWhichHost(t *testing.T) {
- testRequires(t, SameHostDaemon)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunNetContainerWhichHost(c *check.C) {
+ testRequires(c, SameHostDaemon)
hostNet, err := os.Readlink("/proc/1/ns/net")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "-d", "--net=host", "--name=test", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
cmd = exec.Command(dockerBinary, "run", "--net=container:test", "busybox", "readlink", "/proc/self/ns/net")
out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
out = strings.Trim(out, "\n")
if hostNet != out {
- t.Fatalf("Container should have host network namespace")
+ c.Fatalf("Container should have host network namespace")
}
-
- logDone("run - net container mode, where container in host mode")
}
-func TestRunAllowPortRangeThroughPublish(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunAllowPortRangeThroughPublish(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-p", "3000-3003", "busybox", "top")
out, _, err := runCommandWithOutput(cmd)
id := strings.TrimSpace(out)
portstr, err := inspectFieldJSON(id, "NetworkSettings.Ports")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
var ports nat.PortMap
err = unmarshalJSON([]byte(portstr), &ports)
for port, binding := range ports {
portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0])
if portnum < 3000 || portnum > 3003 {
- t.Fatalf("Port is out of range ", portnum, binding, out)
+ c.Fatalf("Port %d is out of range ", portnum)
}
if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 {
- t.Fatal("Port is not mapped for the port "+port, out)
+ c.Fatal("Port is not mapped for the port "+port, out)
}
}
- logDone("run - allow port range through --expose flag")
}
-func TestRunOOMExitCode(t *testing.T) {
- defer deleteAllContainers()
-
- done := make(chan struct{})
+func (s *DockerSuite) TestRunOOMExitCode(c *check.C) {
+ errChan := make(chan error)
go func() {
- defer close(done)
-
- runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x; done")
+ defer close(errChan)
+ runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done")
out, exitCode, _ := runCommandWithOutput(runCmd)
if expected := 137; exitCode != expected {
- t.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out)
+ errChan <- fmt.Errorf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out)
}
}()
select {
- case <-done:
- case <-time.After(3 * time.Second):
- t.Fatal("Timeout waiting for container to die on OOM")
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
+ case <-time.After(30 * time.Second):
+ c.Fatal("Timeout waiting for container to die on OOM")
}
-
- logDone("run - exit code on oom")
}
-func TestRunSetDefaultRestartPolicy(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunSetDefaultRestartPolicy(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "test", "busybox", "top")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
- cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.RestartPolicy.Name}}", "test")
- out, _, err := runCommandWithOutput(cmd)
- if err != nil {
- t.Fatalf("failed to inspect container: %v, output: %q", err, out)
- }
- out = strings.Trim(out, "\r\n")
+ out, err := inspectField("test", "HostConfig.RestartPolicy.Name")
+ c.Assert(err, check.IsNil)
if out != "no" {
- t.Fatalf("Set default restart policy failed")
+ c.Fatalf("Set default restart policy failed")
}
-
- logDone("run - set default restart policy success")
}
-func TestRunRestartMaxRetries(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunRestartMaxRetries(c *check.C) {
out, err := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:3", "busybox", "false").CombinedOutput()
if err != nil {
- t.Fatal(string(out), err)
+ c.Fatal(string(out), err)
}
id := strings.TrimSpace(string(out))
- if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 5); err != nil {
- t.Fatal(err)
+ if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 10); err != nil {
+ c.Fatal(err)
}
count, err := inspectField(id, "RestartCount")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if count != "3" {
- t.Fatalf("Container was restarted %s times, expected %d", count, 3)
+ c.Fatalf("Container was restarted %s times, expected %d", count, 3)
}
MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
if MaximumRetryCount != "3" {
- t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3")
+ c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3")
}
- logDone("run - test max-retries for --restart")
}
-func TestRunContainerWithWritableRootfs(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunContainerWithWritableRootfs(c *check.C) {
out, err := exec.Command(dockerBinary, "run", "--rm", "busybox", "touch", "/file").CombinedOutput()
if err != nil {
- t.Fatal(string(out), err)
+ c.Fatal(string(out), err)
}
- logDone("run - writable rootfs")
}
-func TestRunContainerWithReadonlyRootfs(t *testing.T) {
- testRequires(t, NativeExecDriver)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) {
+ testRequires(c, NativeExecDriver)
- out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", "/file").CombinedOutput()
+ for _, f := range []string{"/file", "/etc/hosts", "/etc/resolv.conf", "/etc/hostname"} {
+ testReadOnlyFile(f, c)
+ }
+}
+
+func testReadOnlyFile(filename string, c *check.C) {
+ testRequires(c, NativeExecDriver)
+
+ out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", filename).CombinedOutput()
if err == nil {
- t.Fatal("expected container to error on run with read only error")
+ c.Fatal("expected container to error on run with read only error")
}
expected := "Read-only file system"
if !strings.Contains(string(out), expected) {
- t.Fatalf("expected output from failure to contain %s but contains %s", expected, out)
+ c.Fatalf("expected output from failure to contain %s but contains %s", expected, out)
}
- logDone("run - read only rootfs")
}
-func TestRunVolumesFromRestartAfterRemoved(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunContainerWithReadonlyEtcHostsAndLinkedContainer(c *check.C) {
+ testRequires(c, NativeExecDriver)
+ _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "test-etc-hosts-ro-linked", "busybox", "top"))
+ c.Assert(err, check.IsNil)
+
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--read-only", "--link", "test-etc-hosts-ro-linked:testlinked", "busybox", "cat", "/etc/hosts"))
+ c.Assert(err, check.IsNil)
+
+ if !strings.Contains(string(out), "testlinked") {
+ c.Fatal("Expected /etc/hosts to be updated even if --read-only enabled")
+ }
+}
+
+func (s *DockerSuite) TestRunContainerWithReadonlyRootfsWithDnsFlag(c *check.C) {
+ testRequires(c, NativeExecDriver)
+
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--read-only", "--dns", "1.1.1.1", "busybox", "/bin/cat", "/etc/resolv.conf"))
+ c.Assert(err, check.IsNil)
+
+ if !strings.Contains(string(out), "1.1.1.1") {
+ c.Fatal("Expected /etc/resolv.conf to be updated even if --read-only enabled and --dns flag used")
+ }
+}
+
+func (s *DockerSuite) TestRunContainerWithReadonlyRootfsWithAddHostFlag(c *check.C) {
+ testRequires(c, NativeExecDriver)
+
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--read-only", "--add-host", "testreadonly:127.0.0.1", "busybox", "/bin/cat", "/etc/hosts"))
+ c.Assert(err, check.IsNil)
+
+ if !strings.Contains(string(out), "testreadonly") {
+ c.Fatal("Expected /etc/hosts to be updated even if --read-only enabled and --add-host flag used")
+ }
+}
+
+func (s *DockerSuite) TestRunVolumesFromRestartAfterRemoved(c *check.C) {
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "voltest", "-v", "/foo", "busybox"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "restarter", "--volumes-from", "voltest", "busybox", "top"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// Remove the main volume container and restart the consuming container
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "rm", "-f", "voltest"))
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
// This should not fail since the volumes-from were already applied
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "restarter"))
if err != nil {
- t.Fatalf("expected container to restart successfully: %v\n%s", err, out)
+ c.Fatalf("expected container to restart successfully: %v\n%s", err, out)
}
-
- logDone("run - can restart a volumes-from container after producer is removed")
}
-func TestRunPidHostWithChildIsKillable(t *testing.T) {
- defer deleteAllContainers()
+// run container with --rm should remove container if exit code != 0
+func (s *DockerSuite) TestRunContainerWithRmFlagExitCodeNotEqualToZero(c *check.C) {
+ name := "flowers"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "ls", "/notexists")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err == nil {
+ c.Fatal("Expected docker run to fail", out, err)
+ }
+
+ out, err = getAllContainers()
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ if out != "" {
+ c.Fatal("Expected not to have containers", out)
+ }
+}
+
+func (s *DockerSuite) TestRunContainerWithRmFlagCannotStartContainer(c *check.C) {
+ name := "sparkles"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "commandNotFound")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err == nil {
+ c.Fatal("Expected docker run to fail", out, err)
+ }
+
+ out, err = getAllContainers()
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ if out != "" {
+ c.Fatal("Expected not to have containers", out)
+ }
+}
+
+func (s *DockerSuite) TestRunPidHostWithChildIsKillable(c *check.C) {
name := "ibuildthecloud"
if out, err := exec.Command(dockerBinary, "run", "-d", "--pid=host", "--name", name, "busybox", "sh", "-c", "sleep 30; echo hi").CombinedOutput(); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
time.Sleep(1 * time.Second)
errchan := make(chan error)
@@ -3428,80 +3098,91 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) {
}()
select {
case err := <-errchan:
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
case <-time.After(5 * time.Second):
- t.Fatal("Kill container timed out")
+ c.Fatal("Kill container timed out")
}
- logDone("run - can kill container with pid-host and some childs of pid 1")
}
-func TestRunWithTooSmallMemoryLimit(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWithTooSmallMemoryLimit(c *check.C) {
// this memory limit is 1 byte less than the min, which is 4MB
// https://github.com/docker/docker/blob/v1.5.0/daemon/create.go#L22
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-m", "4194303", "busybox"))
if err == nil || !strings.Contains(out, "Minimum memory limit allowed is 4MB") {
- t.Fatalf("expected run to fail when using too low a memory limit: %q", out)
+ c.Fatalf("expected run to fail when using too low a memory limit: %q", out)
}
-
- logDone("run - can't set too low memory limit")
}
-func TestRunWriteToProcAsound(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWriteToProcAsound(c *check.C) {
code, err := runCommand(exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "echo 111 >> /proc/asound/version"))
if err == nil || code == 0 {
- t.Fatal("standard container should not be able to write to /proc/asound")
+ c.Fatal("standard container should not be able to write to /proc/asound")
}
- logDone("run - ro write to /proc/asound")
}
-func TestRunReadProcTimer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunReadProcTimer(c *check.C) {
+ testRequires(c, NativeExecDriver)
out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/timer_stats"))
if err != nil || code != 0 {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.Trim(out, "\n ") != "" {
- t.Fatalf("expected to receive no output from /proc/timer_stats but received %q", out)
+ c.Fatalf("expected to receive no output from /proc/timer_stats but received %q", out)
}
- logDone("run - read /proc/timer_stats")
}
-func TestRunReadProcLatency(t *testing.T) {
+func (s *DockerSuite) TestRunReadProcLatency(c *check.C) {
+ testRequires(c, NativeExecDriver)
// some kernels don't have this configured so skip the test if this file is not found
// on the host running the tests.
if _, err := os.Stat("/proc/latency_stats"); err != nil {
- t.Skip()
+ c.Skip("kernel doesnt have latency_stats configured")
return
}
- defer deleteAllContainers()
out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/latency_stats"))
if err != nil || code != 0 {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.Trim(out, "\n ") != "" {
- t.Fatalf("expected to receive no output from /proc/latency_stats but received %q", out)
+ c.Fatalf("expected to receive no output from /proc/latency_stats but received %q", out)
}
- logDone("run - read /proc/latency_stats")
}
-func TestMountIntoProc(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestMountIntoProc(c *check.C) {
+ testRequires(c, NativeExecDriver)
code, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/proc//sys", "busybox", "true"))
if err == nil || code == 0 {
- t.Fatal("container should not be able to mount into /proc")
+ c.Fatal("container should not be able to mount into /proc")
}
- logDone("run - mount into proc")
}
-func TestMountIntoSys(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestMountIntoSys(c *check.C) {
+ testRequires(c, NativeExecDriver)
_, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/sys/fs/cgroup", "busybox", "true"))
if err != nil {
- t.Fatal("container should be able to mount into /sys")
+ c.Fatal("container should be able to mount into /sys/fs/cgroup")
+ }
+}
+
+func (s *DockerSuite) TestTwoContainersInNetHost(c *check.C) {
+ dockerCmd(c, "run", "-d", "--net=host", "--name=first", "busybox", "top")
+ dockerCmd(c, "run", "-d", "--net=host", "--name=second", "busybox", "top")
+ dockerCmd(c, "stop", "first")
+ dockerCmd(c, "stop", "second")
+}
+
+func (s *DockerSuite) TestRunUnshareProc(c *check.C) {
+ testRequires(c, Apparmor, NativeExecDriver)
+
+ name := "acidburn"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "--mount-proc=/proc", "mount")
+ if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Permission denied") {
+ c.Fatalf("unshare should have failed with permission denied, got: %s, %v", out, err)
+ }
+
+ name = "cereal"
+ runCmd = exec.Command(dockerBinary, "run", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
+ if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Permission denied") {
+ c.Fatalf("unshare should have failed with permission denied, got: %s, %v", out, err)
}
- logDone("run - mount into sys")
}
diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go
index 9327ac240..59b623162 100644
--- a/integration-cli/docker_cli_run_unix_test.go
+++ b/integration-cli/docker_cli_run_unix_test.go
@@ -3,6 +3,7 @@
package main
import (
+ "bufio"
"fmt"
"io/ioutil"
"os"
@@ -10,58 +11,52 @@ import (
"path"
"path/filepath"
"strings"
- "testing"
"time"
"github.com/docker/docker/pkg/mount"
+ "github.com/go-check/check"
"github.com/kr/pty"
)
// #6509
-func TestRunRedirectStdout(t *testing.T) {
-
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunRedirectStdout(c *check.C) {
checkRedirect := func(command string) {
_, tty, err := pty.Open()
if err != nil {
- t.Fatalf("Could not open pty: %v", err)
+ c.Fatalf("Could not open pty: %v", err)
}
cmd := exec.Command("sh", "-c", command)
cmd.Stdin = tty
cmd.Stdout = tty
cmd.Stderr = tty
- ch := make(chan struct{})
if err := cmd.Start(); err != nil {
- t.Fatalf("start err: %v", err)
+ c.Fatalf("start err: %v", err)
}
+ ch := make(chan error)
go func() {
- if err := cmd.Wait(); err != nil {
- t.Fatalf("wait err=%v", err)
- }
+ ch <- cmd.Wait()
close(ch)
}()
select {
case <-time.After(10 * time.Second):
- t.Fatal("command timeout")
- case <-ch:
+ c.Fatal("command timeout")
+ case err := <-ch:
+ if err != nil {
+ c.Fatalf("wait err=%v", err)
+ }
}
}
checkRedirect(dockerBinary + " run -i busybox cat /etc/passwd | grep -q root")
checkRedirect(dockerBinary + " run busybox cat /etc/passwd | grep -q root")
-
- logDone("run - redirect stdout")
}
// Test recursive bind mount works by default
-func TestRunWithVolumesIsRecursive(t *testing.T) {
- defer deleteAllContainers()
-
+func (s *DockerSuite) TestRunWithVolumesIsRecursive(c *check.C) {
tmpDir, err := ioutil.TempDir("", "docker_recursive_mount_test")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer os.RemoveAll(tmpDir)
@@ -69,68 +64,62 @@ func TestRunWithVolumesIsRecursive(t *testing.T) {
// Create a temporary tmpfs mount.
tmpfsDir := filepath.Join(tmpDir, "tmpfs")
if err := os.MkdirAll(tmpfsDir, 0777); err != nil {
- t.Fatalf("failed to mkdir at %s - %s", tmpfsDir, err)
+ c.Fatalf("failed to mkdir at %s - %s", tmpfsDir, err)
}
if err := mount.Mount("tmpfs", tmpfsDir, "tmpfs", ""); err != nil {
- t.Fatalf("failed to create a tmpfs mount at %s - %s", tmpfsDir, err)
+ c.Fatalf("failed to create a tmpfs mount at %s - %s", tmpfsDir, err)
}
f, err := ioutil.TempFile(tmpfsDir, "touch-me")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer f.Close()
runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", fmt.Sprintf("%s:/tmp:ro", tmpDir), "busybox:latest", "ls", "/tmp/tmpfs")
out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
if err != nil && exitCode != 0 {
- t.Fatal(out, stderr, err)
+ c.Fatal(out, stderr, err)
}
if !strings.Contains(out, filepath.Base(f.Name())) {
- t.Fatal("Recursive bind mount test failed. Expected file not found")
+ c.Fatal("Recursive bind mount test failed. Expected file not found")
}
-
- logDone("run - volumes are bind mounted recursively")
}
-func TestRunWithUlimits(t *testing.T) {
- testRequires(t, NativeExecDriver)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWithUlimits(c *check.C) {
+ testRequires(c, NativeExecDriver)
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name=testulimits", "--ulimit", "nofile=42", "busybox", "/bin/sh", "-c", "ulimit -n"))
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
ul := strings.TrimSpace(out)
if ul != "42" {
- t.Fatalf("expected `ulimit -n` to be 42, got %s", ul)
+ c.Fatalf("expected `ulimit -n` to be 42, got %s", ul)
}
-
- logDone("run - ulimits are set")
}
-func TestRunContainerWithCgroupParent(t *testing.T) {
- testRequires(t, NativeExecDriver)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunContainerWithCgroupParent(c *check.C) {
+ testRequires(c, NativeExecDriver)
cgroupParent := "test"
data, err := ioutil.ReadFile("/proc/self/cgroup")
if err != nil {
- t.Fatalf("failed to read '/proc/self/cgroup - %v", err)
+ c.Fatalf("failed to read '/proc/self/cgroup - %v", err)
}
selfCgroupPaths := parseCgroupPaths(string(data))
selfCpuCgroup, found := selfCgroupPaths["memory"]
if !found {
- t.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths)
+ c.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths)
}
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--cgroup-parent", cgroupParent, "--rm", "busybox", "cat", "/proc/self/cgroup"))
if err != nil {
- t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err)
+ c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err)
}
cgroupPaths := parseCgroupPaths(string(out))
if len(cgroupPaths) == 0 {
- t.Fatalf("unexpected output - %q", string(out))
+ c.Fatalf("unexpected output - %q", string(out))
}
found = false
expectedCgroupPrefix := path.Join(selfCpuCgroup, cgroupParent)
@@ -141,24 +130,22 @@ func TestRunContainerWithCgroupParent(t *testing.T) {
}
}
if !found {
- t.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", expectedCgroupPrefix, cgroupPaths)
+ c.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", expectedCgroupPrefix, cgroupPaths)
}
- logDone("run - cgroup parent")
}
-func TestRunContainerWithCgroupParentAbsPath(t *testing.T) {
- testRequires(t, NativeExecDriver)
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunContainerWithCgroupParentAbsPath(c *check.C) {
+ testRequires(c, NativeExecDriver)
cgroupParent := "/cgroup-parent/test"
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--cgroup-parent", cgroupParent, "--rm", "busybox", "cat", "/proc/self/cgroup"))
if err != nil {
- t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err)
+ c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err)
}
cgroupPaths := parseCgroupPaths(string(out))
if len(cgroupPaths) == 0 {
- t.Fatalf("unexpected output - %q", string(out))
+ c.Fatalf("unexpected output - %q", string(out))
}
found := false
for _, path := range cgroupPaths {
@@ -168,8 +155,98 @@ func TestRunContainerWithCgroupParentAbsPath(t *testing.T) {
}
}
if !found {
- t.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", cgroupParent, cgroupPaths)
+ c.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", cgroupParent, cgroupPaths)
+ }
+}
+
+func (s *DockerSuite) TestRunDeviceDirectory(c *check.C) {
+ testRequires(c, NativeExecDriver)
+ cmd := exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/snd", "busybox", "sh", "-c", "ls /dev/snd/")
+
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
}
- logDone("run - cgroup parent with absolute cgroup path")
+ if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "timer") {
+ c.Fatalf("expected output /dev/snd/timer, received %s", actual)
+ }
+
+ cmd = exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/othersnd", "busybox", "sh", "-c", "ls /dev/othersnd/")
+
+ out, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+
+ if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "seq") {
+ c.Fatalf("expected output /dev/othersnd/seq, received %s", actual)
+ }
+}
+
+// TestRunDetach checks attaching and detaching with the escape sequence.
+func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
+ name := "attach-detach"
+ cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat")
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ cpty, tty, err := pty.Open()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer cpty.Close()
+ cmd.Stdin = tty
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ if err := waitRun(name); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ // escape sequence
+ if _, err := cpty.Write([]byte{16}); err != nil {
+ c.Fatal(err)
+ }
+ time.Sleep(100 * time.Millisecond)
+ if _, err := cpty.Write([]byte{17}); err != nil {
+ c.Fatal(err)
+ }
+
+ ch := make(chan struct{})
+ go func() {
+ cmd.Wait()
+ ch <- struct{}{}
+ }()
+
+ running, err := inspectField(name, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
+ }
+
+ go func() {
+ exec.Command(dockerBinary, "kill", name).Run()
+ }()
+
+ select {
+ case <-ch:
+ case <-time.After(10 * time.Millisecond):
+ c.Fatal("timed out waiting for container to exit")
+ }
}
diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go
index 1dd71ee5f..c53889039 100644
--- a/integration-cli/docker_cli_save_load_test.go
+++ b/integration-cli/docker_cli_save_load_test.go
@@ -9,37 +9,31 @@ import (
"reflect"
"sort"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
// save a repo using gz compression and try to load it using stdout
-func TestSaveXzAndLoadRepoStdout(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveXzAndLoadRepoStdout(c *check.C) {
+ name := "test-save-xz-and-load-repo-stdout"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to create a container: %v %v", out, err)
+ c.Fatalf("failed to create a container: %v %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
-
repoName := "foobar-save-load-test-xz-gz"
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- out, _, err = runCommandWithOutput(inspectCmd)
- if err != nil {
- t.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, repoName)
out, _, err = runCommandWithOutput(commitCmd)
if err != nil {
- t.Fatalf("failed to commit container: %v %v", out, err)
+ c.Fatalf("failed to commit container: %v %v", out, err)
}
- inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
+ inspectCmd := exec.Command(dockerBinary, "inspect", repoName)
before, _, err := runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("the repo should exist before saving it: %v %v", before, err)
+ c.Fatalf("the repo should exist before saving it: %v %v", before, err)
}
repoTarball, _, err := runCommandPipelineWithOutput(
@@ -47,7 +41,7 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) {
exec.Command("xz", "-c"),
exec.Command("gzip", "-c"))
if err != nil {
- t.Fatalf("failed to save repo: %v %v", out, err)
+ c.Fatalf("failed to save repo: %v %v", out, err)
}
deleteImages(repoName)
@@ -55,48 +49,40 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) {
loadCmd.Stdin = strings.NewReader(repoTarball)
out, _, err = runCommandWithOutput(loadCmd)
if err == nil {
- t.Fatalf("expected error, but succeeded with no error and output: %v", out)
+ c.Fatalf("expected error, but succeeded with no error and output: %v", out)
}
inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
after, _, err := runCommandWithOutput(inspectCmd)
if err == nil {
- t.Fatalf("the repo should not exist: %v", after)
+ c.Fatalf("the repo should not exist: %v", after)
}
deleteImages(repoName)
- logDone("load - save a repo with xz compression & load it using stdout")
}
// save a repo using xz+gz compression and try to load it using stdout
-func TestSaveXzGzAndLoadRepoStdout(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveXzGzAndLoadRepoStdout(c *check.C) {
+ name := "test-save-xz-gz-and-load-repo-stdout"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to create a container: %v %v", out, err)
+ c.Fatalf("failed to create a container: %v %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
-
repoName := "foobar-save-load-test-xz-gz"
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- out, _, err = runCommandWithOutput(inspectCmd)
- if err != nil {
- t.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, repoName)
out, _, err = runCommandWithOutput(commitCmd)
if err != nil {
- t.Fatalf("failed to commit container: %v %v", out, err)
+ c.Fatalf("failed to commit container: %v %v", out, err)
}
- inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
+ inspectCmd := exec.Command(dockerBinary, "inspect", repoName)
before, _, err := runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("the repo should exist before saving it: %v %v", before, err)
+ c.Fatalf("the repo should exist before saving it: %v %v", before, err)
}
out, _, err = runCommandPipelineWithOutput(
@@ -104,7 +90,7 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) {
exec.Command("xz", "-c"),
exec.Command("gzip", "-c"))
if err != nil {
- t.Fatalf("failed to save repo: %v %v", out, err)
+ c.Fatalf("failed to save repo: %v %v", out, err)
}
deleteImages(repoName)
@@ -113,90 +99,82 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) {
loadCmd.Stdin = strings.NewReader(out)
out, _, err = runCommandWithOutput(loadCmd)
if err == nil {
- t.Fatalf("expected error, but succeeded with no error and output: %v", out)
+ c.Fatalf("expected error, but succeeded with no error and output: %v", out)
}
inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
after, _, err := runCommandWithOutput(inspectCmd)
if err == nil {
- t.Fatalf("the repo should not exist: %v", after)
+ c.Fatalf("the repo should not exist: %v", after)
}
-
- deleteContainer(cleanedContainerID)
- deleteImages(repoName)
-
- logDone("load - save a repo with xz+gz compression & load it using stdout")
}
-func TestSaveSingleTag(t *testing.T) {
+func (s *DockerSuite) TestSaveSingleTag(c *check.C) {
repoName := "foobar-save-single-tag-test"
tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", fmt.Sprintf("%v:latest", repoName))
- defer deleteImages(repoName)
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatalf("failed to tag repo: %s, %v", out, err)
+ c.Fatalf("failed to tag repo: %s, %v", out, err)
}
idCmd := exec.Command(dockerBinary, "images", "-q", "--no-trunc", repoName)
out, _, err := runCommandWithOutput(idCmd)
if err != nil {
- t.Fatalf("failed to get repo ID: %s, %v", out, err)
+ c.Fatalf("failed to get repo ID: %s, %v", out, err)
}
- cleanedImageID := stripTrailingCharacters(out)
+ cleanedImageID := strings.TrimSpace(out)
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "save", fmt.Sprintf("%v:latest", repoName)),
exec.Command("tar", "t"),
exec.Command("grep", "-E", fmt.Sprintf("(^repositories$|%v)", cleanedImageID)))
if err != nil {
- t.Fatalf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err)
+ c.Fatalf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err)
}
- logDone("save - save a specific image:tag")
}
-func TestSaveImageId(t *testing.T) {
+func (s *DockerSuite) TestSaveImageId(c *check.C) {
repoName := "foobar-save-image-id-test"
tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName))
- defer deleteImages(repoName)
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatalf("failed to tag repo: %s, %v", out, err)
+ c.Fatalf("failed to tag repo: %s, %v", out, err)
}
idLongCmd := exec.Command(dockerBinary, "images", "-q", "--no-trunc", repoName)
out, _, err := runCommandWithOutput(idLongCmd)
if err != nil {
- t.Fatalf("failed to get repo ID: %s, %v", out, err)
+ c.Fatalf("failed to get repo ID: %s, %v", out, err)
}
- cleanedLongImageID := stripTrailingCharacters(out)
+ cleanedLongImageID := strings.TrimSpace(out)
idShortCmd := exec.Command(dockerBinary, "images", "-q", repoName)
out, _, err = runCommandWithOutput(idShortCmd)
if err != nil {
- t.Fatalf("failed to get repo short ID: %s, %v", out, err)
+ c.Fatalf("failed to get repo short ID: %s, %v", out, err)
}
- cleanedShortImageID := stripTrailingCharacters(out)
+ cleanedShortImageID := strings.TrimSpace(out)
saveCmd := exec.Command(dockerBinary, "save", cleanedShortImageID)
tarCmd := exec.Command("tar", "t")
tarCmd.Stdin, err = saveCmd.StdoutPipe()
if err != nil {
- t.Fatalf("cannot set stdout pipe for tar: %v", err)
+ c.Fatalf("cannot set stdout pipe for tar: %v", err)
}
grepCmd := exec.Command("grep", cleanedLongImageID)
grepCmd.Stdin, err = tarCmd.StdoutPipe()
if err != nil {
- t.Fatalf("cannot set stdout pipe for grep: %v", err)
+ c.Fatalf("cannot set stdout pipe for grep: %v", err)
}
if err = tarCmd.Start(); err != nil {
- t.Fatalf("tar failed with error: %v", err)
+ c.Fatalf("tar failed with error: %v", err)
}
if err = saveCmd.Start(); err != nil {
- t.Fatalf("docker save failed with error: %v", err)
+ c.Fatalf("docker save failed with error: %v", err)
}
defer saveCmd.Wait()
defer tarCmd.Wait()
@@ -204,40 +182,31 @@ func TestSaveImageId(t *testing.T) {
out, _, err = runCommandWithOutput(grepCmd)
if err != nil {
- t.Fatalf("failed to save repo with image ID: %s, %v", out, err)
+ c.Fatalf("failed to save repo with image ID: %s, %v", out, err)
}
- logDone("save - save a image by ID")
}
// save a repo and try to load it using flags
-func TestSaveAndLoadRepoFlags(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) {
+ name := "test-save-and-load-repo-flags"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to create a container: %s, %v", out, err)
+ c.Fatalf("failed to create a container: %s, %v", out, err)
}
-
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
-
repoName := "foobar-save-load-test"
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("output should've been a container id: %s, %v", out, err)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, repoName)
deleteImages(repoName)
if out, _, err = runCommandWithOutput(commitCmd); err != nil {
- t.Fatalf("failed to commit container: %s, %v", out, err)
+ c.Fatalf("failed to commit container: %s, %v", out, err)
}
- inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
+ inspectCmd := exec.Command(dockerBinary, "inspect", repoName)
before, _, err := runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("the repo should exist before saving it: %s, %v", before, err)
+ c.Fatalf("the repo should exist before saving it: %s, %v", before, err)
}
@@ -245,39 +214,35 @@ func TestSaveAndLoadRepoFlags(t *testing.T) {
exec.Command(dockerBinary, "save", repoName),
exec.Command(dockerBinary, "load"))
if err != nil {
- t.Fatalf("failed to save and load repo: %s, %v", out, err)
+ c.Fatalf("failed to save and load repo: %s, %v", out, err)
}
inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
after, _, err := runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("the repo should exist after loading it: %s, %v", after, err)
+ c.Fatalf("the repo should exist after loading it: %s, %v", after, err)
}
if before != after {
- t.Fatalf("inspect is not the same after a save / load")
+ c.Fatalf("inspect is not the same after a save / load")
}
-
- logDone("save - save a repo using -o && load a repo using -i")
}
-func TestSaveMultipleNames(t *testing.T) {
+func (s *DockerSuite) TestSaveMultipleNames(c *check.C) {
repoName := "foobar-save-multi-name-test"
// Make one image
tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-one:latest", repoName))
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatalf("failed to tag repo: %s, %v", out, err)
+ c.Fatalf("failed to tag repo: %s, %v", out, err)
}
- defer deleteImages(repoName + "-one")
// Make two images
tagCmd = exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-two:latest", repoName))
out, _, err := runCommandWithOutput(tagCmd)
if err != nil {
- t.Fatalf("failed to tag repo: %s, %v", out, err)
+ c.Fatalf("failed to tag repo: %s, %v", out, err)
}
- defer deleteImages(repoName + "-two")
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "save", fmt.Sprintf("%v-one", repoName), fmt.Sprintf("%v-two:latest", repoName)),
@@ -285,13 +250,12 @@ func TestSaveMultipleNames(t *testing.T) {
exec.Command("grep", "-q", "-E", "(-one|-two)"),
)
if err != nil {
- t.Fatalf("failed to save multiple repos: %s, %v", out, err)
+ c.Fatalf("failed to save multiple repos: %s, %v", out, err)
}
- logDone("save - save by multiple names")
}
-func TestSaveRepoWithMultipleImages(t *testing.T) {
+func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) {
makeImage := func(from string, tag string) string {
runCmd := exec.Command(dockerBinary, "run", "-d", from, "true")
@@ -300,16 +264,15 @@ func TestSaveRepoWithMultipleImages(t *testing.T) {
err error
)
if out, _, err = runCommandWithOutput(runCmd); err != nil {
- t.Fatalf("failed to create a container: %v %v", out, err)
+ c.Fatalf("failed to create a container: %v %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, tag)
if out, _, err = runCommandWithOutput(commitCmd); err != nil {
- t.Fatalf("failed to commit container: %v %v", out, err)
+ c.Fatalf("failed to commit container: %v %v", out, err)
}
- imageID := stripTrailingCharacters(out)
+ imageID := strings.TrimSpace(out)
return imageID
}
@@ -318,9 +281,7 @@ func TestSaveRepoWithMultipleImages(t *testing.T) {
tagBar := repoName + ":bar"
idFoo := makeImage("busybox:latest", tagFoo)
- defer deleteImages(idFoo)
idBar := makeImage("busybox:latest", tagBar)
- defer deleteImages(idBar)
deleteImages(repoName)
@@ -331,61 +292,59 @@ func TestSaveRepoWithMultipleImages(t *testing.T) {
exec.Command("grep", "VERSION"),
exec.Command("cut", "-d", "/", "-f1"))
if err != nil {
- t.Fatalf("failed to save multiple images: %s, %v", out, err)
+ c.Fatalf("failed to save multiple images: %s, %v", out, err)
}
- actual := strings.Split(stripTrailingCharacters(out), "\n")
+ actual := strings.Split(strings.TrimSpace(out), "\n")
// make the list of expected layers
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "history", "-q", "--no-trunc", "busybox:latest"))
if err != nil {
- t.Fatalf("failed to get history: %s, %v", out, err)
+ c.Fatalf("failed to get history: %s, %v", out, err)
}
- expected := append(strings.Split(stripTrailingCharacters(out), "\n"), idFoo, idBar)
+ expected := append(strings.Split(strings.TrimSpace(out), "\n"), idFoo, idBar)
sort.Strings(actual)
sort.Strings(expected)
if !reflect.DeepEqual(expected, actual) {
- t.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected)
+ c.Fatalf("archive does not contains the right layers: got %v, expected %v", actual, expected)
}
- logDone("save - save repository with multiple images")
}
// Issue #6722 #5892 ensure directories are included in changes
-func TestSaveDirectoryPermissions(t *testing.T) {
+func (s *DockerSuite) TestSaveDirectoryPermissions(c *check.C) {
layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
name := "save-directory-permissions"
tmpDir, err := ioutil.TempDir("", "save-layers-with-directories")
if err != nil {
- t.Errorf("failed to create temporary directory: %s", err)
+ c.Errorf("failed to create temporary directory: %s", err)
}
extractionDirectory := filepath.Join(tmpDir, "image-extraction-dir")
os.Mkdir(extractionDirectory, 0777)
defer os.RemoveAll(tmpDir)
- defer deleteImages(name)
_, err = buildImage(name,
`FROM busybox
RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a
RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if out, _, err := runCommandPipelineWithOutput(
exec.Command(dockerBinary, "save", name),
exec.Command("tar", "-xf", "-", "-C", extractionDirectory),
); err != nil {
- t.Errorf("failed to save and extract image: %s", out)
+ c.Errorf("failed to save and extract image: %s", out)
}
dirs, err := ioutil.ReadDir(extractionDirectory)
if err != nil {
- t.Errorf("failed to get a listing of the layer directories: %s", err)
+ c.Errorf("failed to get a listing of the layer directories: %s", err)
}
found := false
@@ -396,7 +355,7 @@ func TestSaveDirectoryPermissions(t *testing.T) {
f, err := os.Open(layerPath)
if err != nil {
- t.Fatalf("failed to open %s: %s", layerPath, err)
+ c.Fatalf("failed to open %s: %s", layerPath, err)
}
entries, err := ListTar(f)
@@ -406,7 +365,7 @@ func TestSaveDirectoryPermissions(t *testing.T) {
}
}
if err != nil {
- t.Fatalf("encountered error while listing tar entries: %s", err)
+ c.Fatalf("encountered error while listing tar entries: %s", err)
}
if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) {
@@ -417,8 +376,7 @@ func TestSaveDirectoryPermissions(t *testing.T) {
}
if !found {
- t.Fatalf("failed to find the layer with the right content listing")
+ c.Fatalf("failed to find the layer with the right content listing")
}
- logDone("save - ensure directories exist in exported layers")
}
diff --git a/integration-cli/docker_cli_save_load_unix_test.go b/integration-cli/docker_cli_save_load_unix_test.go
index c6704877a..5a6f5809a 100644
--- a/integration-cli/docker_cli_save_load_unix_test.go
+++ b/integration-cli/docker_cli_save_load_unix_test.go
@@ -4,99 +4,95 @@ package main
import (
"bytes"
- "fmt"
+ "io/ioutil"
"os"
"os/exec"
- "testing"
"github.com/docker/docker/vendor/src/github.com/kr/pty"
+ "github.com/go-check/check"
)
// save a repo and try to load it using stdout
-func TestSaveAndLoadRepoStdout(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveAndLoadRepoStdout(c *check.C) {
+ name := "test-save-and-load-repo-stdout"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to create a container: %s, %v", out, err)
+ c.Fatalf("failed to create a container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
-
repoName := "foobar-save-load-test"
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- if out, _, err = runCommandWithOutput(inspectCmd); err != nil {
- t.Fatalf("output should've been a container id: %s, %v", out, err)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, repoName)
if out, _, err = runCommandWithOutput(commitCmd); err != nil {
- t.Fatalf("failed to commit container: %s, %v", out, err)
+ c.Fatalf("failed to commit container: %s, %v", out, err)
}
- inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
+ inspectCmd := exec.Command(dockerBinary, "inspect", repoName)
before, _, err := runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("the repo should exist before saving it: %s, %v", before, err)
+ c.Fatalf("the repo should exist before saving it: %s, %v", before, err)
}
- saveCmdTemplate := `%v save %v > /tmp/foobar-save-load-test.tar`
- saveCmdFinal := fmt.Sprintf(saveCmdTemplate, dockerBinary, repoName)
- saveCmd := exec.Command("bash", "-c", saveCmdFinal)
- if out, _, err = runCommandWithOutput(saveCmd); err != nil {
- t.Fatalf("failed to save repo: %s, %v", out, err)
+ tmpFile, err := ioutil.TempFile("", "foobar-save-load-test.tar")
+ c.Assert(err, check.IsNil)
+ defer os.Remove(tmpFile.Name())
+
+ saveCmd := exec.Command(dockerBinary, "save", repoName)
+ saveCmd.Stdout = tmpFile
+
+ if _, err = runCommand(saveCmd); err != nil {
+ c.Fatalf("failed to save repo: %v", err)
}
+ tmpFile, err = os.Open(tmpFile.Name())
+ c.Assert(err, check.IsNil)
+
deleteImages(repoName)
- loadCmdFinal := `cat /tmp/foobar-save-load-test.tar | docker load`
- loadCmd := exec.Command("bash", "-c", loadCmdFinal)
+ loadCmd := exec.Command(dockerBinary, "load")
+ loadCmd.Stdin = tmpFile
+
if out, _, err = runCommandWithOutput(loadCmd); err != nil {
- t.Fatalf("failed to load repo: %s, %v", out, err)
+ c.Fatalf("failed to load repo: %s, %v", out, err)
}
inspectCmd = exec.Command(dockerBinary, "inspect", repoName)
after, _, err := runCommandWithOutput(inspectCmd)
if err != nil {
- t.Fatalf("the repo should exist after loading it: %s %v", after, err)
+ c.Fatalf("the repo should exist after loading it: %s %v", after, err)
}
if before != after {
- t.Fatalf("inspect is not the same after a save / load")
+ c.Fatalf("inspect is not the same after a save / load")
}
- deleteContainer(cleanedContainerID)
deleteImages(repoName)
- os.Remove("/tmp/foobar-save-load-test.tar")
-
- logDone("save - save/load a repo using stdout")
-
pty, tty, err := pty.Open()
if err != nil {
- t.Fatalf("Could not open pty: %v", err)
+ c.Fatalf("Could not open pty: %v", err)
}
cmd := exec.Command(dockerBinary, "save", repoName)
cmd.Stdin = tty
cmd.Stdout = tty
cmd.Stderr = tty
if err := cmd.Start(); err != nil {
- t.Fatalf("start err: %v", err)
+ c.Fatalf("start err: %v", err)
}
if err := cmd.Wait(); err == nil {
- t.Fatal("did not break writing to a TTY")
+ c.Fatal("did not break writing to a TTY")
}
buf := make([]byte, 1024)
n, err := pty.Read(buf)
if err != nil {
- t.Fatal("could not read tty output")
+ c.Fatal("could not read tty output")
}
if !bytes.Contains(buf[:n], []byte("Cowardly refusing")) {
- t.Fatal("help output is not being yielded", out)
+ c.Fatal("help output is not being yielded", out)
}
- logDone("save - do not save to a tty")
}
diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go
index fafb5df75..da298a1e0 100644
--- a/integration-cli/docker_cli_search_test.go
+++ b/integration-cli/docker_cli_search_test.go
@@ -3,20 +3,103 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
// search for repos named "registry" on the central registry
-func TestSearchOnCentralRegistry(t *testing.T) {
+func (s *DockerSuite) TestSearchOnCentralRegistry(c *check.C) {
+ testRequires(c, Network)
searchCmd := exec.Command(dockerBinary, "search", "busybox")
out, exitCode, err := runCommandWithOutput(searchCmd)
if err != nil || exitCode != 0 {
- t.Fatalf("failed to search on the central registry: %s, %v", out, err)
+ c.Fatalf("failed to search on the central registry: %s, %v", out, err)
}
if !strings.Contains(out, "Busybox base image.") {
- t.Fatal("couldn't find any repository named (or containing) 'Busybox base image.'")
+ c.Fatal("couldn't find any repository named (or containing) 'Busybox base image.'")
+ }
+
+}
+
+func (s *DockerSuite) TestSearchStarsOptionWithWrongParameter(c *check.C) {
+ searchCmdStarsChars := exec.Command(dockerBinary, "search", "--stars=a", "busybox")
+ out, exitCode, err := runCommandWithOutput(searchCmdStarsChars)
+ if err == nil || exitCode == 0 {
+ c.Fatalf("Should not get right information: %s, %v", out, err)
+ }
+
+ if !strings.Contains(out, "invalid value") {
+ c.Fatal("couldn't find the invalid value warning")
+ }
+
+ searchCmdStarsNegativeNumber := exec.Command(dockerBinary, "search", "-s=-1", "busybox")
+ out, exitCode, err = runCommandWithOutput(searchCmdStarsNegativeNumber)
+ if err == nil || exitCode == 0 {
+ c.Fatalf("Should not get right information: %s, %v", out, err)
+ }
+
+ if !strings.Contains(out, "invalid value") {
+ c.Fatal("couldn't find the invalid value warning")
+ }
+
+}
+
+func (s *DockerSuite) TestSearchCmdOptions(c *check.C) {
+ testRequires(c, Network)
+ searchCmdhelp := exec.Command(dockerBinary, "search", "--help")
+ out, exitCode, err := runCommandWithOutput(searchCmdhelp)
+ if err != nil || exitCode != 0 {
+ c.Fatalf("failed to get search help information: %s, %v", out, err)
+ }
+
+ if !strings.Contains(out, "Usage: docker search [OPTIONS] TERM") {
+ c.Fatalf("failed to show docker search usage: %s, %v", out, err)
+ }
+
+ searchCmd := exec.Command(dockerBinary, "search", "busybox")
+ outSearchCmd, exitCode, err := runCommandWithOutput(searchCmd)
+ if err != nil || exitCode != 0 {
+ c.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err)
+ }
+
+ searchCmdNotrunc := exec.Command(dockerBinary, "search", "--no-trunc=true", "busybox")
+ outSearchCmdNotrunc, _, err := runCommandWithOutput(searchCmdNotrunc)
+ if err != nil {
+ c.Fatalf("failed to search on the central registry: %s, %v", outSearchCmdNotrunc, err)
+ }
+
+ if len(outSearchCmd) > len(outSearchCmdNotrunc) {
+ c.Fatalf("The no-trunc option can't take effect.")
+ }
+
+ searchCmdautomated := exec.Command(dockerBinary, "search", "--automated=true", "busybox")
+ outSearchCmdautomated, exitCode, err := runCommandWithOutput(searchCmdautomated) //The busybox is a busybox base image, not an AUTOMATED image.
+ if err != nil || exitCode != 0 {
+ c.Fatalf("failed to search with automated=true on the central registry: %s, %v", outSearchCmdautomated, err)
+ }
+
+ outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n")
+ for i := range outSearchCmdautomatedSlice {
+ if strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox ") {
+ c.Fatalf("The busybox is not an AUTOMATED image: %s, %v", out, err)
+ }
+ }
+
+ searchCmdStars := exec.Command(dockerBinary, "search", "-s=2", "busybox")
+ outSearchCmdStars, exitCode, err := runCommandWithOutput(searchCmdStars)
+ if err != nil || exitCode != 0 {
+ c.Fatalf("failed to search with stars=2 on the central registry: %s, %v", outSearchCmdStars, err)
+ }
+
+ if strings.Count(outSearchCmdStars, "[OK]") > strings.Count(outSearchCmd, "[OK]") {
+ c.Fatalf("The quantity of images with stars should be less than that of all images: %s, %v", outSearchCmdStars, err)
+ }
+
+ searchCmdOptions := exec.Command(dockerBinary, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox")
+ out, exitCode, err = runCommandWithOutput(searchCmdOptions)
+ if err != nil || exitCode != 0 {
+ c.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err)
}
- logDone("search - search for repositories named (or containing) 'Busybox base image.'")
}
diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go
index 3ec04c916..047582673 100644
--- a/integration-cli/docker_cli_start_test.go
+++ b/integration-cli/docker_cli_start_test.go
@@ -4,253 +4,200 @@ import (
"fmt"
"os/exec"
"strings"
- "testing"
"time"
+
+ "github.com/go-check/check"
)
// Regression test for https://github.com/docker/docker/issues/7843
-func TestStartAttachReturnsOnError(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestStartAttachReturnsOnError(c *check.C) {
- dockerCmd(t, "run", "-d", "--name", "test", "busybox")
- dockerCmd(t, "wait", "test")
+ dockerCmd(c, "run", "-d", "--name", "test", "busybox")
+ dockerCmd(c, "wait", "test")
// Expect this to fail because the above container is stopped, this is what we want
if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "test2", "--link", "test:test", "busybox")); err == nil {
- t.Fatal("Expected error but got none")
+ c.Fatal("Expected error but got none")
}
- ch := make(chan struct{})
+ ch := make(chan error)
go func() {
// Attempt to start attached to the container that won't start
// This should return an error immediately since the container can't be started
if _, err := runCommand(exec.Command(dockerBinary, "start", "-a", "test2")); err == nil {
- t.Fatal("Expected error but got none")
+ ch <- fmt.Errorf("Expected error but got none")
}
close(ch)
}()
select {
- case <-ch:
+ case err := <-ch:
+ c.Assert(err, check.IsNil)
case <-time.After(time.Second):
- t.Fatalf("Attach did not exit properly")
+ c.Fatalf("Attach did not exit properly")
}
- logDone("start - error on start with attach exits")
}
// gh#8555: Exit code should be passed through when using start -a
-func TestStartAttachCorrectExitCode(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestStartAttachCorrectExitCode(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 2; exit 1")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
- out = stripTrailingCharacters(out)
+ out = strings.TrimSpace(out)
// make sure the container has exited before trying the "start -a"
waitCmd := exec.Command(dockerBinary, "wait", out)
if _, _, err = runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("Failed to wait on container: %v", err)
+ c.Fatalf("Failed to wait on container: %v", err)
}
startCmd := exec.Command(dockerBinary, "start", "-a", out)
startOut, exitCode, err := runCommandWithOutput(startCmd)
if err != nil && !strings.Contains("exit status 1", fmt.Sprintf("%s", err)) {
- t.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut)
+ c.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut)
}
if exitCode != 1 {
- t.Fatalf("start -a did not respond with proper exit code: expected 1, got %d", exitCode)
+ c.Fatalf("start -a did not respond with proper exit code: expected 1, got %d", exitCode)
}
- logDone("start - correct exit code returned with -a")
}
-func TestStartSilentAttach(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestStartAttachSilent(c *check.C) {
name := "teststartattachcorrectexitcode"
runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "echo", "test")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
}
// make sure the container has exited before trying the "start -a"
waitCmd := exec.Command(dockerBinary, "wait", name)
if _, _, err = runCommandWithOutput(waitCmd); err != nil {
- t.Fatalf("wait command failed with error: %v", err)
+ c.Fatalf("wait command failed with error: %v", err)
}
startCmd := exec.Command(dockerBinary, "start", "-a", name)
startOut, _, err := runCommandWithOutput(startCmd)
if err != nil {
- t.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut)
+ c.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut)
}
if expected := "test\n"; startOut != expected {
- t.Fatalf("start -a produced unexpected output: expected %q, got %q", expected, startOut)
+ c.Fatalf("start -a produced unexpected output: expected %q, got %q", expected, startOut)
}
- logDone("start - don't echo container ID when attaching")
}
-func TestStartRecordError(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestStartRecordError(c *check.C) {
// when container runs successfully, we should not have state.Error
- dockerCmd(t, "run", "-d", "-p", "9999:9999", "--name", "test", "busybox", "top")
+ dockerCmd(c, "run", "-d", "-p", "9999:9999", "--name", "test", "busybox", "top")
stateErr, err := inspectField("test", "State.Error")
- if err != nil {
- t.Fatalf("Failed to inspect %q state's error, got error %q", "test", err)
- }
+ c.Assert(err, check.IsNil)
if stateErr != "" {
- t.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr)
+ c.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr)
}
// Expect this to fail and records error because of ports conflict
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "test2", "-p", "9999:9999", "busybox", "top"))
if err == nil {
- t.Fatalf("Expected error but got none, output %q", out)
+ c.Fatalf("Expected error but got none, output %q", out)
}
stateErr, err = inspectField("test2", "State.Error")
- if err != nil {
- t.Fatalf("Failed to inspect %q state's error, got error %q", "test2", err)
- }
+ c.Assert(err, check.IsNil)
expected := "port is already allocated"
if stateErr == "" || !strings.Contains(stateErr, expected) {
- t.Fatalf("State.Error(%q) does not include %q", stateErr, expected)
+ c.Fatalf("State.Error(%q) does not include %q", stateErr, expected)
}
// Expect the conflict to be resolved when we stop the initial container
- dockerCmd(t, "stop", "test")
- dockerCmd(t, "start", "test2")
+ dockerCmd(c, "stop", "test")
+ dockerCmd(c, "start", "test2")
stateErr, err = inspectField("test2", "State.Error")
- if err != nil {
- t.Fatalf("Failed to inspect %q state's error, got error %q", "test", err)
- }
+ c.Assert(err, check.IsNil)
if stateErr != "" {
- t.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr)
+ c.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr)
}
- logDone("start - set state error when start is unsuccessful")
}
-// gh#8726: a failed Start() breaks --volumes-from on subsequent Start()'s
-func TestStartVolumesFromFailsCleanly(t *testing.T) {
- defer deleteAllContainers()
-
- // Create the first data volume
- dockerCmd(t, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox")
-
- // Expect this to fail because the data test after contaienr doesn't exist yet
- if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "consumer", "--volumes-from", "data_before", "--volumes-from", "data_after", "busybox")); err == nil {
- t.Fatal("Expected error but got none")
- }
-
- // Create the second data volume
- dockerCmd(t, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox")
-
- // Now, all the volumes should be there
- dockerCmd(t, "start", "consumer")
-
- // Check that we have the volumes we want
- out, _, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer")
- n_volumes := strings.Trim(out, " \r\n'")
- if n_volumes != "2" {
- t.Fatalf("Missing volumes: expected 2, got %s", n_volumes)
- }
-
- logDone("start - missing containers in --volumes-from did not affect subsequent runs")
-}
-
-func TestStartPausedContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestStartPausedContainer(c *check.C) {
defer unpauseAllContainers()
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "pause", "testing")
if out, _, err := runCommandWithOutput(runCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
runCmd = exec.Command(dockerBinary, "start", "testing")
if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Cannot start a paused container, try unpause instead.") {
- t.Fatalf("an error should have been shown that you cannot start paused container: %s\n%v", out, err)
+ c.Fatalf("an error should have been shown that you cannot start paused container: %s\n%v", out, err)
}
- logDone("start - error should show if trying to start paused container")
}
-func TestStartMultipleContainers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestStartMultipleContainers(c *check.C) {
// run a container named 'parent' and create two container link to `parent`
cmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
for _, container := range []string{"child_first", "child_second"} {
cmd = exec.Command(dockerBinary, "create", "--name", container, "--link", "parent:parent", "busybox", "top")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
}
// stop 'parent' container
cmd = exec.Command(dockerBinary, "stop", "parent")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", "parent")
- out, _, err := runCommandWithOutput(cmd)
- if err != nil {
- t.Fatal(out, err)
- }
- out = strings.Trim(out, "\r\n")
+ out, err := inspectField("parent", "State.Running")
+ c.Assert(err, check.IsNil)
if out != "false" {
- t.Fatal("Container should be stopped")
+ c.Fatal("Container should be stopped")
}
- // start all the three containers, container `child_first` start first which should be faild
+ // start all the three containers, container `child_first` start first which should be failed
// container 'parent' start second and then start container 'child_second'
cmd = exec.Command(dockerBinary, "start", "child_first", "parent", "child_second")
out, _, err = runCommandWithOutput(cmd)
if !strings.Contains(out, "Cannot start container child_first") || err == nil {
- t.Fatal("Expected error but got none")
+ c.Fatal("Expected error but got none")
}
for container, expected := range map[string]string{"parent": "true", "child_first": "false", "child_second": "true"} {
- cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container)
- out, _, err = runCommandWithOutput(cmd)
- if err != nil {
- t.Fatal(out, err)
- }
- out = strings.Trim(out, "\r\n")
+ out, err := inspectField(container, "State.Running")
+ c.Assert(err, check.IsNil)
if out != expected {
- t.Fatal("Container running state wrong")
+ c.Fatal("Container running state wrong")
}
}
- logDone("start - start multiple containers continue on one failed")
}
-func TestStartAttachMultipleContainers(t *testing.T) {
+func (s *DockerSuite) TestStartAttachMultipleContainers(c *check.C) {
var cmd *exec.Cmd
- defer deleteAllContainers()
// run multiple containers to test
for _, container := range []string{"test1", "test2", "test3"} {
cmd = exec.Command(dockerBinary, "run", "-d", "--name", container, "busybox", "top")
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
}
@@ -258,7 +205,7 @@ func TestStartAttachMultipleContainers(t *testing.T) {
for _, container := range []string{"test1", "test2", "test3"} {
cmd = exec.Command(dockerBinary, "stop", container)
if out, _, err := runCommandWithOutput(cmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
}
@@ -267,22 +214,19 @@ func TestStartAttachMultipleContainers(t *testing.T) {
cmd = exec.Command(dockerBinary, "start", option, "test1", "test2", "test3")
out, _, err := runCommandWithOutput(cmd)
if !strings.Contains(out, "You cannot start and attach multiple containers at once.") || err == nil {
- t.Fatal("Expected error but got none")
+ c.Fatal("Expected error but got none")
}
}
// confirm the state of all the containers be stopped
for container, expected := range map[string]string{"test1": "false", "test2": "false", "test3": "false"} {
- cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container)
- out, _, err := runCommandWithOutput(cmd)
+ out, err := inspectField(container, "State.Running")
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- out = strings.Trim(out, "\r\n")
if out != expected {
- t.Fatal("Container running state wrong")
+ c.Fatal("Container running state wrong")
}
}
- logDone("start - error on start and attach multiple containers at once")
}
diff --git a/integration-cli/docker_cli_start_volume_driver_unix_test.go b/integration-cli/docker_cli_start_volume_driver_unix_test.go
new file mode 100644
index 000000000..1cc90801d
--- /dev/null
+++ b/integration-cli/docker_cli_start_volume_driver_unix_test.go
@@ -0,0 +1,249 @@
+// +build experimental
+// +build !windows
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func init() {
+ check.Suite(&DockerExternalVolumeSuite{
+ ds: &DockerSuite{},
+ })
+}
+
+type eventCounter struct {
+ activations int
+ creations int
+ removals int
+ mounts int
+ unmounts int
+ paths int
+}
+
+type DockerExternalVolumeSuite struct {
+ server *httptest.Server
+ ds *DockerSuite
+ d *Daemon
+ ec *eventCounter
+}
+
+func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
+ s.d = NewDaemon(c)
+ s.ds.SetUpTest(c)
+ s.ec = &eventCounter{}
+
+}
+
+func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
+ s.d.Stop()
+ s.ds.TearDownTest(c)
+}
+
+func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
+ mux := http.NewServeMux()
+ s.server = httptest.NewServer(mux)
+
+ type pluginRequest struct {
+ name string
+ }
+
+ mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.activations++
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{"Implements": ["VolumeDriver"]}`)
+ })
+
+ mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.creations++
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{}`)
+ })
+
+ mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.removals++
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{}`)
+ })
+
+ mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.paths++
+
+ var pr pluginRequest
+ if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ p := hostVolumePath(pr.name)
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
+ })
+
+ mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.mounts++
+
+ var pr pluginRequest
+ if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ p := hostVolumePath(pr.name)
+ if err := os.MkdirAll(p, 0755); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
+ })
+
+ mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.unmounts++
+
+ var pr pluginRequest
+ if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ p := hostVolumePath(pr.name)
+ if err := os.RemoveAll(p); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{}`)
+ })
+
+ if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
+ c.Fatal(err)
+ }
+
+ if err := ioutil.WriteFile("/usr/share/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644); err != nil {
+ c.Fatal(err)
+ }
+}
+
+func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
+ s.server.Close()
+
+ if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
+ c.Fatal(err)
+ }
+}
+
+func (s *DockerExternalVolumeSuite) TestStartExternalNamedVolumeDriver(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ if !strings.Contains(out, s.server.URL) {
+ c.Fatalf("External volume mount failed. Output: %s\n", out)
+ }
+
+ p := hostVolumePath("external-volume-test")
+ _, err = os.Lstat(p)
+ if err == nil {
+ c.Fatalf("Expected error checking volume path in host: %s\n", p)
+ }
+
+ if !os.IsNotExist(err) {
+ c.Fatalf("Expected volume path in host to not exist: %s, %v\n", p, err)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 1)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 1)
+ c.Assert(s.ec.unmounts, check.Equals, 1)
+}
+
+func (s *DockerExternalVolumeSuite) TestStartExternalVolumeUnnamedDriver(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ if !strings.Contains(out, s.server.URL) {
+ c.Fatalf("External volume mount failed. Output: %s\n", out)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 1)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 1)
+ c.Assert(s.ec.unmounts, check.Equals, 1)
+}
+
+func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverVolumesFrom(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp"); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("rm", "-f", "vol-test1"); err != nil {
+ c.Fatal(err)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 2)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 2)
+ c.Assert(s.ec.unmounts, check.Equals, 2)
+}
+
+func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverDeleteContainer(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
+ c.Fatal(err)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 1)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 1)
+ c.Assert(s.ec.unmounts, check.Equals, 1)
+}
+
+func hostVolumePath(name string) string {
+ return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
+}
diff --git a/integration-cli/docker_cli_stats_test.go b/integration-cli/docker_cli_stats_test.go
new file mode 100644
index 000000000..7664de597
--- /dev/null
+++ b/integration-cli/docker_cli_stats_test.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestCliStatsNoStream(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "top"))
+ if err != nil {
+ c.Fatalf("Error on container creation: %v, output: %s", err, out)
+ }
+ id := strings.TrimSpace(out)
+ if err := waitRun(id); err != nil {
+ c.Fatalf("error waiting for container to start: %v", err)
+ }
+
+ statsCmd := exec.Command(dockerBinary, "stats", "--no-stream", id)
+ chErr := make(chan error)
+ go func() {
+ chErr <- statsCmd.Run()
+ }()
+
+ select {
+ case err := <-chErr:
+ if err != nil {
+ c.Fatalf("Error running stats: %v", err)
+ }
+ case <-time.After(2 * time.Second):
+ statsCmd.Process.Kill()
+ c.Fatalf("stats did not return immediately when not streaming")
+ }
+}
diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go
index 081e239c3..7db21a6f3 100644
--- a/integration-cli/docker_cli_tag_test.go
+++ b/integration-cli/docker_cli_tag_test.go
@@ -1,49 +1,37 @@
package main
import (
- "fmt"
"os/exec"
"strings"
- "testing"
+
+ "github.com/docker/docker/pkg/stringutils"
+ "github.com/go-check/check"
)
// tagging a named image in a new unprefixed repo should work
-func TestTagUnprefixedRepoByName(t *testing.T) {
+func (s *DockerSuite) TestTagUnprefixedRepoByName(c *check.C) {
if err := pullImageIfNotExist("busybox:latest"); err != nil {
- t.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
}
tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "testfoobarbaz")
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
-
- deleteImages("testfoobarbaz")
-
- logDone("tag - busybox -> testfoobarbaz")
}
// tagging an image by ID in a new unprefixed repo should work
-func TestTagUnprefixedRepoByID(t *testing.T) {
- getIDCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox")
- out, _, err := runCommandWithOutput(getIDCmd)
- if err != nil {
- t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err)
+func (s *DockerSuite) TestTagUnprefixedRepoByID(c *check.C) {
+ imageID, err := inspectField("busybox", "Id")
+ c.Assert(err, check.IsNil)
+ tagCmd := exec.Command(dockerBinary, "tag", imageID, "testfoobarbaz")
+ if out, _, err := runCommandWithOutput(tagCmd); err != nil {
+ c.Fatal(out, err)
}
-
- cleanedImageID := stripTrailingCharacters(out)
- tagCmd := exec.Command(dockerBinary, "tag", cleanedImageID, "testfoobarbaz")
- if out, _, err = runCommandWithOutput(tagCmd); err != nil {
- t.Fatal(out, err)
- }
-
- deleteImages("testfoobarbaz")
-
- logDone("tag - busybox's image ID -> testfoobarbaz")
}
// ensure we don't allow the use of invalid repository names; these tag operations should fail
-func TestTagInvalidUnprefixedRepo(t *testing.T) {
+func (s *DockerSuite) TestTagInvalidUnprefixedRepo(c *check.C) {
invalidRepos := []string{"fo$z$", "Foo@3cc", "Foo$3", "Foo*3", "Fo^3", "Foo!3", "F)xcz(", "fo%asd"}
@@ -51,32 +39,30 @@ func TestTagInvalidUnprefixedRepo(t *testing.T) {
tagCmd := exec.Command(dockerBinary, "tag", "busybox", repo)
_, _, err := runCommandWithOutput(tagCmd)
if err == nil {
- t.Fatalf("tag busybox %v should have failed", repo)
+ c.Fatalf("tag busybox %v should have failed", repo)
}
}
- logDone("tag - busybox invalid repo names --> must not work")
}
// ensure we don't allow the use of invalid tags; these tag operations should fail
-func TestTagInvalidPrefixedRepo(t *testing.T) {
- long_tag := makeRandomString(121)
+func (s *DockerSuite) TestTagInvalidPrefixedRepo(c *check.C) {
+ longTag := stringutils.GenerateRandomAlphaOnlyString(121)
- invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", long_tag}
+ invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", longTag}
for _, repotag := range invalidTags {
tagCmd := exec.Command(dockerBinary, "tag", "busybox", repotag)
_, _, err := runCommandWithOutput(tagCmd)
if err == nil {
- t.Fatalf("tag busybox %v should have failed", repotag)
+ c.Fatalf("tag busybox %v should have failed", repotag)
}
}
- logDone("tag - busybox with invalid repo:tagnames --> must not work")
}
// ensure we allow the use of valid tags
-func TestTagValidPrefixedRepo(t *testing.T) {
+func (s *DockerSuite) TestTagValidPrefixedRepo(c *check.C) {
if err := pullImageIfNotExist("busybox:latest"); err != nil {
- t.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
}
validRepos := []string{"fooo/bar", "fooaa/test", "foooo:t"}
@@ -85,57 +71,73 @@ func TestTagValidPrefixedRepo(t *testing.T) {
tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", repo)
_, _, err := runCommandWithOutput(tagCmd)
if err != nil {
- t.Errorf("tag busybox %v should have worked: %s", repo, err)
+ c.Errorf("tag busybox %v should have worked: %s", repo, err)
continue
}
deleteImages(repo)
- logMessage := fmt.Sprintf("tag - busybox %v", repo)
- logDone(logMessage)
}
}
// tag an image with an existed tag name without -f option should fail
-func TestTagExistedNameWithoutForce(t *testing.T) {
+func (s *DockerSuite) TestTagExistedNameWithoutForce(c *check.C) {
if err := pullImageIfNotExist("busybox:latest"); err != nil {
- t.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
}
tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test")
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
tagCmd = exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test")
out, _, err := runCommandWithOutput(tagCmd)
if err == nil || !strings.Contains(out, "Conflict: Tag test is already set to image") {
- t.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed")
+ c.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed")
}
- deleteImages("busybox:test")
-
- logDone("tag - busybox with an existed tag name without -f option --> must not work")
}
// tag an image with an existed tag name with -f option should work
-func TestTagExistedNameWithForce(t *testing.T) {
+func (s *DockerSuite) TestTagExistedNameWithForce(c *check.C) {
if err := pullImageIfNotExist("busybox:latest"); err != nil {
- t.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
}
tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test")
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
tagCmd = exec.Command(dockerBinary, "tag", "-f", "busybox:latest", "busybox:test")
if out, _, err := runCommandWithOutput(tagCmd); err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- deleteImages("busybox:test")
+}
- logDone("tag - busybox with an existed tag name with -f option work")
+func (s *DockerSuite) TestTagWithSuffixHyphen(c *check.C) {
+ if err := pullImageIfNotExist("busybox:latest"); err != nil {
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
+ }
+ // test repository name begin with '-'
+ tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "-busybox:test")
+ out, _, err := runCommandWithOutput(tagCmd)
+ if err == nil || !strings.Contains(out, "Invalid repository name (-busybox). Cannot begin or end with a hyphen") {
+ c.Fatal("tag a name begin with '-' should failed")
+ }
+ // test namespace name begin with '-'
+ tagCmd = exec.Command(dockerBinary, "tag", "busybox:latest", "-test/busybox:test")
+ out, _, err = runCommandWithOutput(tagCmd)
+ if err == nil || !strings.Contains(out, "Invalid namespace name (-test). Cannot begin or end with a hyphen") {
+ c.Fatal("tag a name begin with '-' should failed")
+ }
+ // test index name begin wiht '-'
+ tagCmd = exec.Command(dockerBinary, "tag", "busybox:latest", "-index:5000/busybox:test")
+ out, _, err = runCommandWithOutput(tagCmd)
+ if err == nil || !strings.Contains(out, "Invalid index name (-index:5000). Cannot begin or end with a hyphen") {
+ c.Fatal("tag a name begin with '-' should failed")
+ }
}
// ensure tagging using official names works
// ensure all tags result in the same name
-func TestTagOfficialNames(t *testing.T) {
+func (s *DockerSuite) TestTagOfficialNames(c *check.C) {
names := []string{
"docker.io/busybox",
"index.docker.io/busybox",
@@ -148,7 +150,7 @@ func TestTagOfficialNames(t *testing.T) {
tagCmd := exec.Command(dockerBinary, "tag", "-f", "busybox:latest", name+":latest")
out, exitCode, err := runCommandWithOutput(tagCmd)
if err != nil || exitCode != 0 {
- t.Errorf("tag busybox %v should have worked: %s, %s", name, err, out)
+ c.Errorf("tag busybox %v should have worked: %s, %s", name, err, out)
continue
}
@@ -156,13 +158,10 @@ func TestTagOfficialNames(t *testing.T) {
imagesCmd := exec.Command(dockerBinary, "images")
out, _, err = runCommandWithOutput(imagesCmd)
if err != nil {
- t.Errorf("listing images failed with errors: %v, %s", err, out)
+ c.Errorf("listing images failed with errors: %v, %s", err, out)
} else if strings.Contains(out, name) {
- t.Errorf("images should not have listed '%s'", name)
+ c.Errorf("images should not have listed '%s'", name)
deleteImages(name + ":latest")
- } else {
- logMessage := fmt.Sprintf("tag official name - busybox %v", name)
- logDone(logMessage)
}
}
@@ -170,11 +169,9 @@ func TestTagOfficialNames(t *testing.T) {
tagCmd := exec.Command(dockerBinary, "tag", "-f", name+":latest", "fooo/bar:latest")
_, exitCode, err := runCommandWithOutput(tagCmd)
if err != nil || exitCode != 0 {
- t.Errorf("tag %v fooo/bar should have worked: %s", name, err)
+ c.Errorf("tag %v fooo/bar should have worked: %s", name, err)
continue
}
deleteImages("fooo/bar:latest")
- logMessage := fmt.Sprintf("tag official name - %v fooo/bar", name)
- logDone(logMessage)
}
}
diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go
index de0d3d2e8..f28e20ba3 100644
--- a/integration-cli/docker_cli_top_test.go
+++ b/integration-cli/docker_cli_top_test.go
@@ -3,106 +3,99 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestTopMultipleArgs(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "sleep", "20")
+func (s *DockerSuite) TestTopMultipleArgs(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to start the container: %s, %v", out, err)
+ c.Fatalf("failed to start the container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
topCmd := exec.Command(dockerBinary, "top", cleanedContainerID, "-o", "pid")
out, _, err = runCommandWithOutput(topCmd)
if err != nil {
- t.Fatalf("failed to run top: %s, %v", out, err)
+ c.Fatalf("failed to run top: %s, %v", out, err)
}
if !strings.Contains(out, "PID") {
- t.Fatalf("did not see PID after top -o pid: %s", out)
+ c.Fatalf("did not see PID after top -o pid: %s", out)
}
- logDone("top - multiple arguments")
}
-func TestTopNonPrivileged(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "sleep", "20")
+func (s *DockerSuite) TestTopNonPrivileged(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to start the container: %s, %v", out, err)
+ c.Fatalf("failed to start the container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
topCmd := exec.Command(dockerBinary, "top", cleanedContainerID)
out1, _, err := runCommandWithOutput(topCmd)
if err != nil {
- t.Fatalf("failed to run top: %s, %v", out1, err)
+ c.Fatalf("failed to run top: %s, %v", out1, err)
}
topCmd = exec.Command(dockerBinary, "top", cleanedContainerID)
out2, _, err := runCommandWithOutput(topCmd)
if err != nil {
- t.Fatalf("failed to run top: %s, %v", out2, err)
+ c.Fatalf("failed to run top: %s, %v", out2, err)
}
killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID)
if out, _, err = runCommandWithOutput(killCmd); err != nil {
- t.Fatalf("failed to kill container: %s, %v", out, err)
+ c.Fatalf("failed to kill container: %s, %v", out, err)
}
- deleteContainer(cleanedContainerID)
-
- if !strings.Contains(out1, "sleep 20") && !strings.Contains(out2, "sleep 20") {
- t.Fatal("top should've listed `sleep 20` in the process list, but failed twice")
- } else if !strings.Contains(out1, "sleep 20") {
- t.Fatal("top should've listed `sleep 20` in the process list, but failed the first time")
- } else if !strings.Contains(out2, "sleep 20") {
- t.Fatal("top should've listed `sleep 20` in the process list, but failed the second itime")
+ if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed twice")
+ } else if !strings.Contains(out1, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed the first time")
+ } else if !strings.Contains(out2, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed the second itime")
}
- logDone("top - sleep process should be listed in non privileged mode")
}
-func TestTopPrivileged(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "--privileged", "-i", "-d", "busybox", "sleep", "20")
+func (s *DockerSuite) TestTopPrivileged(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "--privileged", "-i", "-d", "busybox", "top")
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf("failed to start the container: %s, %v", out, err)
+ c.Fatalf("failed to start the container: %s, %v", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
topCmd := exec.Command(dockerBinary, "top", cleanedContainerID)
out1, _, err := runCommandWithOutput(topCmd)
if err != nil {
- t.Fatalf("failed to run top: %s, %v", out1, err)
+ c.Fatalf("failed to run top: %s, %v", out1, err)
}
topCmd = exec.Command(dockerBinary, "top", cleanedContainerID)
out2, _, err := runCommandWithOutput(topCmd)
if err != nil {
- t.Fatalf("failed to run top: %s, %v", out2, err)
+ c.Fatalf("failed to run top: %s, %v", out2, err)
}
killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID)
if out, _, err = runCommandWithOutput(killCmd); err != nil {
- t.Fatalf("failed to kill container: %s, %v", out, err)
+ c.Fatalf("failed to kill container: %s, %v", out, err)
}
- deleteContainer(cleanedContainerID)
-
- if !strings.Contains(out1, "sleep 20") && !strings.Contains(out2, "sleep 20") {
- t.Fatal("top should've listed `sleep 20` in the process list, but failed twice")
- } else if !strings.Contains(out1, "sleep 20") {
- t.Fatal("top should've listed `sleep 20` in the process list, but failed the first time")
- } else if !strings.Contains(out2, "sleep 20") {
- t.Fatal("top should've listed `sleep 20` in the process list, but failed the second itime")
+ if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed twice")
+ } else if !strings.Contains(out1, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed the first time")
+ } else if !strings.Contains(out2, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed the second itime")
}
- logDone("top - sleep process should be listed in privileged mode")
}
diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go
index ceaeba8e2..3616da988 100644
--- a/integration-cli/docker_cli_version_test.go
+++ b/integration-cli/docker_cli_version_test.go
@@ -3,15 +3,16 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
// ensure docker version works
-func TestVersionEnsureSucceeds(t *testing.T) {
+func (s *DockerSuite) TestVersionEnsureSucceeds(c *check.C) {
versionCmd := exec.Command(dockerBinary, "version")
out, _, err := runCommandWithOutput(versionCmd)
if err != nil {
- t.Fatalf("failed to execute docker version: %s, %v", out, err)
+ c.Fatalf("failed to execute docker version: %s, %v", out, err)
}
stringsToCheck := []string{
@@ -29,9 +30,8 @@ func TestVersionEnsureSucceeds(t *testing.T) {
for _, linePrefix := range stringsToCheck {
if !strings.Contains(out, linePrefix) {
- t.Errorf("couldn't find string %v in output", linePrefix)
+ c.Errorf("couldn't find string %v in output", linePrefix)
}
}
- logDone("version - verify that it works and that the output is properly formatted")
}
diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go
new file mode 100644
index 000000000..c6d469eed
--- /dev/null
+++ b/integration-cli/docker_cli_wait_test.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ "bytes"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/go-check/check"
+)
+
+// non-blocking wait with 0 exit code
+func (s *DockerSuite) TestWaitNonBlockedExitZero(c *check.C) {
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "true")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ containerID := strings.TrimSpace(out)
+
+ status := "true"
+ for i := 0; status != "false"; i++ {
+ status, err = inspectField(containerID, "State.Running")
+ c.Assert(err, check.IsNil)
+
+ time.Sleep(time.Second)
+ if i >= 60 {
+ c.Fatal("Container should have stopped by now")
+ }
+ }
+
+ runCmd = exec.Command(dockerBinary, "wait", containerID)
+ out, _, err = runCommandWithOutput(runCmd)
+
+ if err != nil || strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out, err)
+ }
+
+}
+
+// blocking wait with 0 exit code
+func (s *DockerSuite) TestWaitBlockedExitZero(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "trap 'exit 0' TERM; while true; do sleep 0.01; done")
+ containerID := strings.TrimSpace(out)
+
+ if err := waitRun(containerID); err != nil {
+ c.Fatal(err)
+ }
+
+ chWait := make(chan string)
+ go func() {
+ out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "wait", containerID))
+ chWait <- out
+ }()
+
+ time.Sleep(100 * time.Millisecond)
+ dockerCmd(c, "stop", containerID)
+
+ select {
+ case status := <-chWait:
+ if strings.TrimSpace(status) != "0" {
+ c.Fatalf("expected exit 0, got %s", status)
+ }
+ case <-time.After(2 * time.Second):
+ c.Fatal("timeout waiting for `docker wait` to exit")
+ }
+
+}
+
+// non-blocking wait with random exit code
+func (s *DockerSuite) TestWaitNonBlockedExitRandom(c *check.C) {
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "exit 99")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ containerID := strings.TrimSpace(out)
+
+ status := "true"
+ for i := 0; status != "false"; i++ {
+ status, err = inspectField(containerID, "State.Running")
+ c.Assert(err, check.IsNil)
+
+ time.Sleep(time.Second)
+ if i >= 60 {
+ c.Fatal("Container should have stopped by now")
+ }
+ }
+
+ runCmd = exec.Command(dockerBinary, "wait", containerID)
+ out, _, err = runCommandWithOutput(runCmd)
+
+ if err != nil || strings.TrimSpace(out) != "99" {
+ c.Fatal("failed to set up container", out, err)
+ }
+
+}
+
+// blocking wait with random exit code
+func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "trap 'exit 99' TERM; while true; do sleep 0.01; done")
+ containerID := strings.TrimSpace(out)
+ if err := waitRun(containerID); err != nil {
+ c.Fatal(err)
+ }
+ if err := waitRun(containerID); err != nil {
+ c.Fatal(err)
+ }
+
+ chWait := make(chan error)
+ waitCmd := exec.Command(dockerBinary, "wait", containerID)
+ waitCmdOut := bytes.NewBuffer(nil)
+ waitCmd.Stdout = waitCmdOut
+ if err := waitCmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+
+ go func() {
+ chWait <- waitCmd.Wait()
+ }()
+
+ dockerCmd(c, "stop", containerID)
+
+ select {
+ case err := <-chWait:
+ if err != nil {
+ c.Fatal(err)
+ }
+ status, err := waitCmdOut.ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(status) != "99" {
+ c.Fatalf("expected exit 99, got %s", status)
+ }
+ case <-time.After(2 * time.Second):
+ waitCmd.Process.Kill()
+ c.Fatal("timeout waiting for `docker wait` to exit")
+ }
+}
diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go
index 9cb28b274..ed394d26d 100644
--- a/integration-cli/docker_test_vars.go
+++ b/integration-cli/docker_test_vars.go
@@ -18,7 +18,6 @@ var (
dockerBasePath = "/var/lib/docker"
volumesConfigPath = dockerBasePath + "/volumes"
- volumesStoragePath = dockerBasePath + "/vfs/dir"
containerStoragePath = dockerBasePath + "/containers"
runtimePath = "/var/run/docker"
diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go
index 10cc6c921..e9c0df6eb 100644
--- a/integration-cli/docker_utils.go
+++ b/integration-cli/docker_utils.go
@@ -18,15 +18,17 @@ import (
"path/filepath"
"strconv"
"strings"
- "testing"
"time"
- "github.com/docker/docker/api"
+ "github.com/docker/docker/opts"
+ "github.com/docker/docker/pkg/ioutils"
+ "github.com/docker/docker/pkg/stringutils"
+ "github.com/go-check/check"
)
// Daemon represents a Docker daemon for the testing framework.
type Daemon struct {
- t *testing.T
+ c *check.C
logFile *os.File
folder string
stdin io.WriteCloser
@@ -35,32 +37,50 @@ type Daemon struct {
storageDriver string
execDriver string
wait chan error
+ userlandProxy bool
+}
+
+func enableUserlandProxy() bool {
+ if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
+ if val, err := strconv.ParseBool(env); err != nil {
+ return val
+ }
+ }
+ return true
}
// NewDaemon returns a Daemon instance to be used for testing.
// This will create a directory such as daemon123456789 in the folder specified by $DEST.
// The daemon will not automatically start.
-func NewDaemon(t *testing.T) *Daemon {
+func NewDaemon(c *check.C) *Daemon {
dest := os.Getenv("DEST")
if dest == "" {
- t.Fatal("Please set the DEST environment variable")
+ c.Fatal("Please set the DEST environment variable")
}
- dir := filepath.Join(dest, fmt.Sprintf("daemon%d", time.Now().UnixNano()%100000000))
+ dir := filepath.Join(dest, fmt.Sprintf("d%d", time.Now().UnixNano()%100000000))
daemonFolder, err := filepath.Abs(dir)
if err != nil {
- t.Fatalf("Could not make %q an absolute path: %v", dir, err)
+ c.Fatalf("Could not make %q an absolute path: %v", dir, err)
}
if err := os.MkdirAll(filepath.Join(daemonFolder, "graph"), 0600); err != nil {
- t.Fatalf("Could not create %s/graph directory", daemonFolder)
+ c.Fatalf("Could not create %s/graph directory", daemonFolder)
+ }
+
+ userlandProxy := true
+ if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
+ if val, err := strconv.ParseBool(env); err != nil {
+ userlandProxy = val
+ }
}
return &Daemon{
- t: t,
+ c: c,
folder: daemonFolder,
storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
execDriver: os.Getenv("DOCKER_EXECDRIVER"),
+ userlandProxy: userlandProxy,
}
}
@@ -69,7 +89,7 @@ func NewDaemon(t *testing.T) *Daemon {
func (d *Daemon) Start(arg ...string) error {
dockerBinary, err := exec.LookPath(dockerBinary)
if err != nil {
- d.t.Fatalf("could not find docker binary in $PATH: %v", err)
+ d.c.Fatalf("could not find docker binary in $PATH: %v", err)
}
args := []string{
@@ -77,6 +97,7 @@ func (d *Daemon) Start(arg ...string) error {
"--daemon",
"--graph", fmt.Sprintf("%s/graph", d.folder),
"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
+ fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
}
// If we don't explicitly set the log-level or debug flag(-D) then
@@ -103,7 +124,7 @@ func (d *Daemon) Start(arg ...string) error {
d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
- d.t.Fatalf("Could not create %s/docker.log: %v", d.folder, err)
+ d.c.Fatalf("Could not create %s/docker.log: %v", d.folder, err)
}
d.cmd.Stdout = d.logFile
@@ -117,7 +138,7 @@ func (d *Daemon) Start(arg ...string) error {
go func() {
wait <- d.cmd.Wait()
- d.t.Log("exiting daemon")
+ d.c.Log("exiting daemon")
close(wait)
}()
@@ -127,7 +148,7 @@ func (d *Daemon) Start(arg ...string) error {
// make sure daemon is ready to receive requests
startTime := time.Now().Unix()
for {
- d.t.Log("waiting for daemon to start")
+ d.c.Log("waiting for daemon to start")
if time.Now().Unix()-startTime > 5 {
// After 5 seconds, give up
return errors.New("Daemon exited and never started")
@@ -146,7 +167,7 @@ func (d *Daemon) Start(arg ...string) error {
req, err := http.NewRequest("GET", "/_ping", nil)
if err != nil {
- d.t.Fatalf("could not create new request: %v", err)
+ d.c.Fatalf("could not create new request: %v", err)
}
resp, err := client.Do(req)
@@ -154,10 +175,10 @@ func (d *Daemon) Start(arg ...string) error {
continue
}
if resp.StatusCode != http.StatusOK {
- d.t.Logf("received status != 200 OK: %s", resp.Status)
+ d.c.Logf("received status != 200 OK: %s", resp.Status)
}
- d.t.Log("daemon started")
+ d.c.Log("daemon started")
return nil
}
}
@@ -184,7 +205,7 @@ func (d *Daemon) StartWithBusybox(arg ...string) error {
return fmt.Errorf("could not load busybox image: %v", err)
}
if err := os.Remove(bb); err != nil {
- d.t.Logf("Could not remove %s: %v", bb, err)
+ d.c.Logf("Could not remove %s: %v", bb, err)
}
return nil
}
@@ -216,7 +237,7 @@ out1:
return err
case <-time.After(15 * time.Second):
// time for stopping jobs and run onShutdown hooks
- d.t.Log("timeout")
+ d.c.Log("timeout")
break out1
}
}
@@ -229,10 +250,10 @@ out2:
case <-tick:
i++
if i > 4 {
- d.t.Logf("tried to interrupt daemon for %d times, now try to kill it", i)
+ d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i)
break out2
}
- d.t.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid)
+ d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid)
if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
return fmt.Errorf("could not send signal: %v", err)
}
@@ -240,7 +261,7 @@ out2:
}
if err := d.cmd.Process.Kill(); err != nil {
- d.t.Logf("Could not kill daemon: %v", err)
+ d.c.Logf("Could not kill daemon: %v", err)
return err
}
@@ -267,12 +288,20 @@ func (d *Daemon) Cmd(name string, arg ...string) (string, error) {
return string(b), err
}
+func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) {
+ args := append(daemonArgs, name)
+ args = append(args, arg...)
+ c := exec.Command(dockerBinary, args...)
+ b, err := c.CombinedOutput()
+ return string(b), err
+}
+
func (d *Daemon) LogfileName() string {
return d.logFile.Name()
}
func daemonHost() string {
- daemonUrlStr := "unix://" + api.DEFAULTUNIXSOCKET
+ daemonUrlStr := "unix://" + opts.DefaultUnixSocket
if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
daemonUrlStr = daemonHostVar
}
@@ -297,58 +326,62 @@ func sockConn(timeout time.Duration) (net.Conn, error) {
}
}
-func sockRequest(method, endpoint string, data interface{}) ([]byte, error) {
+func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) {
jsonData := bytes.NewBuffer(nil)
if err := json.NewEncoder(jsonData).Encode(data); err != nil {
- return nil, err
+ return -1, nil, err
}
- return sockRequestRaw(method, endpoint, jsonData, "application/json")
+ res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json")
+ if err != nil {
+ b, _ := ioutil.ReadAll(body)
+ return -1, b, err
+ }
+ var b []byte
+ b, err = readBody(body)
+ return res.StatusCode, b, err
}
-func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, error) {
+func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) {
c, err := sockConn(time.Duration(10 * time.Second))
if err != nil {
- return nil, fmt.Errorf("could not dial docker daemon: %v", err)
+ return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err)
}
client := httputil.NewClientConn(c, nil)
- defer client.Close()
req, err := http.NewRequest(method, endpoint, data)
if err != nil {
- return nil, fmt.Errorf("could not create new request: %v", err)
+ client.Close()
+ return nil, nil, fmt.Errorf("could not create new request: %v", err)
}
- if ct == "" {
- ct = "application/json"
+ if ct != "" {
+ req.Header.Set("Content-Type", ct)
}
- req.Header.Set("Content-Type", ct)
resp, err := client.Do(req)
if err != nil {
- return nil, fmt.Errorf("could not perform request: %v", err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- body, _ := ioutil.ReadAll(resp.Body)
- return body, fmt.Errorf("received status != 200 OK: %s", resp.Status)
+ client.Close()
+ return nil, nil, fmt.Errorf("could not perform request: %v", err)
}
+ body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
+ defer client.Close()
+ return resp.Body.Close()
+ })
- return ioutil.ReadAll(resp.Body)
+ return resp, body, nil
+}
+
+func readBody(b io.ReadCloser) ([]byte, error) {
+ defer b.Close()
+ return ioutil.ReadAll(b)
}
func deleteContainer(container string) error {
- container = strings.Replace(container, "\n", " ", -1)
- container = strings.Trim(container, " ")
- killArgs := fmt.Sprintf("kill %v", container)
- killSplitArgs := strings.Split(killArgs, " ")
- killCmd := exec.Command(dockerBinary, killSplitArgs...)
- runCommand(killCmd)
- rmArgs := fmt.Sprintf("rm -v %v", container)
- rmSplitArgs := strings.Split(rmArgs, " ")
- rmCmd := exec.Command(dockerBinary, rmSplitArgs...)
- exitCode, err := runCommand(rmCmd)
+ container = strings.TrimSpace(strings.Replace(container, "\n", " ", -1))
+ rmArgs := strings.Split(fmt.Sprintf("rm -fv %v", container), " ")
+ exitCode, err := runCommand(exec.Command(dockerBinary, rmArgs...))
// set error manually if not set
if exitCode != 0 && err == nil {
err = fmt.Errorf("failed to remove container: `docker rm` exit is non-zero")
@@ -380,6 +413,58 @@ func deleteAllContainers() error {
return nil
}
+var protectedImages = map[string]struct{}{}
+
+func init() {
+ out, err := exec.Command(dockerBinary, "images").CombinedOutput()
+ if err != nil {
+ panic(err)
+ }
+ lines := strings.Split(string(out), "\n")[1:]
+ for _, l := range lines {
+ if l == "" {
+ continue
+ }
+ fields := strings.Fields(l)
+ imgTag := fields[0] + ":" + fields[1]
+ // just for case if we have dangling images in tested daemon
+ if imgTag != ":" {
+ protectedImages[imgTag] = struct{}{}
+ }
+ }
+}
+
+func deleteAllImages() error {
+ out, err := exec.Command(dockerBinary, "images").CombinedOutput()
+ if err != nil {
+ return err
+ }
+ lines := strings.Split(string(out), "\n")[1:]
+ var imgs []string
+ for _, l := range lines {
+ if l == "" {
+ continue
+ }
+ fields := strings.Fields(l)
+ imgTag := fields[0] + ":" + fields[1]
+ if _, ok := protectedImages[imgTag]; !ok {
+ if fields[0] == "" {
+ imgs = append(imgs, fields[2])
+ continue
+ }
+ imgs = append(imgs, imgTag)
+ }
+ }
+ if len(imgs) == 0 {
+ return nil
+ }
+ args := append([]string{"rmi", "-f"}, imgs...)
+ if err := exec.Command(dockerBinary, args...).Run(); err != nil {
+ return err
+ }
+ return nil
+}
+
func getPausedContainers() (string, error) {
getPausedContainersCmd := exec.Command(dockerBinary, "ps", "-f", "status=paused", "-q", "-a")
out, exitCode, err := runCommandWithOutput(getPausedContainersCmd)
@@ -393,11 +478,13 @@ func getPausedContainers() (string, error) {
func getSliceOfPausedContainers() ([]string, error) {
out, err := getPausedContainers()
if err == nil {
+ if len(out) == 0 {
+ return nil, err
+ }
slice := strings.Split(strings.TrimSpace(out), "\n")
return slice, err
- } else {
- return []string{out}, err
}
+ return []string{out}, err
}
func unpauseContainer(container string) error {
@@ -431,8 +518,7 @@ func unpauseAllContainers() error {
}
func deleteImages(images ...string) error {
- args := make([]string, 1, 2)
- args[0] = "rmi"
+ args := []string{"rmi", "-f"}
args = append(args, images...)
rmiCmd := exec.Command(dockerBinary, args...)
exitCode, err := runCommand(rmiCmd)
@@ -440,7 +526,6 @@ func deleteImages(images ...string) error {
if exitCode != 0 && err == nil {
err = fmt.Errorf("failed to remove image: `docker rmi` exit is non-zero")
}
-
return err
}
@@ -465,12 +550,12 @@ func pullImageIfNotExist(image string) (err error) {
return
}
-func dockerCmd(t *testing.T, args ...string) (string, int, error) {
+func dockerCmd(c *check.C, args ...string) (string, int) {
out, status, err := runCommandWithOutput(exec.Command(dockerBinary, args...))
if err != nil {
- t.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err)
+ c.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err)
}
- return out, status, err
+ return out, status
}
// execute a docker command with a timeout
@@ -483,7 +568,7 @@ func dockerCmdWithTimeout(timeout time.Duration, args ...string) (string, int, e
}
// execute a docker command in a directory
-func dockerCmdInDir(t *testing.T, path string, args ...string) (string, int, error) {
+func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error) {
dockerCommand := exec.Command(dockerBinary, args...)
dockerCommand.Dir = path
out, status, err := runCommandWithOutput(dockerCommand)
@@ -504,16 +589,21 @@ func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...strin
return out, status, err
}
-func findContainerIP(t *testing.T, id string) string {
- cmd := exec.Command(dockerBinary, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id)
+func findContainerIP(c *check.C, id string, vargs ...string) string {
+ args := append(vargs, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id)
+ cmd := exec.Command(dockerBinary, args...)
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
return strings.Trim(out, " \r\n'")
}
+func (d *Daemon) findContainerIP(id string) string {
+ return findContainerIP(d.c, id, "--host", d.sock())
+}
+
func getContainerCount() (int, error) {
const containers = "Containers:"
@@ -526,7 +616,7 @@ func getContainerCount() (int, error) {
lines := strings.Split(out, "\n")
for _, line := range lines {
if strings.Contains(line, containers) {
- output := stripTrailingCharacters(line)
+ output := strings.TrimSpace(line)
output = strings.TrimLeft(output, containers)
output = strings.Trim(output, " ")
containerCount, err := strconv.Atoi(output)
@@ -597,7 +687,6 @@ func fakeContextAddDockerfile(ctx *FakeContext, dockerfile string) error {
func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) {
ctx, err := fakeContextWithFiles(files)
if err != nil {
- ctx.Close()
return nil, err
}
if err := fakeContextAddDockerfile(ctx, dockerfile); err != nil {
@@ -696,8 +785,8 @@ func (f *remoteFileServer) Close() error {
func newRemoteFileServer(ctx *FakeContext) (*remoteFileServer, error) {
var (
- image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(makeRandomString(10)))
- container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(makeRandomString(10)))
+ image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10)))
+ container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10)))
)
// Build the image
@@ -766,14 +855,14 @@ func getIDByName(name string) (string, error) {
// getContainerState returns the exit code of the container
// and true if it's running
// the exit code should be ignored if it's running
-func getContainerState(t *testing.T, id string) (int, bool, error) {
+func getContainerState(c *check.C, id string) (int, bool, error) {
var (
exitStatus int
running bool
)
- out, exitCode, err := dockerCmd(t, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id)
- if err != nil || exitCode != 0 {
- return 0, false, fmt.Errorf("%q doesn't exist: %s", id, err)
+ out, exitCode := dockerCmd(c, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id)
+ if exitCode != 0 {
+ return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out)
}
out = strings.Trim(out, "\n")
@@ -972,28 +1061,28 @@ func fakeGIT(name string, files map[string]string, enforceLocalServer bool) (*Fa
// Write `content` to the file at path `dst`, creating it if necessary,
// as well as any missing directories.
// The file is truncated if it already exists.
-// Call t.Fatal() at the first error.
-func writeFile(dst, content string, t *testing.T) {
+// Call c.Fatal() at the first error.
+func writeFile(dst, content string, c *check.C) {
// Create subdirectories if necessary
if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) {
- t.Fatal(err)
+ c.Fatal(err)
}
f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Write content (truncate if it exists)
if _, err := io.Copy(f, strings.NewReader(content)); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
}
// Return the contents of file at path `src`.
-// Call t.Fatal() at the first error (including if the file doesn't exist)
-func readFile(src string, t *testing.T) (content string) {
+// Call c.Fatal() at the first error (including if the file doesn't exist)
+func readFile(src string, c *check.C) (content string) {
data, err := ioutil.ReadFile(src)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
return string(data)
@@ -1038,36 +1127,35 @@ func readContainerFileWithExec(containerId, filename string) ([]byte, error) {
}
// daemonTime provides the current time on the daemon host
-func daemonTime(t *testing.T) time.Time {
+func daemonTime(c *check.C) time.Time {
if isLocalDaemon {
return time.Now()
}
- body, err := sockRequest("GET", "/info", nil)
- if err != nil {
- t.Fatal("daemonTime: failed to get /info: %v", err)
- }
+ status, body, err := sockRequest("GET", "/info", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
type infoJSON struct {
SystemTime string
}
var info infoJSON
if err = json.Unmarshal(body, &info); err != nil {
- t.Fatalf("unable to unmarshal /info response: %v", err)
+ c.Fatalf("unable to unmarshal /info response: %v", err)
}
dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
return dt
}
-func setupRegistry(t *testing.T) func() {
- testRequires(t, RegistryHosting)
- reg, err := newTestRegistryV2(t)
+func setupRegistry(c *check.C) *testRegistryV2 {
+ testRequires(c, RegistryHosting)
+ reg, err := newTestRegistryV2(c)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
// Wait for registry to be ready to serve requests.
@@ -1079,10 +1167,9 @@ func setupRegistry(t *testing.T) func() {
}
if err != nil {
- t.Fatal("Timeout waiting for test registry to become available")
+ c.Fatal("Timeout waiting for test registry to become available")
}
-
- return func() { reg.Close() }
+ return reg
}
// appendBaseEnv appends the minimum set of environment variables to exec the
diff --git a/integration/fixtures/https/ca.pem b/integration-cli/fixtures/https/ca.pem
similarity index 100%
rename from integration/fixtures/https/ca.pem
rename to integration-cli/fixtures/https/ca.pem
diff --git a/integration/fixtures/https/client-cert.pem b/integration-cli/fixtures/https/client-cert.pem
similarity index 100%
rename from integration/fixtures/https/client-cert.pem
rename to integration-cli/fixtures/https/client-cert.pem
diff --git a/integration/fixtures/https/client-key.pem b/integration-cli/fixtures/https/client-key.pem
similarity index 100%
rename from integration/fixtures/https/client-key.pem
rename to integration-cli/fixtures/https/client-key.pem
diff --git a/integration/fixtures/https/client-rogue-cert.pem b/integration-cli/fixtures/https/client-rogue-cert.pem
similarity index 100%
rename from integration/fixtures/https/client-rogue-cert.pem
rename to integration-cli/fixtures/https/client-rogue-cert.pem
diff --git a/integration/fixtures/https/client-rogue-key.pem b/integration-cli/fixtures/https/client-rogue-key.pem
similarity index 100%
rename from integration/fixtures/https/client-rogue-key.pem
rename to integration-cli/fixtures/https/client-rogue-key.pem
diff --git a/integration/fixtures/https/server-cert.pem b/integration-cli/fixtures/https/server-cert.pem
similarity index 100%
rename from integration/fixtures/https/server-cert.pem
rename to integration-cli/fixtures/https/server-cert.pem
diff --git a/integration/fixtures/https/server-key.pem b/integration-cli/fixtures/https/server-key.pem
similarity index 100%
rename from integration/fixtures/https/server-key.pem
rename to integration-cli/fixtures/https/server-key.pem
diff --git a/integration/fixtures/https/server-rogue-cert.pem b/integration-cli/fixtures/https/server-rogue-cert.pem
similarity index 100%
rename from integration/fixtures/https/server-rogue-cert.pem
rename to integration-cli/fixtures/https/server-rogue-cert.pem
diff --git a/integration/fixtures/https/server-rogue-key.pem b/integration-cli/fixtures/https/server-rogue-key.pem
similarity index 100%
rename from integration/fixtures/https/server-rogue-key.pem
rename to integration-cli/fixtures/https/server-rogue-key.pem
diff --git a/integration-cli/registry.go b/integration-cli/registry.go
index 8290e710f..2801eacb5 100644
--- a/integration-cli/registry.go
+++ b/integration-cli/registry.go
@@ -7,7 +7,8 @@ import (
"os"
"os/exec"
"path/filepath"
- "testing"
+
+ "github.com/go-check/check"
)
const v2binary = "registry-v2"
@@ -17,7 +18,7 @@ type testRegistryV2 struct {
dir string
}
-func newTestRegistryV2(t *testing.T) (*testRegistryV2, error) {
+func newTestRegistryV2(c *check.C) (*testRegistryV2, error) {
template := `version: 0.1
loglevel: debug
storage:
@@ -43,7 +44,7 @@ http:
if err := cmd.Start(); err != nil {
os.RemoveAll(tmp)
if os.IsNotExist(err) {
- t.Skip()
+ c.Skip(err.Error())
}
return nil, err
}
diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go
index 346d0cdf6..fc4f5ee95 100644
--- a/integration-cli/requirements.go
+++ b/integration-cli/requirements.go
@@ -3,10 +3,13 @@ package main
import (
"encoding/json"
"fmt"
+ "io/ioutil"
"log"
+ "net/http"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
type TestCondition func() bool
@@ -32,6 +35,23 @@ var (
func() bool { return supportsExec },
"Test requires 'docker exec' capabilities on the tested daemon.",
}
+ Network = TestRequirement{
+ func() bool {
+ resp, err := http.Get("http://hub.docker.com")
+ if resp != nil {
+ resp.Body.Close()
+ }
+ return err == nil
+ },
+ "Test requires network availability, environment variable set to none to run in a non-network enabled mode.",
+ }
+ Apparmor = TestRequirement{
+ func() bool {
+ buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
+ return err == nil && len(buf) > 1 && buf[0] == 'Y'
+ },
+ "Test requires apparmor is enabled.",
+ }
RegistryHosting = TestRequirement{
func() bool {
// for now registry binary is built only if we're running inside
@@ -46,8 +66,8 @@ var (
func() bool {
if daemonExecDriver == "" {
// get daemon info
- body, err := sockRequest("GET", "/info", nil)
- if err != nil {
+ status, body, err := sockRequest("GET", "/info", nil)
+ if err != nil || status != http.StatusOK {
log.Fatalf("sockRequest failed for /info: %v", err)
}
@@ -66,7 +86,6 @@ var (
},
"Test requires the native (libcontainer) exec driver.",
}
-
NotOverlay = TestRequirement{
func() bool {
cmd := exec.Command("grep", "^overlay / overlay", "/proc/mounts")
@@ -81,10 +100,10 @@ var (
// testRequires checks if the environment satisfies the requirements
// for the test to run or skips the tests.
-func testRequires(t *testing.T, requirements ...TestRequirement) {
+func testRequires(c *check.C, requirements ...TestRequirement) {
for _, r := range requirements {
if !r.Condition() {
- t.Skip(r.SkipMessage)
+ c.Skip(r.SkipMessage)
}
}
}
diff --git a/integration-cli/utils.go b/integration-cli/utils.go
index 85e6f1ccd..0ec4e758c 100644
--- a/integration-cli/utils.go
+++ b/integration-cli/utils.go
@@ -1,13 +1,12 @@
package main
import (
+ "archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
- "math/rand"
- "net/http"
"net/http/httptest"
"os"
"os/exec"
@@ -17,13 +16,13 @@ import (
"syscall"
"time"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/stringutils"
)
func getExitCode(err error) (int, error) {
exitCode := 0
if exiterr, ok := err.(*exec.ExitError); ok {
- if procExit := exiterr.Sys().(syscall.WaitStatus); ok {
+ if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return procExit.ExitStatus(), nil
}
}
@@ -44,12 +43,14 @@ func processExitCode(err error) (exitCode int) {
func IsKilled(err error) bool {
if exitErr, ok := err.(*exec.ExitError); ok {
- sys := exitErr.ProcessState.Sys()
- status, ok := sys.(syscall.WaitStatus)
+ status, ok := exitErr.Sys().(syscall.WaitStatus)
if !ok {
return false
}
- return status.Signaled() && status.Signal() == os.Kill
+ // status.ExitStatus() is required on Windows because it does not
+ // implement Signal() nor Signaled(). Just check it had a bad exit
+ // status could mean it was killed (and in tests we do kill)
+ return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
}
return false
}
@@ -141,6 +142,7 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in
if i > 0 {
prevCmd := cmds[i-1]
cmd.Stdin, err = prevCmd.StdoutPipe()
+
if err != nil {
return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err)
}
@@ -165,17 +167,8 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in
return runCommandWithOutput(cmds[len(cmds)-1])
}
-func logDone(message string) {
- fmt.Printf("[PASSED]: %s\n", message)
-}
-
-func stripTrailingCharacters(target string) string {
- return strings.TrimSpace(target)
-}
-
func unmarshalJSON(data []byte, result interface{}) error {
- err := json.Unmarshal(data, result)
- if err != nil {
+ if err := json.Unmarshal(data, result); err != nil {
return err
}
@@ -215,7 +208,16 @@ func waitInspect(name, expr, expected string, timeout int) error {
cmd := exec.Command(dockerBinary, "inspect", "-f", expr, name)
out, _, err := runCommandWithOutput(cmd)
if err != nil {
- return fmt.Errorf("error executing docker inspect: %v", err)
+ if !strings.Contains(out, "No such") {
+ return fmt.Errorf("error executing docker inspect: %v\n%s", err, out)
+ }
+ select {
+ case <-after:
+ return err
+ default:
+ time.Sleep(10 * time.Millisecond)
+ continue
+ }
}
out = strings.TrimSpace(out)
@@ -272,50 +274,10 @@ type FileServer struct {
*httptest.Server
}
-func fileServer(files map[string]string) (*FileServer, error) {
- var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
- if filePath, found := files[r.URL.Path]; found {
- http.ServeFile(w, r, filePath)
- } else {
- http.Error(w, http.StatusText(404), 404)
- }
- }
-
- for _, file := range files {
- if _, err := os.Stat(file); err != nil {
- return nil, err
- }
- }
- server := httptest.NewServer(handler)
- return &FileServer{
- Server: server,
- }, nil
-}
-
-func copyWithCP(source, target string) error {
- copyCmd := exec.Command("cp", "-rp", source, target)
- out, exitCode, err := runCommandWithOutput(copyCmd)
- if err != nil || exitCode != 0 {
- return fmt.Errorf("failed to copy: error: %q ,output: %q", err, out)
- }
- return nil
-}
-
-func makeRandomString(n int) string {
- // make a really long string
- letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
- b := make([]byte, n)
- r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
- for i := range b {
- b[i] = letters[r.Intn(len(letters))]
- }
- return string(b)
-}
-
// randomUnixTmpDirPath provides a temporary unix path with rand string appended.
// does not create or checks if it exists.
func randomUnixTmpDirPath(s string) string {
- return path.Join("/tmp", fmt.Sprintf("%s.%s", s, makeRandomString(10)))
+ return path.Join("/tmp", fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10)))
}
// Reads chunkSize bytes from reader after every interval.
@@ -354,3 +316,26 @@ func parseCgroupPaths(procCgroupData string) map[string]string {
}
return cgroupPaths
}
+
+type channelBuffer struct {
+ c chan []byte
+}
+
+func (c *channelBuffer) Write(b []byte) (int, error) {
+ c.c <- b
+ return len(b), nil
+}
+
+func (c *channelBuffer) Close() error {
+ close(c.c)
+ return nil
+}
+
+func (c *channelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
+ select {
+ case b := <-c.c:
+ return copy(p[0:], b), nil
+ case <-time.After(n):
+ return -1, fmt.Errorf("timeout reading from channel")
+ }
+}
diff --git a/integration/README.md b/integration/README.md
deleted file mode 100644
index 41f43a4ba..000000000
--- a/integration/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-## Legacy integration tests
-
-`./integration` contains Docker's legacy integration tests.
-It is DEPRECATED and will eventually be removed.
-
-### If you are a *CONTRIBUTOR* and want to add a test:
-
-* Consider mocking out side effects and contributing a *unit test* in the subsystem
-you're modifying. For example, the remote API has unit tests in `./api/server/server_unit_tests.go`.
-The events subsystem has unit tests in `./events/events_test.go`. And so on.
-
-* For end-to-end integration tests, please contribute to `./integration-cli`.
-
-
-### If you are a *MAINTAINER*
-
-Please don't allow patches adding new tests to `./integration`.
-
-### If you are *LOOKING FOR A WAY TO HELP*
-
-Please consider porting tests away from `./integration` and into either unit tests or CLI tests.
-
-Any help will be greatly appreciated!
diff --git a/integration/api_test.go b/integration/api_test.go
deleted file mode 100644
index e978c311b..000000000
--- a/integration/api_test.go
+++ /dev/null
@@ -1,991 +0,0 @@
-package docker
-
-import (
- "bufio"
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
-
- "github.com/docker/docker/api"
- "github.com/docker/docker/api/server"
- "github.com/docker/docker/builder"
- "github.com/docker/docker/engine"
- "github.com/docker/docker/runconfig"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
-)
-
-func TestSaveImageAndThenLoad(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- // save image
- r := httptest.NewRecorder()
- req, err := http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusOK {
- t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
- }
- tarball := r.Body
-
- // delete the image
- r = httptest.NewRecorder()
- req, err = http.NewRequest("DELETE", "/images/"+unitTestImageID, nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusOK {
- t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
- }
-
- // make sure there is no image
- r = httptest.NewRecorder()
- req, err = http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusNotFound {
- t.Fatalf("%d NotFound expected, received %d\n", http.StatusNotFound, r.Code)
- }
-
- // load the image
- r = httptest.NewRecorder()
- req, err = http.NewRequest("POST", "/images/load", tarball)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusOK {
- t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
- }
-
- // finally make sure the image is there
- r = httptest.NewRecorder()
- req, err = http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusOK {
- t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
- }
-}
-
-func TestGetContainersTop(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/sh", "-c", "cat"},
- OpenStdin: true,
- },
- t,
- )
- defer func() {
- // Make sure the process dies before destroying daemon
- containerKill(eng, containerID, t)
- containerWait(eng, containerID, t)
- }()
-
- startContainer(eng, containerID, t)
-
- setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
- for {
- if containerRunning(eng, containerID, t) {
- break
- }
- time.Sleep(10 * time.Millisecond)
- }
- })
-
- if !containerRunning(eng, containerID, t) {
- t.Fatalf("Container should be running")
- }
-
- // Make sure sh spawn up cat
- setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
- in, out := containerAttach(eng, containerID, t)
- if err := assertPipe("hello\n", "hello", out, in, 150); err != nil {
- t.Fatal(err)
- }
- })
-
- r := httptest.NewRecorder()
- req, err := http.NewRequest("GET", "/containers/"+containerID+"/top?ps_args=aux", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- var procs engine.Env
- if err := procs.Decode(r.Body); err != nil {
- t.Fatal(err)
- }
-
- if len(procs.GetList("Titles")) != 11 {
- t.Fatalf("Expected 11 titles, found %d.", len(procs.GetList("Titles")))
- }
- if procs.GetList("Titles")[0] != "USER" || procs.GetList("Titles")[10] != "COMMAND" {
- t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.GetList("Titles")[0], procs.GetList("Titles")[10])
- }
- processes := [][]string{}
- if err := procs.GetJson("Processes", &processes); err != nil {
- t.Fatal(err)
- }
- if len(processes) != 2 {
- t.Fatalf("Expected 2 processes, found %d.", len(processes))
- }
- if processes[0][10] != "/bin/sh -c cat" {
- t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[0][10])
- }
- if processes[1][10] != "/bin/sh -c cat" {
- t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[1][10])
- }
-}
-
-func TestPostCommit(t *testing.T) {
- eng := NewTestEngine(t)
- b := &builder.BuilderJob{Engine: eng}
- b.Install()
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- // Create a container and remove a file
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"touch", "/test"},
- },
- t,
- )
-
- containerRun(eng, containerID, t)
-
- req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+containerID, bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
-
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusCreated {
- t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
- }
-
- var env engine.Env
- if err := env.Decode(r.Body); err != nil {
- t.Fatal(err)
- }
- if err := eng.Job("image_inspect", env.Get("Id")).Run(); err != nil {
- t.Fatalf("The image has not been committed")
- }
-}
-
-func TestPostContainersCreate(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- configJSON, err := json.Marshal(&runconfig.Config{
- Image: unitTestImageID,
- Memory: 33554432,
- Cmd: []string{"touch", "/test"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON))
- if err != nil {
- t.Fatal(err)
- }
-
- req.Header.Set("Content-Type", "application/json")
-
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusCreated {
- t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
- }
-
- var apiRun engine.Env
- if err := apiRun.Decode(r.Body); err != nil {
- t.Fatal(err)
- }
- containerID := apiRun.Get("Id")
-
- containerAssertExists(eng, containerID, t)
- containerRun(eng, containerID, t)
-
- if !containerFileExists(eng, containerID, "test", t) {
- t.Fatal("Test file was not created")
- }
-}
-
-func TestPostJsonVerify(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- configJSON, err := json.Marshal(&runconfig.Config{
- Image: unitTestImageID,
- Memory: 33554432,
- Cmd: []string{"touch", "/test"},
- })
- if err != nil {
- t.Fatal(err)
- }
-
- req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON))
- if err != nil {
- t.Fatal(err)
- }
-
- r := httptest.NewRecorder()
-
- server.ServeRequest(eng, api.APIVERSION, r, req)
-
- // Don't add Content-Type header
- // req.Header.Set("Content-Type", "application/json")
-
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusInternalServerError || !strings.Contains(((*r.Body).String()), "application/json") {
- t.Fatal("Create should have failed due to no Content-Type header - got:", r)
- }
-
- // Now add header but with wrong type and retest
- req.Header.Set("Content-Type", "application/xml")
-
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusInternalServerError || !strings.Contains(((*r.Body).String()), "application/json") {
- t.Fatal("Create should have failed due to wrong Content-Type header - got:", r)
- }
-}
-
-// Issue 7941 - test to make sure a "null" in JSON is just ignored.
-// W/o this fix a null in JSON would be parsed into a string var as "null"
-func TestPostCreateNull(t *testing.T) {
- eng := NewTestEngine(t)
- daemon := mkDaemonFromEngine(eng, t)
- defer daemon.Nuke()
-
- configStr := fmt.Sprintf(`{
- "Hostname":"",
- "Domainname":"",
- "Memory":0,
- "MemorySwap":0,
- "CpuShares":0,
- "Cpuset":null,
- "AttachStdin":true,
- "AttachStdout":true,
- "AttachStderr":true,
- "PortSpecs":null,
- "ExposedPorts":{},
- "Tty":true,
- "OpenStdin":true,
- "StdinOnce":true,
- "Env":[],
- "Cmd":"ls",
- "Image":"%s",
- "Volumes":{},
- "WorkingDir":"",
- "Entrypoint":null,
- "NetworkDisabled":false,
- "OnBuild":null}`, unitTestImageID)
-
- req, err := http.NewRequest("POST", "/containers/create", strings.NewReader(configStr))
- if err != nil {
- t.Fatal(err)
- }
-
- req.Header.Set("Content-Type", "application/json")
-
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusCreated {
- t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
- }
-
- var apiRun engine.Env
- if err := apiRun.Decode(r.Body); err != nil {
- t.Fatal(err)
- }
- containerID := apiRun.Get("Id")
-
- containerAssertExists(eng, containerID, t)
-
- c, _ := daemon.Get(containerID)
- if c.Config.Cpuset != "" {
- t.Fatalf("Cpuset should have been empty - instead its:" + c.Config.Cpuset)
- }
-}
-
-func TestPostContainersKill(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/cat"},
- OpenStdin: true,
- },
- t,
- )
-
- startContainer(eng, containerID, t)
-
- // Give some time to the process to start
- containerWaitTimeout(eng, containerID, t)
-
- if !containerRunning(eng, containerID, t) {
- t.Errorf("Container should be running")
- }
-
- r := httptest.NewRecorder()
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/kill", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusNoContent {
- t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
- }
- if containerRunning(eng, containerID, t) {
- t.Fatalf("The container hasn't been killed")
- }
-}
-
-func TestPostContainersRestart(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/top"},
- OpenStdin: true,
- },
- t,
- )
-
- startContainer(eng, containerID, t)
-
- // Give some time to the process to start
- containerWaitTimeout(eng, containerID, t)
-
- if !containerRunning(eng, containerID, t) {
- t.Errorf("Container should be running")
- }
-
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/restart?t=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusNoContent {
- t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
- }
-
- // Give some time to the process to restart
- containerWaitTimeout(eng, containerID, t)
-
- if !containerRunning(eng, containerID, t) {
- t.Fatalf("Container should be running")
- }
-
- containerKill(eng, containerID, t)
-}
-
-func TestPostContainersStart(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(
- eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/cat"},
- OpenStdin: true,
- },
- t,
- )
-
- hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{})
-
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON))
- if err != nil {
- t.Fatal(err)
- }
-
- req.Header.Set("Content-Type", "application/json")
-
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusNoContent {
- t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
- }
-
- containerAssertExists(eng, containerID, t)
-
- req, err = http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON))
- if err != nil {
- t.Fatal(err)
- }
-
- req.Header.Set("Content-Type", "application/json")
-
- r = httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
-
- // Starting an already started container should return a 304
- assertHttpNotError(r, t)
- if r.Code != http.StatusNotModified {
- t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code)
- }
- containerAssertExists(eng, containerID, t)
- containerKill(eng, containerID, t)
-}
-
-func TestPostContainersStop(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/top"},
- OpenStdin: true,
- },
- t,
- )
-
- startContainer(eng, containerID, t)
-
- // Give some time to the process to start
- containerWaitTimeout(eng, containerID, t)
-
- if !containerRunning(eng, containerID, t) {
- t.Errorf("Container should be running")
- }
-
- // Note: as it is a POST request, it requires a body.
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusNoContent {
- t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
- }
- if containerRunning(eng, containerID, t) {
- t.Fatalf("The container hasn't been stopped")
- }
-
- req, err = http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
-
- r = httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
-
- // Stopping an already stopper container should return a 304
- assertHttpNotError(r, t)
- if r.Code != http.StatusNotModified {
- t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code)
- }
-}
-
-func TestPostContainersWait(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/sleep", "1"},
- OpenStdin: true,
- },
- t,
- )
- startContainer(eng, containerID, t)
-
- setTimeout(t, "Wait timed out", 3*time.Second, func() {
- r := httptest.NewRecorder()
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/wait", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- var apiWait engine.Env
- if err := apiWait.Decode(r.Body); err != nil {
- t.Fatal(err)
- }
- if apiWait.GetInt("StatusCode") != 0 {
- t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.GetInt("StatusCode"))
- }
- })
-
- if containerRunning(eng, containerID, t) {
- t.Fatalf("The container should be stopped after wait")
- }
-}
-
-func TestPostContainersAttach(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/cat"},
- OpenStdin: true,
- },
- t,
- )
- // Start the process
- startContainer(eng, containerID, t)
-
- stdin, stdinPipe := io.Pipe()
- stdout, stdoutPipe := io.Pipe()
-
- // Try to avoid the timeout in destroy. Best effort, don't check error
- defer func() {
- closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
- containerKill(eng, containerID, t)
- }()
-
- // Attach to it
- c1 := make(chan struct{})
- go func() {
- defer close(c1)
-
- r := &hijackTester{
- ResponseRecorder: httptest.NewRecorder(),
- in: stdin,
- out: stdoutPipe,
- }
-
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
-
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r.ResponseRecorder, t)
- }()
-
- // Acknowledge hijack
- setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
- stdout.Read([]byte{})
- stdout.Read(make([]byte, 4096))
- })
-
- setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
- if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 150); err != nil {
- t.Fatal(err)
- }
- })
-
- // Close pipes (client disconnects)
- if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
- t.Fatal(err)
- }
-
- // Wait for attach to finish, the client disconnected, therefore, Attach finished his job
- setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() {
- <-c1
- })
-
- // We closed stdin, expect /bin/cat to still be running
- // Wait a little bit to make sure container.monitor() did his thing
- containerWaitTimeout(eng, containerID, t)
-
- // Try to avoid the timeout in destroy. Best effort, don't check error
- cStdin, _ := containerAttach(eng, containerID, t)
- cStdin.Close()
- containerWait(eng, containerID, t)
-}
-
-func TestPostContainersAttachStderr(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"},
- OpenStdin: true,
- },
- t,
- )
- // Start the process
- startContainer(eng, containerID, t)
-
- stdin, stdinPipe := io.Pipe()
- stdout, stdoutPipe := io.Pipe()
-
- // Try to avoid the timeout in destroy. Best effort, don't check error
- defer func() {
- closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
- containerKill(eng, containerID, t)
- }()
-
- // Attach to it
- c1 := make(chan struct{})
- go func() {
- defer close(c1)
-
- r := &hijackTester{
- ResponseRecorder: httptest.NewRecorder(),
- in: stdin,
- out: stdoutPipe,
- }
-
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
-
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r.ResponseRecorder, t)
- }()
-
- // Acknowledge hijack
- setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
- stdout.Read([]byte{})
- stdout.Read(make([]byte, 4096))
- })
-
- setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
- if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 150); err != nil {
- t.Fatal(err)
- }
- })
-
- // Close pipes (client disconnects)
- if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
- t.Fatal(err)
- }
-
- // Wait for attach to finish, the client disconnected, therefore, Attach finished his job
- setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() {
- <-c1
- })
-
- // We closed stdin, expect /bin/cat to still be running
- // Wait a little bit to make sure container.monitor() did his thing
- containerWaitTimeout(eng, containerID, t)
-
- // Try to avoid the timeout in destroy. Best effort, don't check error
- cStdin, _ := containerAttach(eng, containerID, t)
- cStdin.Close()
- containerWait(eng, containerID, t)
-}
-
-func TestOptionsRoute(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- r := httptest.NewRecorder()
- req, err := http.NewRequest("OPTIONS", "/", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusOK {
- t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
- }
-}
-
-func TestGetEnabledCors(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- r := httptest.NewRecorder()
-
- req, err := http.NewRequest("GET", "/version", nil)
- if err != nil {
- t.Fatal(err)
- }
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
- if r.Code != http.StatusOK {
- t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
- }
-
- allowOrigin := r.Header().Get("Access-Control-Allow-Origin")
- allowHeaders := r.Header().Get("Access-Control-Allow-Headers")
- allowMethods := r.Header().Get("Access-Control-Allow-Methods")
-
- if allowOrigin != "*" {
- t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin)
- }
- if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth" {
- t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth\", %s found.", allowHeaders)
- }
- if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" {
- t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods)
- }
-}
-
-func TestDeleteImages(t *testing.T) {
- eng := NewTestEngine(t)
- //we expect errors, so we disable stderr
- eng.Stderr = ioutil.Discard
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- initialImages := getImages(eng, t, true, "")
-
- if err := eng.Job("tag", unitTestImageName, "test", "test").Run(); err != nil {
- t.Fatal(err)
- }
-
- images := getImages(eng, t, true, "")
-
- if len(images.Data[0].GetList("RepoTags")) != len(initialImages.Data[0].GetList("RepoTags"))+1 {
- t.Errorf("Expected %d images, %d found", len(initialImages.Data[0].GetList("RepoTags"))+1, len(images.Data[0].GetList("RepoTags")))
- }
-
- req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- r := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusConflict {
- t.Fatalf("Expected http status 409-conflict, got %v", r.Code)
- }
-
- req2, err := http.NewRequest("DELETE", "/images/test:test", nil)
- if err != nil {
- t.Fatal(err)
- }
-
- r2 := httptest.NewRecorder()
- server.ServeRequest(eng, api.APIVERSION, r2, req2)
- assertHttpNotError(r2, t)
- if r2.Code != http.StatusOK {
- t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
- }
-
- outs := engine.NewTable("Created", 0)
- if _, err := outs.ReadListFrom(r2.Body.Bytes()); err != nil {
- t.Fatal(err)
- }
- if len(outs.Data) != 1 {
- t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs.Data))
- }
- images = getImages(eng, t, false, "")
-
- if images.Len() != initialImages.Len() {
- t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len())
- }
-}
-
-func TestPostContainersCopy(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- // Create a container and remove a file
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"touch", "/test.txt"},
- },
- t,
- )
- containerRun(eng, containerID, t)
-
- r := httptest.NewRecorder()
-
- var copyData engine.Env
- copyData.Set("Resource", "/test.txt")
- copyData.Set("HostPath", ".")
-
- jsonData := bytes.NewBuffer(nil)
- if err := copyData.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
-
- req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", jsonData)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
-
- if r.Code != http.StatusOK {
- t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
- }
-
- found := false
- for tarReader := tar.NewReader(r.Body); ; {
- h, err := tarReader.Next()
- if err != nil {
- if err == io.EOF {
- break
- }
- t.Fatal(err)
- }
- if h.Name == "test.txt" {
- found = true
- break
- }
- }
- if !found {
- t.Fatalf("The created test file has not been found in the copied output")
- }
-}
-
-func TestPostContainersCopyWhenContainerNotFound(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- r := httptest.NewRecorder()
-
- var copyData engine.Env
- copyData.Set("Resource", "/test.txt")
- copyData.Set("HostPath", ".")
-
- jsonData := bytes.NewBuffer(nil)
- if err := copyData.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
-
- req, err := http.NewRequest("POST", "/containers/id_not_found/copy", jsonData)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r, req)
- if r.Code != http.StatusNotFound {
- t.Fatalf("404 expected for id_not_found Container, received %v", r.Code)
- }
-}
-
-// Regression test for https://github.com/docker/docker/issues/6231
-func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- r := httptest.NewRecorder()
-
- var testData engine.Env
- testData.Set("Image", "docker-test-image")
- testData.SetAuto("Volumes", map[string]struct{}{"/foo": {}})
- testData.Set("Cmd", "true")
- jsonData := bytes.NewBuffer(nil)
- if err := testData.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
-
- req, err := http.NewRequest("POST", "/containers/create?name=chunk_test", jsonData)
- if err != nil {
- t.Fatal(err)
- }
-
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
-
- var testData2 engine.Env
- testData2.SetAuto("Binds", []string{"/tmp:/foo"})
- jsonData = bytes.NewBuffer(nil)
- if err := testData2.Encode(jsonData); err != nil {
- t.Fatal(err)
- }
-
- req, err = http.NewRequest("POST", "/containers/chunk_test/start", jsonData)
- if err != nil {
- t.Fatal(err)
- }
-
- req.Header.Add("Content-Type", "application/json")
- // This is a cheat to make the http request do chunked encoding
- // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite
- // http://golang.org/src/pkg/net/http/request.go?s=11980:12172
- req.ContentLength = -1
- server.ServeRequest(eng, api.APIVERSION, r, req)
- assertHttpNotError(r, t)
-
- type config struct {
- HostConfig struct {
- Binds []string
- }
- }
-
- req, err = http.NewRequest("GET", "/containers/chunk_test/json", nil)
- if err != nil {
- t.Fatal(err)
- }
-
- r2 := httptest.NewRecorder()
- req.Header.Add("Content-Type", "application/json")
- server.ServeRequest(eng, api.APIVERSION, r2, req)
- assertHttpNotError(r, t)
-
- c := config{}
-
- json.Unmarshal(r2.Body.Bytes(), &c)
-
- if len(c.HostConfig.Binds) == 0 {
- t.Fatal("Chunked Encoding not handled")
- }
-
- if c.HostConfig.Binds[0] != "/tmp:/foo" {
- t.Fatal("Chunked encoding not properly handled, execpted binds to be /tmp:/foo, got:", c.HostConfig.Binds[0])
- }
-}
-
-// Mocked types for tests
-type NopConn struct {
- io.ReadCloser
- io.Writer
-}
-
-func (c *NopConn) LocalAddr() net.Addr { return nil }
-func (c *NopConn) RemoteAddr() net.Addr { return nil }
-func (c *NopConn) SetDeadline(t time.Time) error { return nil }
-func (c *NopConn) SetReadDeadline(t time.Time) error { return nil }
-func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil }
-
-type hijackTester struct {
- *httptest.ResponseRecorder
- in io.ReadCloser
- out io.Writer
-}
-
-func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) {
- bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out))
- conn := &NopConn{
- ReadCloser: t.in,
- Writer: t.out,
- }
- return conn, bufrw, nil
-}
diff --git a/integration/commands_test.go b/integration/commands_test.go
deleted file mode 100644
index 6c6ad0e71..000000000
--- a/integration/commands_test.go
+++ /dev/null
@@ -1,436 +0,0 @@
-package docker
-
-import (
- "bufio"
- "fmt"
- "io"
- "io/ioutil"
- "strings"
- "testing"
- "time"
-
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/api/client"
- "github.com/docker/docker/daemon"
- "github.com/docker/docker/pkg/common"
- "github.com/docker/docker/pkg/term"
- "github.com/kr/pty"
-)
-
-func closeWrap(args ...io.Closer) error {
- e := false
- ret := fmt.Errorf("Error closing elements")
- for _, c := range args {
- if err := c.Close(); err != nil {
- e = true
- ret = fmt.Errorf("%s\n%s", ret, err)
- }
- }
- if e {
- return ret
- }
- return nil
-}
-
-func setRaw(t *testing.T, c *daemon.Container) *term.State {
- pty, err := c.GetPtyMaster()
- if err != nil {
- t.Fatal(err)
- }
- state, err := term.MakeRaw(pty.Fd())
- if err != nil {
- t.Fatal(err)
- }
- return state
-}
-
-func unsetRaw(t *testing.T, c *daemon.Container, state *term.State) {
- pty, err := c.GetPtyMaster()
- if err != nil {
- t.Fatal(err)
- }
- term.RestoreTerminal(pty.Fd(), state)
-}
-
-func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container {
- var container *daemon.Container
-
- setTimeout(t, "Waiting for the container to be started timed out", timeout, func() {
- for {
- l := globalDaemon.List()
- if len(l) == 1 && l[0].IsRunning() {
- container = l[0]
- break
- }
- time.Sleep(10 * time.Millisecond)
- }
- })
-
- if container == nil {
- t.Fatal("An error occured while waiting for the container to start")
- }
-
- return container
-}
-
-func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
- c := make(chan bool)
-
- // Make sure we are not too long
- go func() {
- time.Sleep(d)
- c <- true
- }()
- go func() {
- f()
- c <- false
- }()
- if <-c && msg != "" {
- t.Fatal(msg)
- }
-}
-
-func expectPipe(expected string, r io.Reader) error {
- o, err := bufio.NewReader(r).ReadString('\n')
- if err != nil {
- return err
- }
- if strings.Trim(o, " \r\n") != expected {
- return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", expected, o)
- }
- return nil
-}
-
-func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error {
- for i := 0; i < count; i++ {
- if _, err := w.Write([]byte(input)); err != nil {
- return err
- }
- if err := expectPipe(output, r); err != nil {
- return err
- }
- }
- return nil
-}
-
-// TestRunDetach checks attaching and detaching with the escape sequence.
-func TestRunDetach(t *testing.T) {
- stdout, stdoutPipe := io.Pipe()
- cpty, tty, err := pty.Open()
- if err != nil {
- t.Fatal(err)
- }
-
- cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil)
- defer cleanup(globalEngine, t)
-
- ch := make(chan struct{})
- go func() {
- defer close(ch)
- cli.CmdRun("-i", "-t", unitTestImageID, "cat")
- }()
-
- container := waitContainerStart(t, 10*time.Second)
-
- state := setRaw(t, container)
- defer unsetRaw(t, container, state)
-
- setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
- if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil {
- t.Fatal(err)
- }
- })
-
- setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
- cpty.Write([]byte{16})
- time.Sleep(100 * time.Millisecond)
- cpty.Write([]byte{17})
- })
-
- // wait for CmdRun to return
- setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() {
- <-ch
- })
- closeWrap(cpty, stdout, stdoutPipe)
-
- time.Sleep(500 * time.Millisecond)
- if !container.IsRunning() {
- t.Fatal("The detached container should be still running")
- }
-
- setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() {
- container.Kill()
- })
-}
-
-// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
-func TestAttachDetach(t *testing.T) {
- stdout, stdoutPipe := io.Pipe()
- cpty, tty, err := pty.Open()
- if err != nil {
- t.Fatal(err)
- }
-
- cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil)
- defer cleanup(globalEngine, t)
-
- ch := make(chan struct{})
- go func() {
- defer close(ch)
- if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
- t.Fatal(err)
- }
- }()
-
- container := waitContainerStart(t, 10*time.Second)
-
- setTimeout(t, "Reading container's id timed out", 10*time.Second, func() {
- buf := make([]byte, 1024)
- n, err := stdout.Read(buf)
- if err != nil {
- t.Fatal(err)
- }
-
- if strings.Trim(string(buf[:n]), " \r\n") != container.ID {
- t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n])
- }
- })
- setTimeout(t, "Starting container timed out", 10*time.Second, func() {
- <-ch
- })
-
- state := setRaw(t, container)
- defer unsetRaw(t, container, state)
-
- stdout, stdoutPipe = io.Pipe()
- cpty, tty, err = pty.Open()
- if err != nil {
- t.Fatal(err)
- }
-
- cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil)
-
- ch = make(chan struct{})
- go func() {
- defer close(ch)
- if err := cli.CmdAttach(container.ID); err != nil {
- if err != io.ErrClosedPipe {
- t.Fatal(err)
- }
- }
- }()
-
- setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
- if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil {
- if err != io.ErrClosedPipe {
- t.Fatal(err)
- }
- }
- })
-
- setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
- cpty.Write([]byte{16})
- time.Sleep(100 * time.Millisecond)
- cpty.Write([]byte{17})
- })
-
- // wait for CmdRun to return
- setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
- <-ch
- })
-
- closeWrap(cpty, stdout, stdoutPipe)
-
- time.Sleep(500 * time.Millisecond)
- if !container.IsRunning() {
- t.Fatal("The detached container should be still running")
- }
-
- setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
- container.Kill()
- })
-}
-
-// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
-func TestAttachDetachTruncatedID(t *testing.T) {
- stdout, stdoutPipe := io.Pipe()
- cpty, tty, err := pty.Open()
- if err != nil {
- t.Fatal(err)
- }
-
- cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil)
- defer cleanup(globalEngine, t)
-
- // Discard the CmdRun output
- go stdout.Read(make([]byte, 1024))
- setTimeout(t, "Starting container timed out", 2*time.Second, func() {
- if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
- t.Fatal(err)
- }
- })
-
- container := waitContainerStart(t, 10*time.Second)
-
- state := setRaw(t, container)
- defer unsetRaw(t, container, state)
-
- stdout, stdoutPipe = io.Pipe()
- cpty, tty, err = pty.Open()
- if err != nil {
- t.Fatal(err)
- }
-
- cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil)
-
- ch := make(chan struct{})
- go func() {
- defer close(ch)
- if err := cli.CmdAttach(common.TruncateID(container.ID)); err != nil {
- if err != io.ErrClosedPipe {
- t.Fatal(err)
- }
- }
- }()
-
- setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
- if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil {
- if err != io.ErrClosedPipe {
- t.Fatal(err)
- }
- }
- })
-
- setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
- cpty.Write([]byte{16})
- time.Sleep(100 * time.Millisecond)
- cpty.Write([]byte{17})
- })
-
- // wait for CmdRun to return
- setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
- <-ch
- })
- closeWrap(cpty, stdout, stdoutPipe)
-
- time.Sleep(500 * time.Millisecond)
- if !container.IsRunning() {
- t.Fatal("The detached container should be still running")
- }
-
- setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
- container.Kill()
- })
-}
-
-// Expected behaviour, the process stays alive when the client disconnects
-func TestAttachDisconnect(t *testing.T) {
- stdout, stdoutPipe := io.Pipe()
- cpty, tty, err := pty.Open()
- if err != nil {
- t.Fatal(err)
- }
-
- cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil)
- defer cleanup(globalEngine, t)
-
- go func() {
- // Start a process in daemon mode
- if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
- log.Debugf("Error CmdRun: %s", err)
- }
- }()
-
- setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
- if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
- t.Fatal(err)
- }
- })
-
- setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
- for {
- l := globalDaemon.List()
- if len(l) == 1 && l[0].IsRunning() {
- break
- }
- time.Sleep(10 * time.Millisecond)
- }
- })
-
- container := globalDaemon.List()[0]
-
- // Attach to it
- c1 := make(chan struct{})
- go func() {
- // We're simulating a disconnect so the return value doesn't matter. What matters is the
- // fact that CmdAttach returns.
- cli.CmdAttach(container.ID)
- close(c1)
- }()
-
- setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
- if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil {
- t.Fatal(err)
- }
- })
- // Close pipes (client disconnects)
- if err := closeWrap(cpty, stdout, stdoutPipe); err != nil {
- t.Fatal(err)
- }
-
- // Wait for attach to finish, the client disconnected, therefore, Attach finished his job
- setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
- <-c1
- })
-
- // We closed stdin, expect /bin/cat to still be running
- // Wait a little bit to make sure container.monitor() did his thing
- _, err = container.WaitStop(500 * time.Millisecond)
- if err == nil || !container.IsRunning() {
- t.Fatalf("/bin/cat is not running after closing stdin")
- }
-
- // Try to avoid the timeout in destroy. Best effort, don't check error
- cStdin := container.StdinPipe()
- cStdin.Close()
- container.WaitStop(-1 * time.Second)
-}
-
-// Expected behaviour: container gets deleted automatically after exit
-func TestRunAutoRemove(t *testing.T) {
- t.Skip("Fixme. Skipping test for now, race condition")
- stdout, stdoutPipe := io.Pipe()
-
- cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil)
- defer cleanup(globalEngine, t)
-
- c := make(chan struct{})
- go func() {
- defer close(c)
- if err := cli.CmdRun("--rm", unitTestImageID, "hostname"); err != nil {
- t.Fatal(err)
- }
- }()
-
- var temporaryContainerID string
- setTimeout(t, "Reading command output time out", 2*time.Second, func() {
- cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
- if err != nil {
- t.Fatal(err)
- }
- temporaryContainerID = cmdOutput
- if err := closeWrap(stdout, stdoutPipe); err != nil {
- t.Fatal(err)
- }
- })
-
- setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
- <-c
- })
-
- time.Sleep(500 * time.Millisecond)
-
- if len(globalDaemon.List()) > 0 {
- t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID)
- }
-}
diff --git a/integration/container_test.go b/integration/container_test.go
deleted file mode 100644
index b6cbfd096..000000000
--- a/integration/container_test.go
+++ /dev/null
@@ -1,235 +0,0 @@
-package docker
-
-import (
- "io"
- "io/ioutil"
- "testing"
- "time"
-
- "github.com/docker/docker/runconfig"
-)
-
-func TestRestartStdin(t *testing.T) {
- daemon := mkDaemon(t)
- defer nuke(daemon)
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"cat"},
-
- OpenStdin: true,
- },
- &runconfig.HostConfig{},
- "",
- )
- if err != nil {
- t.Fatal(err)
- }
- defer daemon.Rm(container)
-
- stdin := container.StdinPipe()
- stdout := container.StdoutPipe()
- if err := container.Start(); err != nil {
- t.Fatal(err)
- }
- if _, err := io.WriteString(stdin, "hello world"); err != nil {
- t.Fatal(err)
- }
- if err := stdin.Close(); err != nil {
- t.Fatal(err)
- }
- container.WaitStop(-1 * time.Second)
- output, err := ioutil.ReadAll(stdout)
- if err != nil {
- t.Fatal(err)
- }
- if err := stdout.Close(); err != nil {
- t.Fatal(err)
- }
- if string(output) != "hello world" {
- t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output))
- }
-
- // Restart and try again
- stdin = container.StdinPipe()
- stdout = container.StdoutPipe()
- if err := container.Start(); err != nil {
- t.Fatal(err)
- }
- if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
- t.Fatal(err)
- }
- if err := stdin.Close(); err != nil {
- t.Fatal(err)
- }
- container.WaitStop(-1 * time.Second)
- output, err = ioutil.ReadAll(stdout)
- if err != nil {
- t.Fatal(err)
- }
- if err := stdout.Close(); err != nil {
- t.Fatal(err)
- }
- if string(output) != "hello world #2" {
- t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world #2", string(output))
- }
-}
-
-func TestStdin(t *testing.T) {
- daemon := mkDaemon(t)
- defer nuke(daemon)
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"cat"},
-
- OpenStdin: true,
- },
- &runconfig.HostConfig{},
- "",
- )
- if err != nil {
- t.Fatal(err)
- }
- defer daemon.Rm(container)
-
- stdin := container.StdinPipe()
- stdout := container.StdoutPipe()
- if err := container.Start(); err != nil {
- t.Fatal(err)
- }
- defer stdin.Close()
- defer stdout.Close()
- if _, err := io.WriteString(stdin, "hello world"); err != nil {
- t.Fatal(err)
- }
- if err := stdin.Close(); err != nil {
- t.Fatal(err)
- }
- container.WaitStop(-1 * time.Second)
- output, err := ioutil.ReadAll(stdout)
- if err != nil {
- t.Fatal(err)
- }
- if string(output) != "hello world" {
- t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output))
- }
-}
-
-func TestTty(t *testing.T) {
- daemon := mkDaemon(t)
- defer nuke(daemon)
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"cat"},
-
- OpenStdin: true,
- },
- &runconfig.HostConfig{},
- "",
- )
- if err != nil {
- t.Fatal(err)
- }
- defer daemon.Rm(container)
-
- stdin := container.StdinPipe()
- stdout := container.StdoutPipe()
- if err := container.Start(); err != nil {
- t.Fatal(err)
- }
- defer stdin.Close()
- defer stdout.Close()
- if _, err := io.WriteString(stdin, "hello world"); err != nil {
- t.Fatal(err)
- }
- if err := stdin.Close(); err != nil {
- t.Fatal(err)
- }
- container.WaitStop(-1 * time.Second)
- output, err := ioutil.ReadAll(stdout)
- if err != nil {
- t.Fatal(err)
- }
- if string(output) != "hello world" {
- t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output))
- }
-}
-
-func BenchmarkRunSequential(b *testing.B) {
- daemon := mkDaemon(b)
- defer nuke(daemon)
- for i := 0; i < b.N; i++ {
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"echo", "-n", "foo"},
- },
- &runconfig.HostConfig{},
- "",
- )
- if err != nil {
- b.Fatal(err)
- }
- defer daemon.Rm(container)
- output, err := container.Output()
- if err != nil {
- b.Fatal(err)
- }
- if string(output) != "foo" {
- b.Fatalf("Unexpected output: %s", output)
- }
- if err := daemon.Rm(container); err != nil {
- b.Fatal(err)
- }
- }
-}
-
-func BenchmarkRunParallel(b *testing.B) {
- daemon := mkDaemon(b)
- defer nuke(daemon)
-
- var tasks []chan error
-
- for i := 0; i < b.N; i++ {
- complete := make(chan error)
- tasks = append(tasks, complete)
- go func(i int, complete chan error) {
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"echo", "-n", "foo"},
- },
- &runconfig.HostConfig{},
- "",
- )
- if err != nil {
- complete <- err
- return
- }
- defer daemon.Rm(container)
- if err := container.Start(); err != nil {
- complete <- err
- return
- }
- if _, err := container.WaitStop(15 * time.Second); err != nil {
- complete <- err
- return
- }
- // if string(output) != "foo" {
- // complete <- fmt.Errorf("Unexecpted output: %v", string(output))
- // }
- if err := daemon.Rm(container); err != nil {
- complete <- err
- return
- }
- complete <- nil
- }(i, complete)
- }
- var errors []error
- for _, task := range tasks {
- err := <-task
- if err != nil {
- errors = append(errors, err)
- }
- }
- if len(errors) > 0 {
- b.Fatal(errors)
- }
-}
diff --git a/integration/https_test.go b/integration/https_test.go
deleted file mode 100644
index 17d69345a..000000000
--- a/integration/https_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package docker
-
-import (
- "crypto/tls"
- "crypto/x509"
- "io/ioutil"
- "strings"
- "testing"
- "time"
-
- "github.com/docker/docker/api/client"
-)
-
-const (
- errBadCertificate = "remote error: bad certificate"
- errCaUnknown = "x509: certificate signed by unknown authority"
-)
-
-func getTlsConfig(certFile, keyFile string, t *testing.T) *tls.Config {
- certPool := x509.NewCertPool()
- file, err := ioutil.ReadFile("fixtures/https/ca.pem")
- if err != nil {
- t.Fatal(err)
- }
- certPool.AppendCertsFromPEM(file)
-
- cert, err := tls.LoadX509KeyPair("fixtures/https/"+certFile, "fixtures/https/"+keyFile)
- if err != nil {
- t.Fatalf("Couldn't load X509 key pair: %s", err)
- }
- tlsConfig := &tls.Config{
- RootCAs: certPool,
- Certificates: []tls.Certificate{cert},
- }
- return tlsConfig
-}
-
-// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint
-func TestHttpsInfo(t *testing.T) {
- cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, "", testDaemonProto,
- testDaemonHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t))
-
- setTimeout(t, "Reading command output time out", 10*time.Second, func() {
- if err := cli.CmdInfo(); err != nil {
- t.Fatal(err)
- }
- })
-}
-
-// TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint
-// by using a rogue client certificate and checks that it fails with the expected error.
-func TestHttpsInfoRogueCert(t *testing.T) {
- cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, "", testDaemonProto,
- testDaemonHttpsAddr, getTlsConfig("client-rogue-cert.pem", "client-rogue-key.pem", t))
-
- setTimeout(t, "Reading command output time out", 10*time.Second, func() {
- err := cli.CmdInfo()
- if err == nil {
- t.Fatal("Expected error but got nil")
- }
- if !strings.Contains(err.Error(), errBadCertificate) {
- t.Fatalf("Expected error: %s, got instead: %s", errBadCertificate, err)
- }
- })
-}
-
-// TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint
-// which provides a rogue server certificate and checks that it fails with the expected error
-func TestHttpsInfoRogueServerCert(t *testing.T) {
- cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, "", testDaemonProto,
- testDaemonRogueHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t))
-
- setTimeout(t, "Reading command output time out", 10*time.Second, func() {
- err := cli.CmdInfo()
- if err == nil {
- t.Fatal("Expected error but got nil")
- }
-
- if !strings.Contains(err.Error(), errCaUnknown) {
- t.Fatalf("Expected error: %s, got instead: %s", errCaUnknown, err)
- }
-
- })
-}
diff --git a/integration/runtime_test.go b/integration/runtime_test.go
deleted file mode 100644
index 153c38562..000000000
--- a/integration/runtime_test.go
+++ /dev/null
@@ -1,915 +0,0 @@
-package docker
-
-import (
- "bytes"
- "fmt"
- "io"
- std_log "log"
- "net"
- "net/url"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "syscall"
- "testing"
- "time"
-
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/daemon"
- "github.com/docker/docker/daemon/execdriver"
- "github.com/docker/docker/engine"
- "github.com/docker/docker/image"
- "github.com/docker/docker/nat"
- "github.com/docker/docker/pkg/common"
- "github.com/docker/docker/pkg/ioutils"
- "github.com/docker/docker/pkg/reexec"
- "github.com/docker/docker/runconfig"
- "github.com/docker/docker/utils"
-)
-
-const (
- unitTestImageName = "docker-test-image"
- unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
- unitTestImageIDShort = "83599e29c455"
- unitTestNetworkBridge = "testdockbr0"
- unitTestStoreBase = "/var/lib/docker/unit-tests"
- unitTestDockerTmpdir = "/var/lib/docker/tmp"
- testDaemonAddr = "127.0.0.1:4270"
- testDaemonProto = "tcp"
- testDaemonHttpsProto = "tcp"
- testDaemonHttpsAddr = "localhost:4271"
- testDaemonRogueHttpsAddr = "localhost:4272"
-)
-
-var (
- // FIXME: globalDaemon is deprecated by globalEngine. All tests should be converted.
- globalDaemon *daemon.Daemon
- globalEngine *engine.Engine
- globalHttpsEngine *engine.Engine
- globalRogueHttpsEngine *engine.Engine
- startFds int
- startGoroutines int
-)
-
-// FIXME: nuke() is deprecated by Daemon.Nuke()
-func nuke(daemon *daemon.Daemon) error {
- return daemon.Nuke()
-}
-
-// FIXME: cleanup and nuke are redundant.
-func cleanup(eng *engine.Engine, t *testing.T) error {
- daemon := mkDaemonFromEngine(eng, t)
- for _, container := range daemon.List() {
- container.Kill()
- daemon.Rm(container)
- }
- job := eng.Job("images")
- images, err := job.Stdout.AddTable()
- if err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- for _, image := range images.Data {
- if image.Get("Id") != unitTestImageID {
- eng.Job("image_delete", image.Get("Id")).Run()
- }
- }
- return nil
-}
-
-func init() {
- // Always use the same driver (vfs) for all integration tests.
- // To test other drivers, we need a dedicated driver validation suite.
- os.Setenv("DOCKER_DRIVER", "vfs")
- os.Setenv("TEST", "1")
- os.Setenv("DOCKER_TMPDIR", unitTestDockerTmpdir)
-
- // Hack to run sys init during unit testing
- if reexec.Init() {
- return
- }
-
- if uid := syscall.Geteuid(); uid != 0 {
- log.Fatalf("docker tests need to be run as root")
- }
-
- // Copy dockerinit into our current testing directory, if provided (so we can test a separate dockerinit binary)
- if dockerinit := os.Getenv("TEST_DOCKERINIT_PATH"); dockerinit != "" {
- src, err := os.Open(dockerinit)
- if err != nil {
- log.Fatalf("Unable to open TEST_DOCKERINIT_PATH: %s", err)
- }
- defer src.Close()
- dst, err := os.OpenFile(filepath.Join(filepath.Dir(utils.SelfPath()), "dockerinit"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0555)
- if err != nil {
- log.Fatalf("Unable to create dockerinit in test directory: %s", err)
- }
- defer dst.Close()
- if _, err := io.Copy(dst, src); err != nil {
- log.Fatalf("Unable to copy dockerinit to TEST_DOCKERINIT_PATH: %s", err)
- }
- dst.Close()
- src.Close()
- }
-
- // Setup the base daemon, which will be duplicated for each test.
- // (no tests are run directly in the base)
- setupBaseImage()
-
- // Create the "global daemon" with a long-running daemons for integration tests
- spawnGlobalDaemon()
- spawnLegitHttpsDaemon()
- spawnRogueHttpsDaemon()
- startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine()
-}
-
-func setupBaseImage() {
- eng := newTestEngine(std_log.New(os.Stderr, "", 0), false, unitTestStoreBase)
- job := eng.Job("image_inspect", unitTestImageName)
- img, _ := job.Stdout.AddEnv()
- // If the unit test is not found, try to download it.
- if err := job.Run(); err != nil || img.Get("Id") != unitTestImageID {
- // Retrieve the Image
- job = eng.Job("pull", unitTestImageName)
- job.Stdout.Add(ioutils.NopWriteCloser(os.Stdout))
- if err := job.Run(); err != nil {
- log.Fatalf("Unable to pull the test image: %s", err)
- }
- }
-}
-
-func spawnGlobalDaemon() {
- if globalDaemon != nil {
- log.Debugf("Global daemon already exists. Skipping.")
- return
- }
- t := std_log.New(os.Stderr, "", 0)
- eng := NewTestEngine(t)
- globalEngine = eng
- globalDaemon = mkDaemonFromEngine(eng, t)
-
- // Spawn a Daemon
- go func() {
- log.Debugf("Spawning global daemon for integration tests")
- listenURL := &url.URL{
- Scheme: testDaemonProto,
- Host: testDaemonAddr,
- }
- job := eng.Job("serveapi", listenURL.String())
- job.SetenvBool("Logging", true)
- if err := job.Run(); err != nil {
- log.Fatalf("Unable to spawn the test daemon: %s", err)
- }
- }()
-
- // Give some time to ListenAndServer to actually start
- // FIXME: use inmem transports instead of tcp
- time.Sleep(time.Second)
-
- if err := eng.Job("acceptconnections").Run(); err != nil {
- log.Fatalf("Unable to accept connections for test api: %s", err)
- }
-}
-
-func spawnLegitHttpsDaemon() {
- if globalHttpsEngine != nil {
- return
- }
- globalHttpsEngine = spawnHttpsDaemon(testDaemonHttpsAddr, "fixtures/https/ca.pem",
- "fixtures/https/server-cert.pem", "fixtures/https/server-key.pem")
-}
-
-func spawnRogueHttpsDaemon() {
- if globalRogueHttpsEngine != nil {
- return
- }
- globalRogueHttpsEngine = spawnHttpsDaemon(testDaemonRogueHttpsAddr, "fixtures/https/ca.pem",
- "fixtures/https/server-rogue-cert.pem", "fixtures/https/server-rogue-key.pem")
-}
-
-func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine {
- t := std_log.New(os.Stderr, "", 0)
- root, err := newTestDirectory(unitTestStoreBase)
- if err != nil {
- t.Fatal(err)
- }
- // FIXME: here we don't use NewTestEngine because it configures the daemon with Autorestart=false,
- // and we want to set it to true.
-
- eng := newTestEngine(t, true, root)
-
- // Spawn a Daemon
- go func() {
- log.Debugf("Spawning https daemon for integration tests")
- listenURL := &url.URL{
- Scheme: testDaemonHttpsProto,
- Host: addr,
- }
- job := eng.Job("serveapi", listenURL.String())
- job.SetenvBool("Logging", true)
- job.SetenvBool("Tls", true)
- job.SetenvBool("TlsVerify", true)
- job.Setenv("TlsCa", cacert)
- job.Setenv("TlsCert", cert)
- job.Setenv("TlsKey", key)
- if err := job.Run(); err != nil {
- log.Fatalf("Unable to spawn the test daemon: %s", err)
- }
- }()
-
- // Give some time to ListenAndServer to actually start
- time.Sleep(time.Second)
-
- if err := eng.Job("acceptconnections").Run(); err != nil {
- log.Fatalf("Unable to accept connections for test api: %s", err)
- }
- return eng
-}
-
-// FIXME: test that ImagePull(json=true) send correct json output
-
-func GetTestImage(daemon *daemon.Daemon) *image.Image {
- imgs, err := daemon.Graph().Map()
- if err != nil {
- log.Fatalf("Unable to get the test image: %s", err)
- }
- for _, image := range imgs {
- if image.ID == unitTestImageID {
- return image
- }
- }
- log.Fatalf("Test image %v not found in %s: %s", unitTestImageID, daemon.Graph().Root, imgs)
- return nil
-}
-
-func TestDaemonCreate(t *testing.T) {
- daemon := mkDaemon(t)
- defer nuke(daemon)
-
- // Make sure we start we 0 containers
- if len(daemon.List()) != 0 {
- t.Errorf("Expected 0 containers, %v found", len(daemon.List()))
- }
-
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"ls", "-al"},
- },
- &runconfig.HostConfig{},
- "",
- )
- if err != nil {
- t.Fatal(err)
- }
-
- defer func() {
- if err := daemon.Rm(container); err != nil {
- t.Error(err)
- }
- }()
-
- // Make sure we can find the newly created container with List()
- if len(daemon.List()) != 1 {
- t.Errorf("Expected 1 container, %v found", len(daemon.List()))
- }
-
- // Make sure the container List() returns is the right one
- if daemon.List()[0].ID != container.ID {
- t.Errorf("Unexpected container %v returned by List", daemon.List()[0])
- }
-
- // Make sure we can get the container with Get()
- if _, err := daemon.Get(container.ID); err != nil {
- t.Errorf("Unable to get newly created container")
- }
-
- // Make sure it is the right container
- if c, _ := daemon.Get(container.ID); c != container {
- t.Errorf("Get() returned the wrong container")
- }
-
- // Make sure Exists returns it as existing
- if !daemon.Exists(container.ID) {
- t.Errorf("Exists() returned false for a newly created container")
- }
-
- // Test that conflict error displays correct details
- testContainer, _, _ := daemon.Create(
- &runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"ls", "-al"},
- },
- &runconfig.HostConfig{},
- "conflictname",
- )
- if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: []string{"ls", "-al"}}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), common.TruncateID(testContainer.ID)) {
- t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err)
- }
-
- // Make sure create with bad parameters returns an error
- if _, _, err = daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID}, &runconfig.HostConfig{}, ""); err == nil {
- t.Fatal("Builder.Create should throw an error when Cmd is missing")
- }
-
- if _, _, err := daemon.Create(
- &runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{},
- },
- &runconfig.HostConfig{},
- "",
- ); err == nil {
- t.Fatal("Builder.Create should throw an error when Cmd is empty")
- }
-
- config := &runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"/bin/ls"},
- PortSpecs: []string{"80"},
- }
- container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "")
-
- _, err = daemon.Commit(container, "testrepo", "testtag", "", "", true, config)
- if err != nil {
- t.Error(err)
- }
-
- // test expose 80:8000
- container, warnings, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"ls", "-al"},
- PortSpecs: []string{"80:8000"},
- },
- &runconfig.HostConfig{},
- "",
- )
- if err != nil {
- t.Fatal(err)
- }
- if warnings == nil || len(warnings) != 1 {
- t.Error("Expected a warning, got none")
- }
-}
-
-func TestDestroy(t *testing.T) {
- daemon := mkDaemon(t)
- defer nuke(daemon)
-
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"ls", "-al"},
- },
- &runconfig.HostConfig{},
- "")
- if err != nil {
- t.Fatal(err)
- }
- // Destroy
- if err := daemon.Rm(container); err != nil {
- t.Error(err)
- }
-
- // Make sure daemon.Exists() behaves correctly
- if daemon.Exists("test_destroy") {
- t.Errorf("Exists() returned true")
- }
-
- // Make sure daemon.List() doesn't list the destroyed container
- if len(daemon.List()) != 0 {
- t.Errorf("Expected 0 container, %v found", len(daemon.List()))
- }
-
- // Make sure daemon.Get() refuses to return the unexisting container
- if c, _ := daemon.Get(container.ID); c != nil {
- t.Errorf("Got a container that should not exist")
- }
-
- // Test double destroy
- if err := daemon.Rm(container); err == nil {
- // It should have failed
- t.Errorf("Double destroy did not fail")
- }
-}
-
-func TestGet(t *testing.T) {
- daemon := mkDaemon(t)
- defer nuke(daemon)
-
- container1, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t)
- defer daemon.Rm(container1)
-
- container2, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t)
- defer daemon.Rm(container2)
-
- container3, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t)
- defer daemon.Rm(container3)
-
- if c, _ := daemon.Get(container1.ID); c != container1 {
- t.Errorf("Get(test1) returned %v while expecting %v", c, container1)
- }
-
- if c, _ := daemon.Get(container2.ID); c != container2 {
- t.Errorf("Get(test2) returned %v while expecting %v", c, container2)
- }
-
- if c, _ := daemon.Get(container3.ID); c != container3 {
- t.Errorf("Get(test3) returned %v while expecting %v", c, container3)
- }
-
-}
-
-func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) {
- var (
- err error
- id string
- outputBuffer = bytes.NewBuffer(nil)
- strPort string
- eng = NewTestEngine(t)
- daemon = mkDaemonFromEngine(eng, t)
- port = 5554
- p nat.Port
- )
- defer func() {
- if err != nil {
- daemon.Nuke()
- }
- }()
-
- for {
- port += 1
- strPort = strconv.Itoa(port)
- var cmd string
- if proto == "tcp" {
- cmd = "socat TCP-LISTEN:" + strPort + ",reuseaddr,fork EXEC:/bin/cat"
- } else if proto == "udp" {
- cmd = "socat UDP-RECVFROM:" + strPort + ",fork EXEC:/bin/cat"
- } else {
- t.Fatal(fmt.Errorf("Unknown protocol %v", proto))
- }
- ep := make(map[nat.Port]struct{}, 1)
- p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto))
- ep[p] = struct{}{}
-
- jobCreate := eng.Job("create")
- jobCreate.Setenv("Image", unitTestImageID)
- jobCreate.SetenvList("Cmd", []string{"sh", "-c", cmd})
- jobCreate.SetenvList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)})
- jobCreate.SetenvJson("ExposedPorts", ep)
- jobCreate.Stdout.Add(outputBuffer)
- if err := jobCreate.Run(); err != nil {
- t.Fatal(err)
- }
- id = engine.Tail(outputBuffer, 1)
- // FIXME: this relies on the undocumented behavior of daemon.Create
- // which will return a nil error AND container if the exposed ports
- // are invalid. That behavior should be fixed!
- if id != "" {
- break
- }
- t.Logf("Port %v already in use, trying another one", strPort)
-
- }
-
- jobStart := eng.Job("start", id)
- portBindings := make(map[nat.Port][]nat.PortBinding)
- portBindings[p] = []nat.PortBinding{
- {},
- }
- if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil {
- t.Fatal(err)
- }
- if err := jobStart.Run(); err != nil {
- t.Fatal(err)
- }
-
- container, err := daemon.Get(id)
- if err != nil {
- t.Fatal(err)
- }
-
- setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
- for !container.IsRunning() {
- time.Sleep(10 * time.Millisecond)
- }
- })
-
- // Even if the state is running, lets give some time to lxc to spawn the process
- container.WaitStop(500 * time.Millisecond)
-
- strPort = container.NetworkSettings.Ports[p][0].HostPort
- return daemon, container, strPort
-}
-
-// Run a container with a TCP port allocated, and test that it can receive connections on localhost
-func TestAllocateTCPPortLocalhost(t *testing.T) {
- daemon, container, port := startEchoServerContainer(t, "tcp")
- defer nuke(daemon)
- defer container.Kill()
-
- for i := 0; i != 10; i++ {
- conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port))
- if err != nil {
- t.Fatal(err)
- }
- defer conn.Close()
-
- input := bytes.NewBufferString("well hello there\n")
- _, err = conn.Write(input.Bytes())
- if err != nil {
- t.Fatal(err)
- }
- buf := make([]byte, 16)
- read := 0
- conn.SetReadDeadline(time.Now().Add(3 * time.Second))
- read, err = conn.Read(buf)
- if err != nil {
- if err, ok := err.(*net.OpError); ok {
- if err.Err == syscall.ECONNRESET {
- t.Logf("Connection reset by the proxy, socat is probably not listening yet, trying again in a sec")
- conn.Close()
- time.Sleep(time.Second)
- continue
- }
- if err.Timeout() {
- t.Log("Timeout, trying again")
- conn.Close()
- continue
- }
- }
- t.Fatal(err)
- }
- output := string(buf[:read])
- if !strings.Contains(output, "well hello there") {
- t.Fatal(fmt.Errorf("[%v] doesn't contain [well hello there]", output))
- } else {
- return
- }
- }
-
- t.Fatal("No reply from the container")
-}
-
-// Run a container with an UDP port allocated, and test that it can receive connections on localhost
-func TestAllocateUDPPortLocalhost(t *testing.T) {
- daemon, container, port := startEchoServerContainer(t, "udp")
- defer nuke(daemon)
- defer container.Kill()
-
- conn, err := net.Dial("udp", fmt.Sprintf("localhost:%v", port))
- if err != nil {
- t.Fatal(err)
- }
- defer conn.Close()
-
- input := bytes.NewBufferString("well hello there\n")
- buf := make([]byte, 16)
- // Try for a minute, for some reason the select in socat may take ages
- // to return even though everything on the path seems fine (i.e: the
- // UDPProxy forwards the traffic correctly and you can see the packets
- // on the interface from within the container).
- for i := 0; i != 120; i++ {
- _, err := conn.Write(input.Bytes())
- if err != nil {
- t.Fatal(err)
- }
- conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
- read, err := conn.Read(buf)
- if err == nil {
- output := string(buf[:read])
- if strings.Contains(output, "well hello there") {
- return
- }
- }
- }
-
- t.Fatal("No reply from the container")
-}
-
-func TestRestore(t *testing.T) {
- eng := NewTestEngine(t)
- daemon1 := mkDaemonFromEngine(eng, t)
- defer daemon1.Nuke()
- // Create a container with one instance of docker
- container1, _, _ := mkContainer(daemon1, []string{"_", "ls", "-al"}, t)
- defer daemon1.Rm(container1)
-
- // Create a second container meant to be killed
- container2, _, _ := mkContainer(daemon1, []string{"-i", "_", "/bin/cat"}, t)
- defer daemon1.Rm(container2)
-
- // Start the container non blocking
- if err := container2.Start(); err != nil {
- t.Fatal(err)
- }
-
- if !container2.IsRunning() {
- t.Fatalf("Container %v should appear as running but isn't", container2.ID)
- }
-
- // Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
- cStdin := container2.StdinPipe()
- cStdin.Close()
- if _, err := container2.WaitStop(2 * time.Second); err != nil {
- t.Fatal(err)
- }
- container2.SetRunning(42)
- container2.ToDisk()
-
- if len(daemon1.List()) != 2 {
- t.Errorf("Expected 2 container, %v found", len(daemon1.List()))
- }
- if err := container1.Run(); err != nil {
- t.Fatal(err)
- }
-
- if !container2.IsRunning() {
- t.Fatalf("Container %v should appear as running but isn't", container2.ID)
- }
-
- // Here are are simulating a docker restart - that is, reloading all containers
- // from scratch
- eng = newTestEngine(t, false, daemon1.Config().Root)
- daemon2 := mkDaemonFromEngine(eng, t)
- if len(daemon2.List()) != 2 {
- t.Errorf("Expected 2 container, %v found", len(daemon2.List()))
- }
- runningCount := 0
- for _, c := range daemon2.List() {
- if c.IsRunning() {
- t.Errorf("Running container found: %v (%v)", c.ID, c.Path)
- runningCount++
- }
- }
- if runningCount != 0 {
- t.Fatalf("Expected 0 container alive, %d found", runningCount)
- }
- container3, err := daemon2.Get(container1.ID)
- if err != nil {
- t.Fatal("Unable to Get container")
- }
- if err := container3.Run(); err != nil {
- t.Fatal(err)
- }
- container2.SetStopped(&execdriver.ExitStatus{ExitCode: 0})
-}
-
-func TestDefaultContainerName(t *testing.T) {
- eng := NewTestEngine(t)
- daemon := mkDaemonFromEngine(eng, t)
- defer nuke(daemon)
-
- config, _, _, err := parseRun([]string{unitTestImageID, "echo test"})
- if err != nil {
- t.Fatal(err)
- }
-
- container, err := daemon.Get(createNamedTestContainer(eng, config, t, "some_name"))
- if err != nil {
- t.Fatal(err)
- }
- containerID := container.ID
-
- if container.Name != "/some_name" {
- t.Fatalf("Expect /some_name got %s", container.Name)
- }
-
- c, err := daemon.Get("/some_name")
- if err != nil {
- t.Fatalf("Couldn't retrieve test container as /some_name")
- }
- if c.ID != containerID {
- t.Fatalf("Container /some_name has ID %s instead of %s", c.ID, containerID)
- }
-}
-
-func TestRandomContainerName(t *testing.T) {
- eng := NewTestEngine(t)
- daemon := mkDaemonFromEngine(eng, t)
- defer nuke(daemon)
-
- config, _, _, err := parseRun([]string{GetTestImage(daemon).ID, "echo test"})
- if err != nil {
- t.Fatal(err)
- }
-
- container, err := daemon.Get(createTestContainer(eng, config, t))
- if err != nil {
- t.Fatal(err)
- }
- containerID := container.ID
-
- if container.Name == "" {
- t.Fatalf("Expected not empty container name")
- }
-
- if c, err := daemon.Get(container.Name); err != nil {
- log.Fatalf("Could not lookup container %s by its name", container.Name)
- } else if c.ID != containerID {
- log.Fatalf("Looking up container name %s returned id %s instead of %s", container.Name, c.ID, containerID)
- }
-}
-
-func TestContainerNameValidation(t *testing.T) {
- eng := NewTestEngine(t)
- daemon := mkDaemonFromEngine(eng, t)
- defer nuke(daemon)
-
- for _, test := range []struct {
- Name string
- Valid bool
- }{
- {"abc-123_AAA.1", true},
- {"\000asdf", false},
- } {
- config, _, _, err := parseRun([]string{unitTestImageID, "echo test"})
- if err != nil {
- if !test.Valid {
- continue
- }
- t.Fatal(err)
- }
-
- var outputBuffer = bytes.NewBuffer(nil)
- job := eng.Job("create", test.Name)
- if err := job.ImportEnv(config); err != nil {
- t.Fatal(err)
- }
- job.Stdout.Add(outputBuffer)
- if err := job.Run(); err != nil {
- if !test.Valid {
- continue
- }
- t.Fatal(err)
- }
-
- container, err := daemon.Get(engine.Tail(outputBuffer, 1))
- if err != nil {
- t.Fatal(err)
- }
-
- if container.Name != "/"+test.Name {
- t.Fatalf("Expect /%s got %s", test.Name, container.Name)
- }
-
- if c, err := daemon.Get("/" + test.Name); err != nil {
- t.Fatalf("Couldn't retrieve test container as /%s", test.Name)
- } else if c.ID != container.ID {
- t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID)
- }
- }
-
-}
-
-func TestLinkChildContainer(t *testing.T) {
- eng := NewTestEngine(t)
- daemon := mkDaemonFromEngine(eng, t)
- defer nuke(daemon)
-
- config, _, _, err := parseRun([]string{unitTestImageID, "echo test"})
- if err != nil {
- t.Fatal(err)
- }
-
- container, err := daemon.Get(createNamedTestContainer(eng, config, t, "/webapp"))
- if err != nil {
- t.Fatal(err)
- }
-
- webapp, err := daemon.GetByName("/webapp")
- if err != nil {
- t.Fatal(err)
- }
-
- if webapp.ID != container.ID {
- t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
- }
-
- config, _, _, err = parseRun([]string{GetTestImage(daemon).ID, "echo test"})
- if err != nil {
- t.Fatal(err)
- }
-
- childContainer, err := daemon.Get(createTestContainer(eng, config, t))
- if err != nil {
- t.Fatal(err)
- }
-
- if err := daemon.RegisterLink(webapp, childContainer, "db"); err != nil {
- t.Fatal(err)
- }
-
- // Get the child by it's new name
- db, err := daemon.GetByName("/webapp/db")
- if err != nil {
- t.Fatal(err)
- }
- if db.ID != childContainer.ID {
- t.Fatalf("Expect db id to match container id: %s != %s", db.ID, childContainer.ID)
- }
-}
-
-func TestGetAllChildren(t *testing.T) {
- eng := NewTestEngine(t)
- daemon := mkDaemonFromEngine(eng, t)
- defer nuke(daemon)
-
- config, _, _, err := parseRun([]string{unitTestImageID, "echo test"})
- if err != nil {
- t.Fatal(err)
- }
-
- container, err := daemon.Get(createNamedTestContainer(eng, config, t, "/webapp"))
- if err != nil {
- t.Fatal(err)
- }
-
- webapp, err := daemon.GetByName("/webapp")
- if err != nil {
- t.Fatal(err)
- }
-
- if webapp.ID != container.ID {
- t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
- }
-
- config, _, _, err = parseRun([]string{unitTestImageID, "echo test"})
- if err != nil {
- t.Fatal(err)
- }
-
- childContainer, err := daemon.Get(createTestContainer(eng, config, t))
- if err != nil {
- t.Fatal(err)
- }
-
- if err := daemon.RegisterLink(webapp, childContainer, "db"); err != nil {
- t.Fatal(err)
- }
-
- children, err := daemon.Children("/webapp")
- if err != nil {
- t.Fatal(err)
- }
-
- if children == nil {
- t.Fatal("Children should not be nil")
- }
- if len(children) == 0 {
- t.Fatal("Children should not be empty")
- }
-
- for key, value := range children {
- if key != "/webapp/db" {
- t.Fatalf("Expected /webapp/db got %s", key)
- }
- if value.ID != childContainer.ID {
- t.Fatalf("Expected id %s got %s", childContainer.ID, value.ID)
- }
- }
-}
-
-func TestDestroyWithInitLayer(t *testing.T) {
- daemon := mkDaemon(t)
- defer nuke(daemon)
-
- container, _, err := daemon.Create(&runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"ls", "-al"},
- },
- &runconfig.HostConfig{},
- "")
-
- if err != nil {
- t.Fatal(err)
- }
- // Destroy
- if err := daemon.Rm(container); err != nil {
- t.Fatal(err)
- }
-
- // Make sure daemon.Exists() behaves correctly
- if daemon.Exists("test_destroy") {
- t.Fatalf("Exists() returned true")
- }
-
- // Make sure daemon.List() doesn't list the destroyed container
- if len(daemon.List()) != 0 {
- t.Fatalf("Expected 0 container, %v found", len(daemon.List()))
- }
-
- driver := daemon.Graph().Driver()
-
- // Make sure that the container does not exist in the driver
- if _, err := driver.Get(container.ID, ""); err == nil {
- t.Fatal("Conttainer should not exist in the driver")
- }
-
- // Make sure that the init layer is removed from the driver
- if _, err := driver.Get(fmt.Sprintf("%s-init", container.ID), ""); err == nil {
- t.Fatal("Container's init layer should not exist in the driver")
- }
-}
diff --git a/integration/server_test.go b/integration/server_test.go
deleted file mode 100644
index 6d12ad35a..000000000
--- a/integration/server_test.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package docker
-
-import (
- "bytes"
- "testing"
- "time"
-
- "github.com/docker/docker/builder"
- "github.com/docker/docker/engine"
-)
-
-func TestCreateNumberHostname(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- config, _, _, err := parseRun([]string{"-h", "web.0", unitTestImageID, "echo test"})
- if err != nil {
- t.Fatal(err)
- }
-
- createTestContainer(eng, config, t)
-}
-
-func TestCommit(t *testing.T) {
- eng := NewTestEngine(t)
- b := &builder.BuilderJob{Engine: eng}
- b.Install()
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"})
- if err != nil {
- t.Fatal(err)
- }
-
- id := createTestContainer(eng, config, t)
-
- job := eng.Job("commit", id)
- job.Setenv("repo", "testrepo")
- job.Setenv("tag", "testtag")
- job.SetenvJson("config", config)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestMergeConfigOnCommit(t *testing.T) {
- eng := NewTestEngine(t)
- b := &builder.BuilderJob{Engine: eng}
- b.Install()
- runtime := mkDaemonFromEngine(eng, t)
- defer runtime.Nuke()
-
- container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t)
- defer runtime.Rm(container1)
-
- config, _, _, err := parseRun([]string{container1.ID, "cat /tmp/foo"})
- if err != nil {
- t.Error(err)
- }
-
- job := eng.Job("commit", container1.ID)
- job.Setenv("repo", "testrepo")
- job.Setenv("tag", "testtag")
- job.SetenvJson("config", config)
- var outputBuffer = bytes.NewBuffer(nil)
- job.Stdout.Add(outputBuffer)
- if err := job.Run(); err != nil {
- t.Error(err)
- }
-
- container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t)
- defer runtime.Rm(container2)
-
- job = eng.Job("container_inspect", container1.Name)
- baseContainer, _ := job.Stdout.AddEnv()
- if err := job.Run(); err != nil {
- t.Error(err)
- }
-
- job = eng.Job("container_inspect", container2.Name)
- commitContainer, _ := job.Stdout.AddEnv()
- if err := job.Run(); err != nil {
- t.Error(err)
- }
-
- baseConfig := baseContainer.GetSubEnv("Config")
- commitConfig := commitContainer.GetSubEnv("Config")
-
- if commitConfig.Get("Env") != baseConfig.Get("Env") {
- t.Fatalf("Env config in committed container should be %v, was %v",
- baseConfig.Get("Env"), commitConfig.Get("Env"))
- }
-
- if baseConfig.Get("Cmd") != "[\"echo test \\u003e /tmp/foo\"]" {
- t.Fatalf("Cmd in base container should be [\"echo test \\u003e /tmp/foo\"], was %s",
- baseConfig.Get("Cmd"))
- }
-
- if commitConfig.Get("Cmd") != "[\"cat /tmp/foo\"]" {
- t.Fatalf("Cmd in committed container should be [\"cat /tmp/foo\"], was %s",
- commitConfig.Get("Cmd"))
- }
-}
-
-func TestRestartKillWait(t *testing.T) {
- eng := NewTestEngine(t)
- runtime := mkDaemonFromEngine(eng, t)
- defer runtime.Nuke()
-
- config, hostConfig, _, err := parseRun([]string{"-i", unitTestImageID, "/bin/cat"})
- if err != nil {
- t.Fatal(err)
- }
-
- id := createTestContainer(eng, config, t)
-
- job := eng.Job("containers")
- job.SetenvBool("all", true)
- outs, err := job.Stdout.AddListTable()
- if err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- if len(outs.Data) != 1 {
- t.Errorf("Expected 1 container, %v found", len(outs.Data))
- }
-
- job = eng.Job("start", id)
- if err := job.ImportEnv(hostConfig); err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- job = eng.Job("kill", id)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- eng = newTestEngine(t, false, runtime.Config().Root)
-
- job = eng.Job("containers")
- job.SetenvBool("all", true)
- outs, err = job.Stdout.AddListTable()
- if err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- if len(outs.Data) != 1 {
- t.Errorf("Expected 1 container, %v found", len(outs.Data))
- }
-
- setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() {
- job = eng.Job("wait", outs.Data[0].Get("Id"))
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- })
-}
-
-func TestCreateStartRestartStopStartKillRm(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- config, hostConfig, _, err := parseRun([]string{"-i", unitTestImageID, "/bin/cat"})
- if err != nil {
- t.Fatal(err)
- }
-
- id := createTestContainer(eng, config, t)
-
- job := eng.Job("containers")
- job.SetenvBool("all", true)
- outs, err := job.Stdout.AddListTable()
- if err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- if len(outs.Data) != 1 {
- t.Errorf("Expected 1 container, %v found", len(outs.Data))
- }
-
- job = eng.Job("start", id)
- if err := job.ImportEnv(hostConfig); err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- job = eng.Job("restart", id)
- job.SetenvInt("t", 2)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- job = eng.Job("stop", id)
- job.SetenvInt("t", 2)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- job = eng.Job("start", id)
- if err := job.ImportEnv(hostConfig); err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- if err := eng.Job("kill", id).Run(); err != nil {
- t.Fatal(err)
- }
-
- // FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty")
- job = eng.Job("rm", id)
- job.SetenvBool("removeVolume", true)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- job = eng.Job("containers")
- job.SetenvBool("all", true)
- outs, err = job.Stdout.AddListTable()
- if err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- if len(outs.Data) != 0 {
- t.Errorf("Expected 0 container, %v found", len(outs.Data))
- }
-}
-
-func TestRunWithTooLowMemoryLimit(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
- job := eng.Job("create")
- job.Setenv("Image", unitTestImageID)
- job.Setenv("Memory", "524287")
- job.Setenv("CpuShares", "1000")
- job.SetenvList("Cmd", []string{"/bin/cat"})
- if err := job.Run(); err == nil {
- t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
- }
-}
-
-func TestImagesFilter(t *testing.T) {
- eng := NewTestEngine(t)
- defer nuke(mkDaemonFromEngine(eng, t))
-
- if err := eng.Job("tag", unitTestImageName, "utest", "tag1").Run(); err != nil {
- t.Fatal(err)
- }
-
- if err := eng.Job("tag", unitTestImageName, "utest/docker", "tag2").Run(); err != nil {
- t.Fatal(err)
- }
-
- if err := eng.Job("tag", unitTestImageName, "utest:5000/docker", "tag3").Run(); err != nil {
- t.Fatal(err)
- }
-
- images := getImages(eng, t, false, "utest*/*")
-
- if len(images.Data[0].GetList("RepoTags")) != 2 {
- t.Fatal("incorrect number of matches returned")
- }
-
- images = getImages(eng, t, false, "utest")
-
- if len(images.Data[0].GetList("RepoTags")) != 1 {
- t.Fatal("incorrect number of matches returned")
- }
-
- images = getImages(eng, t, false, "utest*")
-
- if len(images.Data[0].GetList("RepoTags")) != 1 {
- t.Fatal("incorrect number of matches returned")
- }
-
- images = getImages(eng, t, false, "*5000*/*")
-
- if len(images.Data[0].GetList("RepoTags")) != 1 {
- t.Fatal("incorrect number of matches returned")
- }
-}
diff --git a/integration/utils_test.go b/integration/utils_test.go
deleted file mode 100644
index 2e90e4f51..000000000
--- a/integration/utils_test.go
+++ /dev/null
@@ -1,360 +0,0 @@
-package docker
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "path"
- "path/filepath"
- "strings"
- "testing"
- "time"
-
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
-
- "github.com/docker/docker/builtins"
- "github.com/docker/docker/daemon"
- "github.com/docker/docker/engine"
- flag "github.com/docker/docker/pkg/mflag"
- "github.com/docker/docker/registry"
- "github.com/docker/docker/runconfig"
- "github.com/docker/docker/utils"
-)
-
-type Fataler interface {
- Fatal(...interface{})
-}
-
-// This file contains utility functions for docker's unit test suite.
-// It has to be named XXX_test.go, apparently, in other to access private functions
-// from other XXX_test.go functions.
-
-// Create a temporary daemon suitable for unit testing.
-// Call t.Fatal() at the first error.
-func mkDaemon(f Fataler) *daemon.Daemon {
- eng := newTestEngine(f, false, "")
- return mkDaemonFromEngine(eng, f)
-}
-
-func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) {
- job := eng.Job("create", name)
- if err := job.ImportEnv(config); err != nil {
- f.Fatal(err)
- }
- var outputBuffer = bytes.NewBuffer(nil)
- job.Stdout.Add(outputBuffer)
- if err := job.Run(); err != nil {
- f.Fatal(err)
- }
- return engine.Tail(outputBuffer, 1)
-}
-
-func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) {
- return createNamedTestContainer(eng, config, f, "")
-}
-
-func startContainer(eng *engine.Engine, id string, t Fataler) {
- job := eng.Job("start", id)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-}
-
-func containerRun(eng *engine.Engine, id string, t Fataler) {
- startContainer(eng, id, t)
- containerWait(eng, id, t)
-}
-
-func containerFileExists(eng *engine.Engine, id, dir string, t Fataler) bool {
- c := getContainer(eng, id, t)
- if err := c.Mount(); err != nil {
- t.Fatal(err)
- }
- defer c.Unmount()
- if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil {
- if os.IsNotExist(err) {
- return false
- }
- t.Fatal(err)
- }
- return true
-}
-
-func containerAttach(eng *engine.Engine, id string, t Fataler) (io.WriteCloser, io.ReadCloser) {
- c := getContainer(eng, id, t)
- i := c.StdinPipe()
- o := c.StdoutPipe()
- return i, o
-}
-
-func containerWait(eng *engine.Engine, id string, t Fataler) int {
- ex, _ := getContainer(eng, id, t).WaitStop(-1 * time.Second)
- return ex
-}
-
-func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error {
- _, err := getContainer(eng, id, t).WaitStop(500 * time.Millisecond)
- return err
-}
-
-func containerKill(eng *engine.Engine, id string, t Fataler) {
- if err := eng.Job("kill", id).Run(); err != nil {
- t.Fatal(err)
- }
-}
-
-func containerRunning(eng *engine.Engine, id string, t Fataler) bool {
- return getContainer(eng, id, t).IsRunning()
-}
-
-func containerAssertExists(eng *engine.Engine, id string, t Fataler) {
- getContainer(eng, id, t)
-}
-
-func containerAssertNotExists(eng *engine.Engine, id string, t Fataler) {
- daemon := mkDaemonFromEngine(eng, t)
- if c, _ := daemon.Get(id); c != nil {
- t.Fatal(fmt.Errorf("Container %s should not exist", id))
- }
-}
-
-// assertHttpNotError expect the given response to not have an error.
-// Otherwise the it causes the test to fail.
-func assertHttpNotError(r *httptest.ResponseRecorder, t Fataler) {
- // Non-error http status are [200, 400)
- if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest {
- t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code))
- }
-}
-
-// assertHttpError expect the given response to have an error.
-// Otherwise the it causes the test to fail.
-func assertHttpError(r *httptest.ResponseRecorder, t Fataler) {
- // Non-error http status are [200, 400)
- if !(r.Code < http.StatusOK || r.Code >= http.StatusBadRequest) {
- t.Fatal(fmt.Errorf("Unexpected http success code: %v", r.Code))
- }
-}
-
-func getContainer(eng *engine.Engine, id string, t Fataler) *daemon.Container {
- daemon := mkDaemonFromEngine(eng, t)
- c, err := daemon.Get(id)
- if err != nil {
- t.Fatal(err)
- }
- return c
-}
-
-func mkDaemonFromEngine(eng *engine.Engine, t Fataler) *daemon.Daemon {
- iDaemon := eng.Hack_GetGlobalVar("httpapi.daemon")
- if iDaemon == nil {
- panic("Legacy daemon field not set in engine")
- }
- daemon, ok := iDaemon.(*daemon.Daemon)
- if !ok {
- panic("Legacy daemon field in engine does not cast to *daemon.Daemon")
- }
- return daemon
-}
-
-func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
- if root == "" {
- if dir, err := newTestDirectory(unitTestStoreBase); err != nil {
- t.Fatal(err)
- } else {
- root = dir
- }
- }
- os.MkdirAll(root, 0700)
-
- eng := engine.New()
- eng.Logging = false
- // Load default plugins
- if err := builtins.Register(eng); err != nil {
- t.Fatal(err)
- }
- // load registry service
- if err := registry.NewService(nil).Install(eng); err != nil {
- t.Fatal(err)
- }
-
- // (This is manually copied and modified from main() until we have a more generic plugin system)
- cfg := &daemon.Config{
- Root: root,
- AutoRestart: autorestart,
- ExecDriver: "native",
- // Either InterContainerCommunication or EnableIptables must be set,
- // otherwise NewDaemon will fail because of conflicting settings.
- InterContainerCommunication: true,
- TrustKeyPath: filepath.Join(root, "key.json"),
- LogConfig: runconfig.LogConfig{Type: "json-file"},
- }
- d, err := daemon.NewDaemon(cfg, eng)
- if err != nil {
- t.Fatal(err)
- }
- if err := d.Install(eng); err != nil {
- t.Fatal(err)
- }
- return eng
-}
-
-func NewTestEngine(t Fataler) *engine.Engine {
- return newTestEngine(t, false, "")
-}
-
-func newTestDirectory(templateDir string) (dir string, err error) {
- return utils.TestDirectory(templateDir)
-}
-
-func getCallerName(depth int) string {
- return utils.GetCallerName(depth)
-}
-
-// Write `content` to the file at path `dst`, creating it if necessary,
-// as well as any missing directories.
-// The file is truncated if it already exists.
-// Call t.Fatal() at the first error.
-func writeFile(dst, content string, t *testing.T) {
- // Create subdirectories if necessary
- if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) {
- t.Fatal(err)
- }
- f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700)
- if err != nil {
- t.Fatal(err)
- }
- // Write content (truncate if it exists)
- if _, err := io.Copy(f, strings.NewReader(content)); err != nil {
- t.Fatal(err)
- }
-}
-
-// Return the contents of file at path `src`.
-// Call t.Fatal() at the first error (including if the file doesn't exist)
-func readFile(src string, t *testing.T) (content string) {
- f, err := os.Open(src)
- if err != nil {
- t.Fatal(err)
- }
- data, err := ioutil.ReadAll(f)
- if err != nil {
- t.Fatal(err)
- }
- return string(data)
-}
-
-// Create a test container from the given daemon `r` and run arguments `args`.
-// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is
-// dynamically replaced by the current test image.
-// The caller is responsible for destroying the container.
-// Call t.Fatal() at the first error.
-func mkContainer(r *daemon.Daemon, args []string, t *testing.T) (*daemon.Container, *runconfig.HostConfig, error) {
- config, hc, _, err := parseRun(args)
- defer func() {
- if err != nil && t != nil {
- t.Fatal(err)
- }
- }()
- if err != nil {
- return nil, nil, err
- }
- if config.Image == "_" {
- config.Image = GetTestImage(r).ID
- }
- c, _, err := r.Create(config, nil, "")
- if err != nil {
- return nil, nil, err
- }
- // NOTE: hostConfig is ignored.
- // If `args` specify privileged mode, custom lxc conf, external mount binds,
- // port redirects etc. they will be ignored.
- // This is because the correct way to set these things is to pass environment
- // to the `start` job.
- // FIXME: this helper function should be deprecated in favor of calling
- // `create` and `start` jobs directly.
- return c, hc, nil
-}
-
-// Create a test container, start it, wait for it to complete, destroy it,
-// and return its standard output as a string.
-// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
-// If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally.
-func runContainer(eng *engine.Engine, r *daemon.Daemon, args []string, t *testing.T) (output string, err error) {
- defer func() {
- if err != nil && t != nil {
- t.Fatal(err)
- }
- }()
- container, hc, err := mkContainer(r, args, t)
- if err != nil {
- return "", err
- }
- defer r.Rm(container)
- stdout := container.StdoutPipe()
- defer stdout.Close()
-
- job := eng.Job("start", container.ID)
- if err := job.ImportEnv(hc); err != nil {
- return "", err
- }
- if err := job.Run(); err != nil {
- return "", err
- }
-
- container.WaitStop(-1 * time.Second)
- data, err := ioutil.ReadAll(stdout)
- if err != nil {
- return "", err
- }
- output = string(data)
- return
-}
-
-// FIXME: this is duplicated from graph_test.go in the docker package.
-func fakeTar() (io.ReadCloser, error) {
- content := []byte("Hello world!\n")
- buf := new(bytes.Buffer)
- tw := tar.NewWriter(buf)
- for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
- hdr := new(tar.Header)
- hdr.Size = int64(len(content))
- hdr.Name = name
- if err := tw.WriteHeader(hdr); err != nil {
- return nil, err
- }
- tw.Write([]byte(content))
- }
- tw.Close()
- return ioutil.NopCloser(buf), nil
-}
-
-func getAllImages(eng *engine.Engine, t *testing.T) *engine.Table {
- return getImages(eng, t, true, "")
-}
-
-func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engine.Table {
- job := eng.Job("images")
- job.SetenvBool("all", all)
- job.Setenv("filter", filter)
- images, err := job.Stdout.AddListTable()
- if err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- return images
-
-}
-
-func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.FlagSet, error) {
- cmd := flag.NewFlagSet("run", flag.ContinueOnError)
- cmd.SetOutput(ioutil.Discard)
- cmd.Usage = nil
- return runconfig.Parse(cmd, args)
-}
diff --git a/integration/z_final_test.go b/integration/z_final_test.go
deleted file mode 100644
index ad1eb4340..000000000
--- a/integration/z_final_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package docker
-
-import (
- "github.com/docker/docker/utils"
- "runtime"
- "testing"
-)
-
-func displayFdGoroutines(t *testing.T) {
- t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
-}
-
-func TestFinal(t *testing.T) {
- nuke(globalDaemon)
- t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines)
- displayFdGoroutines(t)
-}
diff --git a/links/links.go b/links/links.go
index 96c18cc24..a756c8b0e 100644
--- a/links/links.go
+++ b/links/links.go
@@ -2,10 +2,10 @@ package links
import (
"fmt"
- "github.com/docker/docker/engine"
- "github.com/docker/docker/nat"
"path"
"strings"
+
+ "github.com/docker/docker/nat"
)
type Link struct {
@@ -15,10 +15,9 @@ type Link struct {
ChildEnvironment []string
Ports []nat.Port
IsEnabled bool
- eng *engine.Engine
}
-func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.Port]struct{}, eng *engine.Engine) (*Link, error) {
+func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.Port]struct{}) (*Link, error) {
var (
i int
@@ -36,7 +35,6 @@ func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.
ParentIP: parentIP,
ChildEnvironment: env,
Ports: ports,
- eng: eng,
}
return l, nil
@@ -107,8 +105,8 @@ func (l *Link) ToEnv() []string {
if l.ChildEnvironment != nil {
for _, v := range l.ChildEnvironment {
- parts := strings.Split(v, "=")
- if len(parts) != 2 {
+ parts := strings.SplitN(v, "=", 2)
+ if len(parts) < 2 {
continue
}
// Ignore a few variables that are added during docker build (and not really relevant to linked containers)
@@ -140,39 +138,10 @@ func (l *Link) getDefaultPort() *nat.Port {
}
func (l *Link) Enable() error {
- // -A == iptables append flag
- if err := l.toggle("-A", false); err != nil {
- return err
- }
l.IsEnabled = true
return nil
}
func (l *Link) Disable() {
- // We do not care about errors here because the link may not
- // exist in iptables
- // -D == iptables delete flag
- l.toggle("-D", true)
-
l.IsEnabled = false
}
-
-func (l *Link) toggle(action string, ignoreErrors bool) error {
- job := l.eng.Job("link", action)
-
- job.Setenv("ParentIP", l.ParentIP)
- job.Setenv("ChildIP", l.ChildIP)
- job.SetenvBool("IgnoreErrors", ignoreErrors)
-
- out := make([]string, len(l.Ports))
- for i, p := range l.Ports {
- out[i] = string(p)
- }
- job.SetenvList("Ports", out)
-
- if err := job.Run(); err != nil {
- // TODO: get ouput from job
- return err
- }
- return nil
-}
diff --git a/links/links_test.go b/links/links_test.go
index ba548fc5b..e639e2c42 100644
--- a/links/links_test.go
+++ b/links/links_test.go
@@ -2,16 +2,17 @@ package links
import (
"fmt"
- "github.com/docker/docker/nat"
"strings"
"testing"
+
+ "github.com/docker/docker/nat"
)
func TestLinkNaming(t *testing.T) {
ports := make(nat.PortSet)
ports[nat.Port("6379/tcp")] = struct{}{}
- link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, ports, nil)
+ link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, ports)
if err != nil {
t.Fatal(err)
}
@@ -41,7 +42,7 @@ func TestLinkNew(t *testing.T) {
ports := make(nat.PortSet)
ports[nat.Port("6379/tcp")] = struct{}{}
- link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, ports, nil)
+ link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, ports)
if err != nil {
t.Fatal(err)
}
@@ -72,7 +73,7 @@ func TestLinkEnv(t *testing.T) {
ports := make(nat.PortSet)
ports[nat.Port("6379/tcp")] = struct{}{}
- link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil)
+ link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports)
if err != nil {
t.Fatal(err)
}
@@ -115,7 +116,7 @@ func TestLinkMultipleEnv(t *testing.T) {
ports[nat.Port("6380/tcp")] = struct{}{}
ports[nat.Port("6381/tcp")] = struct{}{}
- link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil)
+ link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports)
if err != nil {
t.Fatal(err)
}
@@ -164,7 +165,7 @@ func TestLinkPortRangeEnv(t *testing.T) {
ports[nat.Port("6380/tcp")] = struct{}{}
ports[nat.Port("6381/tcp")] = struct{}{}
- link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil)
+ link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports)
if err != nil {
t.Fatal(err)
}
diff --git a/nat/nat.go b/nat/nat.go
index fdecf3f9d..2cec2e86c 100644
--- a/nat/nat.go
+++ b/nat/nat.go
@@ -34,6 +34,9 @@ func NewPort(proto, port string) Port {
}
func ParsePort(rawPort string) (int, error) {
+ if len(rawPort) == 0 {
+ return 0, nil
+ }
port, err := strconv.ParseUint(rawPort, 10, 16)
if err != nil {
return 0, err
diff --git a/nat/sort.go b/nat/sort.go
index f36c12f7b..6441936ff 100644
--- a/nat/sort.go
+++ b/nat/sort.go
@@ -1,6 +1,10 @@
package nat
-import "sort"
+import (
+ "sort"
+ "strconv"
+ "strings"
+)
type portSorter struct {
ports []Port
@@ -26,3 +30,63 @@ func Sort(ports []Port, predicate func(i, j Port) bool) {
s := &portSorter{ports, predicate}
sort.Sort(s)
}
+
+type portMapEntry struct {
+ port Port
+ binding PortBinding
+}
+
+type portMapSorter []portMapEntry
+
+func (s portMapSorter) Len() int { return len(s) }
+func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// sort the port so that the order is:
+// 1. port with larger specified bindings
+// 2. larger port
+// 3. port with tcp protocol
+func (s portMapSorter) Less(i, j int) bool {
+ pi, pj := s[i].port, s[j].port
+ hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort)
+ return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp")
+}
+
+// SortPortMap sorts the list of ports and their respected mapping. The ports
+// will explicit HostPort will be placed first.
+func SortPortMap(ports []Port, bindings PortMap) {
+ s := portMapSorter{}
+ for _, p := range ports {
+ if binding, ok := bindings[p]; ok {
+ for _, b := range binding {
+ s = append(s, portMapEntry{port: p, binding: b})
+ }
+ } else {
+ s = append(s, portMapEntry{port: p})
+ }
+ bindings[p] = []PortBinding{}
+ }
+
+ sort.Sort(s)
+ var (
+ i int
+ pm = make(map[Port]struct{})
+ )
+ // reorder ports
+ for _, entry := range s {
+ if _, ok := pm[entry.port]; !ok {
+ ports[i] = entry.port
+ pm[entry.port] = struct{}{}
+ i++
+ }
+ // reorder bindings for this port
+ bindings[entry.port] = append(bindings[entry.port], entry.binding)
+ }
+}
+
+func toInt(s string) int64 {
+ i, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ i = 0
+ }
+ return i
+}
diff --git a/nat/sort_test.go b/nat/sort_test.go
index 5d490e321..ba24cdbcb 100644
--- a/nat/sort_test.go
+++ b/nat/sort_test.go
@@ -2,6 +2,7 @@ package nat
import (
"fmt"
+ "reflect"
"testing"
)
@@ -39,3 +40,46 @@ func TestSortSamePortWithDifferentProto(t *testing.T) {
t.Fail()
}
}
+
+func TestSortPortMap(t *testing.T) {
+ ports := []Port{
+ Port("22/tcp"),
+ Port("22/udp"),
+ Port("8000/tcp"),
+ Port("6379/tcp"),
+ Port("9999/tcp"),
+ }
+
+ portMap := PortMap{
+ Port("22/tcp"): []PortBinding{
+ {},
+ },
+ Port("8000/tcp"): []PortBinding{
+ {},
+ },
+ Port("6379/tcp"): []PortBinding{
+ {},
+ {HostIp: "0.0.0.0", HostPort: "32749"},
+ },
+ Port("9999/tcp"): []PortBinding{
+ {HostIp: "0.0.0.0", HostPort: "40000"},
+ },
+ }
+
+ SortPortMap(ports, portMap)
+ if !reflect.DeepEqual(ports, []Port{
+ Port("9999/tcp"),
+ Port("6379/tcp"),
+ Port("8000/tcp"),
+ Port("22/tcp"),
+ Port("22/udp"),
+ }) {
+ t.Errorf("failed to prioritize port with explicit mappings, got %v", ports)
+ }
+ if pm := portMap[Port("6379/tcp")]; !reflect.DeepEqual(pm, []PortBinding{
+ {HostIp: "0.0.0.0", HostPort: "32749"},
+ {},
+ }) {
+ t.Errorf("failed to prioritize bindings with explicit mappings, got %v", pm)
+ }
+}
diff --git a/opts/opts.go b/opts/opts.go
index e867c0a21..c330c27a5 100644
--- a/opts/opts.go
+++ b/opts/opts.go
@@ -8,24 +8,36 @@ import (
"regexp"
"strings"
- "github.com/docker/docker/api"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/ulimit"
- "github.com/docker/docker/utils"
)
var (
- alphaRegexp = regexp.MustCompile(`[a-zA-Z]`)
- domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
+ alphaRegexp = regexp.MustCompile(`[a-zA-Z]`)
+ domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
+ DefaultHTTPHost = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
+ // TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
+ // is not supplied. A better longer term solution would be to use a named
+ // pipe as the default on the Windows daemon.
+ DefaultHTTPPort = 2375 // Default HTTP Port
+ DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket
)
func ListVar(values *[]string, names []string, usage string) {
flag.Var(newListOptsRef(values, nil), names, usage)
}
+func MapVar(values map[string]string, names []string, usage string) {
+ flag.Var(newMapOpt(values, nil), names, usage)
+}
+
+func LogOptsVar(values map[string]string, names []string, usage string) {
+ flag.Var(newMapOpt(values, ValidateLogOpts), names, usage)
+}
+
func HostListVar(values *[]string, names []string, usage string) {
- flag.Var(newListOptsRef(values, api.ValidateHost), names, usage)
+ flag.Var(newListOptsRef(values, ValidateHost), names, usage)
}
func IPListVar(values *[]string, names []string, usage string) {
@@ -126,10 +138,53 @@ func (opts *ListOpts) Len() int {
return len((*opts.values))
}
+//MapOpts type
+type MapOpts struct {
+ values map[string]string
+ validator ValidatorFctType
+}
+
+func (opts *MapOpts) Set(value string) error {
+ if opts.validator != nil {
+ v, err := opts.validator(value)
+ if err != nil {
+ return err
+ }
+ value = v
+ }
+ vals := strings.SplitN(value, "=", 2)
+ if len(vals) == 1 {
+ (opts.values)[vals[0]] = ""
+ } else {
+ (opts.values)[vals[0]] = vals[1]
+ }
+ return nil
+}
+
+func (opts *MapOpts) String() string {
+ return fmt.Sprintf("%v", map[string]string((opts.values)))
+}
+
+func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts {
+ return &MapOpts{
+ values: values,
+ validator: validator,
+ }
+}
+
// Validators
type ValidatorFctType func(val string) (string, error)
type ValidatorFctListType func(val string) ([]string, error)
+func ValidateLogOpts(val string) (string, error) {
+ allowedKeys := map[string]string{}
+ vals := strings.Split(val, "=")
+ if allowedKeys[vals[0]] != "" {
+ return val, nil
+ }
+ return "", fmt.Errorf("%s is not a valid log opt", vals[0])
+}
+
func ValidateAttach(val string) (string, error) {
s := strings.ToLower(val)
for _, str := range []string{"stdin", "stdout", "stderr"} {
@@ -141,7 +196,7 @@ func ValidateAttach(val string) (string, error) {
}
func ValidateLink(val string) (string, error) {
- if _, err := parsers.PartParser("name:alias", val); err != nil {
+ if _, _, err := parsers.ParseLink(val); err != nil {
return val, err
}
return val, nil
@@ -174,7 +229,7 @@ func ValidateEnv(val string) (string, error) {
if len(arr) > 1 {
return val, nil
}
- if !utils.DoesEnvExist(val) {
+ if !doesEnvExist(val) {
return val, nil
}
return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
@@ -192,9 +247,8 @@ func ValidateMACAddress(val string) (string, error) {
_, err := net.ParseMAC(strings.TrimSpace(val))
if err != nil {
return "", err
- } else {
- return val, nil
}
+ return val, nil
}
// Validates domain for resolvconf search configuration.
@@ -235,3 +289,21 @@ func ValidateLabel(val string) (string, error) {
}
return val, nil
}
+
+func ValidateHost(val string) (string, error) {
+ host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val)
+ if err != nil {
+ return val, err
+ }
+ return host, nil
+}
+
+func doesEnvExist(name string) bool {
+ for _, entry := range os.Environ() {
+ parts := strings.SplitN(entry, "=", 2)
+ if parts[0] == name {
+ return true
+ }
+ }
+ return false
+}
diff --git a/opts/opts_test.go b/opts/opts_test.go
index 8370926da..dfad430ac 100644
--- a/opts/opts_test.go
+++ b/opts/opts_test.go
@@ -1,6 +1,7 @@
package opts
import (
+ "fmt"
"strings"
"testing"
)
@@ -28,6 +29,31 @@ func TestValidateIPAddress(t *testing.T) {
}
+func TestMapOpts(t *testing.T) {
+ tmpMap := make(map[string]string)
+ o := newMapOpt(tmpMap, logOptsValidator)
+ o.Set("max-size=1")
+ if o.String() != "map[max-size:1]" {
+ t.Errorf("%s != [map[max-size:1]", o.String())
+ }
+
+ o.Set("max-file=2")
+ if len(tmpMap) != 2 {
+ t.Errorf("map length %d != 2", len(tmpMap))
+ }
+
+ if tmpMap["max-file"] != "2" {
+ t.Errorf("max-file = %s != 2", tmpMap["max-file"])
+ }
+
+ if tmpMap["max-size"] != "1" {
+ t.Errorf("max-size = %s != 1", tmpMap["max-size"])
+ }
+ if o.Set("dummy-val=3") == nil {
+ t.Errorf("validator is not being called")
+ }
+}
+
func TestValidateMACAddress(t *testing.T) {
if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
@@ -152,3 +178,12 @@ func TestValidateExtraHosts(t *testing.T) {
}
}
}
+
+func logOptsValidator(val string) (string, error) {
+ allowedKeys := map[string]string{"max-size": "1", "max-file": "2"}
+ vals := strings.Split(val, "=")
+ if allowedKeys[vals[0]] != "" {
+ return val, nil
+ }
+ return "", fmt.Errorf("invalid key %s", vals[0])
+}
diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go
index bfa6e1846..cde4de55a 100644
--- a/pkg/archive/archive.go
+++ b/pkg/archive/archive.go
@@ -1,6 +1,7 @@
package archive
import (
+ "archive/tar"
"bufio"
"bytes"
"compress/bzip2"
@@ -11,14 +12,12 @@ import (
"io/ioutil"
"os"
"os/exec"
- "path"
"path/filepath"
+ "runtime"
"strings"
"syscall"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
-
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/promise"
@@ -78,7 +77,7 @@ func DetectCompression(source []byte) Compression {
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
} {
if len(source) < len(m) {
- log.Debugf("Len too short")
+ logrus.Debugf("Len too short")
continue
}
if bytes.Compare(m, source[:len(m)]) == 0 {
@@ -292,17 +291,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
file.Close()
case tar.TypeBlock, tar.TypeChar, tar.TypeFifo:
- mode := uint32(hdr.Mode & 07777)
- switch hdr.Typeflag {
- case tar.TypeBlock:
- mode |= syscall.S_IFBLK
- case tar.TypeChar:
- mode |= syscall.S_IFCHR
- case tar.TypeFifo:
- mode |= syscall.S_IFIFO
- }
-
- if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
+ // Handle this is an OS-specific way
+ if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
return err
}
@@ -331,15 +321,18 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
}
case tar.TypeXGlobalHeader:
- log.Debugf("PAX Global Extended Headers found and ignored")
+ logrus.Debugf("PAX Global Extended Headers found and ignored")
return nil
default:
return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
}
- if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
- return err
+ // Lchown is not supported on Windows
+ if runtime.GOOS != "windows" {
+ if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
+ return err
+ }
}
for key, value := range hdr.Xattrs {
@@ -350,15 +343,19 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
// There is no LChmod, so ignore mode for symlink. Also, this
// must happen after chown, as that can modify the file mode
- if hdr.Typeflag != tar.TypeSymlink {
- if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
- return err
- }
+ if err := handleLChmod(hdr, path, hdrInfo); err != nil {
+ return err
}
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
- // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
- if hdr.Typeflag != tar.TypeSymlink {
+ // syscall.UtimesNano doesn't support a NOFOLLOW flag atm
+ if hdr.Typeflag == tar.TypeLink {
+ if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
+ if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
+ return err
+ }
+ }
+ } else if hdr.Typeflag != tar.TypeSymlink {
if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
return err
}
@@ -376,25 +373,16 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) {
return TarWithOptions(path, &TarOptions{Compression: compression})
}
-func escapeName(name string) string {
- escaped := make([]byte, 0)
- for i, c := range []byte(name) {
- if i == 0 && c == '/' {
- continue
- }
- // all printable chars except "-" which is 0x2d
- if (0x20 <= c && c <= 0x7E) && c != 0x2d {
- escaped = append(escaped, c)
- } else {
- escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
- }
- }
- return string(escaped)
-}
-
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
+
+ patterns, patDirs, exceptions, err := fileutils.CleanPatterns(options.ExcludePatterns)
+
+ if err != nil {
+ return nil, err
+ }
+
pipeReader, pipeWriter := io.Pipe()
compressWriter, err := CompressStream(pipeWriter, options.Compression)
@@ -426,7 +414,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
for _, include := range options.IncludeFiles {
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
if err != nil {
- log.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err)
+ logrus.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err)
return nil
}
@@ -445,15 +433,15 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
// is asking for that file no matter what - which is true
// for some files, like .dockerignore and Dockerfile (sometimes)
if include != relFilePath {
- skip, err = fileutils.Matches(relFilePath, options.ExcludePatterns)
+ skip, err = fileutils.OptimizedMatches(relFilePath, patterns, patDirs)
if err != nil {
- log.Debugf("Error matching %s", relFilePath, err)
+ logrus.Debugf("Error matching %s", relFilePath, err)
return err
}
}
if skip {
- if f.IsDir() {
+ if !exceptions && f.IsDir() {
return filepath.SkipDir
}
return nil
@@ -474,7 +462,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
}
if err := ta.addTarFile(filePath, relFilePath); err != nil {
- log.Debugf("Can't add file %s to tar: %s", filePath, err)
+ logrus.Debugf("Can't add file %s to tar: %s", filePath, err)
}
return nil
})
@@ -482,13 +470,13 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
// Make sure to check the error on Close.
if err := ta.TarWriter.Close(); err != nil {
- log.Debugf("Can't close tar writer: %s", err)
+ logrus.Debugf("Can't close tar writer: %s", err)
}
if err := compressWriter.Close(); err != nil {
- log.Debugf("Can't close compress writer: %s", err)
+ logrus.Debugf("Can't close compress writer: %s", err)
}
if err := pipeWriter.Close(); err != nil {
- log.Debugf("Can't close pipe writer: %s", err)
+ logrus.Debugf("Can't close pipe writer: %s", err)
}
}()
@@ -529,7 +517,7 @@ loop:
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
- err = os.MkdirAll(parentPath, 0777)
+ err = system.MkdirAll(parentPath, 0777)
if err != nil {
return err
}
@@ -606,7 +594,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
}
func (archiver *Archiver) TarUntar(src, dst string) error {
- log.Debugf("TarUntar(%s %s)", src, dst)
+ logrus.Debugf("TarUntar(%s %s)", src, dst)
archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed})
if err != nil {
return err
@@ -648,11 +636,11 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error {
return archiver.CopyFileWithTar(src, dst)
}
// Create dst, copy src's content into it
- log.Debugf("Creating dest directory: %s", dst)
- if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
+ logrus.Debugf("Creating dest directory: %s", dst)
+ if err := system.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
return err
}
- log.Debugf("Calling TarUntar(%s, %s)", src, dst)
+ logrus.Debugf("Calling TarUntar(%s, %s)", src, dst)
return archiver.TarUntar(src, dst)
}
@@ -665,7 +653,7 @@ func CopyWithTar(src, dst string) error {
}
func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
- log.Debugf("CopyFileWithTar(%s, %s)", src, dst)
+ logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst)
srcSt, err := os.Stat(src)
if err != nil {
return err
@@ -673,12 +661,12 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
if srcSt.IsDir() {
return fmt.Errorf("Can't copy a directory")
}
- // Clean up the trailing /
- if dst[len(dst)-1] == '/' {
- dst = path.Join(dst, filepath.Base(src))
+ // Clean up the trailing slash
+ if dst[len(dst)-1] == os.PathSeparator {
+ dst = filepath.Join(dst, filepath.Base(src))
}
// Create the holding directory if necessary
- if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
+ if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
return err
}
@@ -791,9 +779,6 @@ func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
if _, err := io.Copy(f, src); err != nil {
return nil, err
}
- if err = f.Sync(); err != nil {
- return nil, err
- }
if _, err := f.Seek(0, 0); err != nil {
return nil, err
}
diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go
index 6cd95d5ad..f24f628c2 100644
--- a/pkg/archive/archive_test.go
+++ b/pkg/archive/archive_test.go
@@ -1,6 +1,7 @@
package archive
import (
+ "archive/tar"
"bytes"
"fmt"
"io"
@@ -14,9 +15,149 @@ import (
"testing"
"time"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/system"
)
+func TestIsArchiveNilHeader(t *testing.T) {
+ out := IsArchive(nil)
+ if out {
+ t.Fatalf("isArchive should return false as nil is not a valid archive header")
+ }
+}
+
+func TestIsArchiveInvalidHeader(t *testing.T) {
+ header := []byte{0x00, 0x01, 0x02}
+ out := IsArchive(header)
+ if out {
+ t.Fatalf("isArchive should return false as %s is not a valid archive header", header)
+ }
+}
+
+func TestIsArchiveBzip2(t *testing.T) {
+ header := []byte{0x42, 0x5A, 0x68}
+ out := IsArchive(header)
+ if !out {
+ t.Fatalf("isArchive should return true as %s is a bz2 header", header)
+ }
+}
+
+func TestIsArchive7zip(t *testing.T) {
+ header := []byte{0x50, 0x4b, 0x03, 0x04}
+ out := IsArchive(header)
+ if out {
+ t.Fatalf("isArchive should return false as %s is a 7z header and it is not supported", header)
+ }
+}
+
+func TestDecompressStreamGzip(t *testing.T) {
+ cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && gzip -f /tmp/archive")
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Fail to create an archive file for test : %s.", output)
+ }
+ archive, err := os.Open("/tmp/archive.gz")
+ _, err = DecompressStream(archive)
+ if err != nil {
+ t.Fatalf("Failed to decompress a gzip file.")
+ }
+}
+
+func TestDecompressStreamBzip2(t *testing.T) {
+ cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && bzip2 -f /tmp/archive")
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Fail to create an archive file for test : %s.", output)
+ }
+ archive, err := os.Open("/tmp/archive.bz2")
+ _, err = DecompressStream(archive)
+ if err != nil {
+ t.Fatalf("Failed to decompress a bzip2 file.")
+ }
+}
+
+func TestDecompressStreamXz(t *testing.T) {
+ cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && xz -f /tmp/archive")
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Fail to create an archive file for test : %s.", output)
+ }
+ archive, err := os.Open("/tmp/archive.xz")
+ _, err = DecompressStream(archive)
+ if err != nil {
+ t.Fatalf("Failed to decompress a xz file.")
+ }
+}
+
+func TestCompressStreamXzUnsuported(t *testing.T) {
+ dest, err := os.Create("/tmp/dest")
+ if err != nil {
+ t.Fatalf("Fail to create the destination file")
+ }
+ _, err = CompressStream(dest, Xz)
+ if err == nil {
+ t.Fatalf("Should fail as xz is unsupported for compression format.")
+ }
+}
+
+func TestCompressStreamBzip2Unsupported(t *testing.T) {
+ dest, err := os.Create("/tmp/dest")
+ if err != nil {
+ t.Fatalf("Fail to create the destination file")
+ }
+ _, err = CompressStream(dest, Xz)
+ if err == nil {
+ t.Fatalf("Should fail as xz is unsupported for compression format.")
+ }
+}
+
+func TestCompressStreamInvalid(t *testing.T) {
+ dest, err := os.Create("/tmp/dest")
+ if err != nil {
+ t.Fatalf("Fail to create the destination file")
+ }
+ _, err = CompressStream(dest, -1)
+ if err == nil {
+ t.Fatalf("Should fail as xz is unsupported for compression format.")
+ }
+}
+
+func TestExtensionInvalid(t *testing.T) {
+ compression := Compression(-1)
+ output := compression.Extension()
+ if output != "" {
+ t.Fatalf("The extension of an invalid compression should be an empty string.")
+ }
+}
+
+func TestExtensionUncompressed(t *testing.T) {
+ compression := Uncompressed
+ output := compression.Extension()
+ if output != "tar" {
+ t.Fatalf("The extension of a uncompressed archive should be 'tar'.")
+ }
+}
+func TestExtensionBzip2(t *testing.T) {
+ compression := Bzip2
+ output := compression.Extension()
+ if output != "tar.bz2" {
+ t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'")
+ }
+}
+func TestExtensionGzip(t *testing.T) {
+ compression := Gzip
+ output := compression.Extension()
+ if output != "tar.gz" {
+ t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'")
+ }
+}
+func TestExtensionXz(t *testing.T) {
+ compression := Xz
+ output := compression.Extension()
+ if output != "tar.xz" {
+ t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'")
+ }
+}
+
func TestCmdStreamLargeStderr(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
out, err := CmdStream(cmd, nil)
@@ -66,6 +207,315 @@ func TestCmdStreamGood(t *testing.T) {
}
}
+func TestUntarPathWithInvalidDest(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempFolder)
+ invalidDestFolder := path.Join(tempFolder, "invalidDest")
+ // Create a src file
+ srcFile := path.Join(tempFolder, "src")
+ _, err = os.Create(srcFile)
+ if err != nil {
+ t.Fatalf("Fail to create the source file")
+ }
+ err = UntarPath(srcFile, invalidDestFolder)
+ if err == nil {
+ t.Fatalf("UntarPath with invalid destination path should throw an error.")
+ }
+}
+
+func TestUntarPathWithInvalidSrc(t *testing.T) {
+ dest, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatalf("Fail to create the destination file")
+ }
+ defer os.RemoveAll(dest)
+ err = UntarPath("/invalid/path", dest)
+ if err == nil {
+ t.Fatalf("UntarPath with invalid src path should throw an error.")
+ }
+}
+
+func TestUntarPath(t *testing.T) {
+ tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpFolder)
+ srcFile := path.Join(tmpFolder, "src")
+ tarFile := path.Join(tmpFolder, "src.tar")
+ os.Create(path.Join(tmpFolder, "src"))
+ cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile)
+ _, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ destFolder := path.Join(tmpFolder, "dest")
+ err = os.MkdirAll(destFolder, 0740)
+ if err != nil {
+ t.Fatalf("Fail to create the destination file")
+ }
+ err = UntarPath(tarFile, destFolder)
+ if err != nil {
+ t.Fatalf("UntarPath shouldn't throw an error, %s.", err)
+ }
+ expectedFile := path.Join(destFolder, srcFile)
+ _, err = os.Stat(expectedFile)
+ if err != nil {
+ t.Fatalf("Destination folder should contain the source file but did not.")
+ }
+}
+
+// Do the same test as above but with the destination as file, it should fail
+func TestUntarPathWithDestinationFile(t *testing.T) {
+ tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpFolder)
+ srcFile := path.Join(tmpFolder, "src")
+ tarFile := path.Join(tmpFolder, "src.tar")
+ os.Create(path.Join(tmpFolder, "src"))
+ cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile)
+ _, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ destFile := path.Join(tmpFolder, "dest")
+ _, err = os.Create(destFile)
+ if err != nil {
+ t.Fatalf("Fail to create the destination file")
+ }
+ err = UntarPath(tarFile, destFile)
+ if err == nil {
+ t.Fatalf("UntarPath should throw an error if the destination if a file")
+ }
+}
+
+// Do the same test as above but with the destination folder already exists
+// and the destination file is a directory
+// It's working, see https://github.com/docker/docker/issues/10040
+func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) {
+ tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpFolder)
+ srcFile := path.Join(tmpFolder, "src")
+ tarFile := path.Join(tmpFolder, "src.tar")
+ os.Create(srcFile)
+ cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile)
+ _, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ destFolder := path.Join(tmpFolder, "dest")
+ err = os.MkdirAll(destFolder, 0740)
+ if err != nil {
+ t.Fatalf("Fail to create the destination folder")
+ }
+ // Let's create a folder that will has the same path as the extracted file (from tar)
+ destSrcFileAsFolder := path.Join(destFolder, srcFile)
+ err = os.MkdirAll(destSrcFileAsFolder, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = UntarPath(tarFile, destFolder)
+ if err != nil {
+ t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder")
+ }
+}
+
+func TestCopyWithTarInvalidSrc(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(nil)
+ }
+ destFolder := path.Join(tempFolder, "dest")
+ invalidSrc := path.Join(tempFolder, "doesnotexists")
+ err = os.MkdirAll(destFolder, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = CopyWithTar(invalidSrc, destFolder)
+ if err == nil {
+ t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
+ }
+}
+
+func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(nil)
+ }
+ srcFolder := path.Join(tempFolder, "src")
+ inexistentDestFolder := path.Join(tempFolder, "doesnotexists")
+ err = os.MkdirAll(srcFolder, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = CopyWithTar(srcFolder, inexistentDestFolder)
+ if err != nil {
+ t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
+ }
+ _, err = os.Stat(inexistentDestFolder)
+ if err != nil {
+ t.Fatalf("CopyWithTar with an inexistent folder should create it.")
+ }
+}
+
+// Test CopyWithTar with a file as src
+func TestCopyWithTarSrcFile(t *testing.T) {
+ folder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(folder)
+ dest := path.Join(folder, "dest")
+ srcFolder := path.Join(folder, "src")
+ src := path.Join(folder, path.Join("src", "src"))
+ err = os.MkdirAll(srcFolder, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = os.MkdirAll(dest, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ioutil.WriteFile(src, []byte("content"), 0777)
+ err = CopyWithTar(src, dest)
+ if err != nil {
+ t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
+ }
+ _, err = os.Stat(dest)
+ // FIXME Check the content
+ if err != nil {
+ t.Fatalf("Destination file should be the same as the source.")
+ }
+}
+
+// Test CopyWithTar with a folder as src
+func TestCopyWithTarSrcFolder(t *testing.T) {
+ folder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(folder)
+ dest := path.Join(folder, "dest")
+ src := path.Join(folder, path.Join("src", "folder"))
+ err = os.MkdirAll(src, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = os.MkdirAll(dest, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ioutil.WriteFile(path.Join(src, "file"), []byte("content"), 0777)
+ err = CopyWithTar(src, dest)
+ if err != nil {
+ t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
+ }
+ _, err = os.Stat(dest)
+ // FIXME Check the content (the file inside)
+ if err != nil {
+ t.Fatalf("Destination folder should contain the source file but did not.")
+ }
+}
+
+func TestCopyFileWithTarInvalidSrc(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempFolder)
+ destFolder := path.Join(tempFolder, "dest")
+ err = os.MkdirAll(destFolder, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ invalidFile := path.Join(tempFolder, "doesnotexists")
+ err = CopyFileWithTar(invalidFile, destFolder)
+ if err == nil {
+ t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
+ }
+}
+
+func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(nil)
+ }
+ defer os.RemoveAll(tempFolder)
+ srcFile := path.Join(tempFolder, "src")
+ inexistentDestFolder := path.Join(tempFolder, "doesnotexists")
+ _, err = os.Create(srcFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = CopyFileWithTar(srcFile, inexistentDestFolder)
+ if err != nil {
+ t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
+ }
+ _, err = os.Stat(inexistentDestFolder)
+ if err != nil {
+ t.Fatalf("CopyWithTar with an inexistent folder should create it.")
+ }
+ // FIXME Test the src file and content
+}
+
+func TestCopyFileWithTarSrcFolder(t *testing.T) {
+ folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(folder)
+ dest := path.Join(folder, "dest")
+ src := path.Join(folder, "srcfolder")
+ err = os.MkdirAll(src, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = os.MkdirAll(dest, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = CopyFileWithTar(src, dest)
+ if err == nil {
+ t.Fatalf("CopyFileWithTar should throw an error with a folder.")
+ }
+}
+
+func TestCopyFileWithTarSrcFile(t *testing.T) {
+ folder, err := ioutil.TempDir("", "docker-archive-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(folder)
+ dest := path.Join(folder, "dest")
+ srcFolder := path.Join(folder, "src")
+ src := path.Join(folder, path.Join("src", "src"))
+ err = os.MkdirAll(srcFolder, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = os.MkdirAll(dest, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ioutil.WriteFile(src, []byte("content"), 0777)
+ err = CopyWithTar(src, dest+"/")
+ if err != nil {
+ t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err)
+ }
+ _, err = os.Stat(dest)
+ if err != nil {
+ t.Fatalf("Destination folder should contain the source file but did not.")
+ }
+}
+
func TestTarFiles(t *testing.T) {
// try without hardlinks
if err := checkNoChanges(1000, false); err != nil {
@@ -179,11 +629,56 @@ func TestTarUntar(t *testing.T) {
}
}
+func TestTarUntarWithXattr(t *testing.T) {
+ origin, err := ioutil.TempDir("", "docker-test-untar-origin")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(origin)
+ if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := ioutil.WriteFile(path.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := system.Lsetxattr(path.Join(origin, "2"), "security.capability", []byte{0x00}, 0); err != nil {
+ t.Fatal(err)
+ }
+
+ for _, c := range []Compression{
+ Uncompressed,
+ Gzip,
+ } {
+ changes, err := tarUntar(t, origin, &TarOptions{
+ Compression: c,
+ ExcludePatterns: []string{"3"},
+ })
+
+ if err != nil {
+ t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
+ }
+
+ if len(changes) != 1 || changes[0].Path != "/3" {
+ t.Fatalf("Unexpected differences after tarUntar: %v", changes)
+ }
+ capability, _ := system.Lgetxattr(path.Join(origin, "2"), "security.capability")
+ if capability == nil && capability[0] != 0x00 {
+ t.Fatalf("Untar should have kept the 'security.capability' xattr.")
+ }
+ }
+}
+
func TestTarWithOptions(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
if err != nil {
t.Fatal(err)
}
+ if _, err := ioutil.TempDir(origin, "folder"); err != nil {
+ t.Fatal(err)
+ }
defer os.RemoveAll(origin)
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
t.Fatal(err)
@@ -196,8 +691,11 @@ func TestTarWithOptions(t *testing.T) {
opts *TarOptions
numChanges int
}{
- {&TarOptions{IncludeFiles: []string{"1"}}, 1},
+ {&TarOptions{IncludeFiles: []string{"1"}}, 2},
{&TarOptions{ExcludePatterns: []string{"2"}}, 1},
+ {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
+ {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
+ {&TarOptions{Name: "test", IncludeFiles: []string{"1"}}, 4},
}
for _, testCase := range cases {
changes, err := tarUntar(t, origin, testCase.opts)
@@ -256,6 +754,58 @@ func TestUntarUstarGnuConflict(t *testing.T) {
}
}
+func TestTarWithBlockCharFifo(t *testing.T) {
+ origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(origin)
+ if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := system.Mknod(path.Join(origin, "2"), syscall.S_IFBLK, int(system.Mkdev(int64(12), int64(5)))); err != nil {
+ t.Fatal(err)
+ }
+ if err := system.Mknod(path.Join(origin, "3"), syscall.S_IFCHR, int(system.Mkdev(int64(12), int64(5)))); err != nil {
+ t.Fatal(err)
+ }
+ if err := system.Mknod(path.Join(origin, "4"), syscall.S_IFIFO, int(system.Mkdev(int64(12), int64(5)))); err != nil {
+ t.Fatal(err)
+ }
+
+ dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dest)
+
+ // we'll do this in two steps to separate failure
+ fh, err := Tar(origin, Uncompressed)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // ensure we can read the whole thing with no error, before writing back out
+ buf, err := ioutil.ReadAll(fh)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ bRdr := bytes.NewReader(buf)
+ err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ changes, err := ChangesDirs(origin, dest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(changes) > 0 {
+ t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %v", changes)
+ }
+}
+
func TestTarWithHardLink(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
if err != nil {
@@ -435,6 +985,34 @@ func TestUntarInvalidFilenames(t *testing.T) {
}
}
+func TestUntarHardlinkToSymlink(t *testing.T) {
+ for i, headers := range [][]*tar.Header{
+ {
+ {
+ Name: "symlink1",
+ Typeflag: tar.TypeSymlink,
+ Linkname: "regfile",
+ Mode: 0644,
+ },
+ {
+ Name: "symlink2",
+ Typeflag: tar.TypeLink,
+ Linkname: "symlink1",
+ Mode: 0644,
+ },
+ {
+ Name: "regfile",
+ Typeflag: tar.TypeReg,
+ Mode: 0644,
+ },
+ },
+ } {
+ if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil {
+ t.Fatalf("i=%d. %v", i, err)
+ }
+ }
+}
+
func TestUntarInvalidHardlink(t *testing.T) {
for i, headers := range [][]*tar.Header{
{ // try reading victim/hello (../)
diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go
index cbce65e31..8a15cfebe 100644
--- a/pkg/archive/archive_unix.go
+++ b/pkg/archive/archive_unix.go
@@ -3,11 +3,12 @@
package archive
import (
+ "archive/tar"
"errors"
"os"
"syscall"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/system"
)
// canonicalTarNameForPath returns platform-specific filepath
@@ -36,8 +37,8 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st
inode = uint64(s.Ino)
// Currently go does not fil in the major/minors
- if s.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
- s.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
+ if s.Mode&syscall.S_IFBLK != 0 ||
+ s.Mode&syscall.S_IFCHR != 0 {
hdr.Devmajor = int64(major(uint64(s.Rdev)))
hdr.Devminor = int64(minor(uint64(s.Rdev)))
}
@@ -52,3 +53,37 @@ func major(device uint64) uint64 {
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
+
+// handleTarTypeBlockCharFifo is an OS-specific helper function used by
+// createTarFile to handle the following types of header: Block; Char; Fifo
+func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
+ mode := uint32(hdr.Mode & 07777)
+ switch hdr.Typeflag {
+ case tar.TypeBlock:
+ mode |= syscall.S_IFBLK
+ case tar.TypeChar:
+ mode |= syscall.S_IFCHR
+ case tar.TypeFifo:
+ mode |= syscall.S_IFIFO
+ }
+
+ if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
+ return err
+ }
+ return nil
+}
+
+func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
+ if hdr.Typeflag == tar.TypeLink {
+ if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
+ if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
+ return err
+ }
+ }
+ } else if hdr.Typeflag != tar.TypeSymlink {
+ if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go
index 6caef3b73..10db4bd00 100644
--- a/pkg/archive/archive_windows.go
+++ b/pkg/archive/archive_windows.go
@@ -3,11 +3,10 @@
package archive
import (
+ "archive/tar"
"fmt"
"os"
"strings"
-
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
// canonicalTarNameForPath returns platform-specific filepath
@@ -15,11 +14,11 @@ import (
// path.
func CanonicalTarNameForPath(p string) (string, error) {
// windows: convert windows style relative path with backslashes
- // into forward slashes. since windows does not allow '/' or '\'
+ // into forward slashes. Since windows does not allow '/' or '\'
// in file names, it is mostly safe to replace however we must
// check just in case
if strings.Contains(p, "/") {
- return "", fmt.Errorf("windows path contains forward slash: %s", p)
+ return "", fmt.Errorf("Windows path contains forward slash: %s", p)
}
return strings.Replace(p, string(os.PathSeparator), "/", -1), nil
@@ -39,3 +38,13 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
return
}
+
+// handleTarTypeBlockCharFifo is an OS-specific helper function used by
+// createTarFile to handle the following types of header: Block; Char; Fifo
+func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
+ return nil
+}
+
+func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
+ return nil
+}
diff --git a/pkg/archive/archive_windows_test.go b/pkg/archive/archive_windows_test.go
index b33e0fb00..72bc71e06 100644
--- a/pkg/archive/archive_windows_test.go
+++ b/pkg/archive/archive_windows_test.go
@@ -20,7 +20,7 @@ func TestCanonicalTarNameForPath(t *testing.T) {
if out, err := CanonicalTarNameForPath(v.in); err != nil && !v.shouldFail {
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
} else if v.shouldFail && err == nil {
- t.Fatalf("canonical path call should have pailed with error. in=%s out=%s", v.in, out)
+ t.Fatalf("canonical path call should have failed with error. in=%s out=%s", v.in, out)
} else if !v.shouldFail && out != v.expected {
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
}
diff --git a/pkg/archive/changes.go b/pkg/archive/changes.go
index c3cb4ebe0..affafad39 100644
--- a/pkg/archive/changes.go
+++ b/pkg/archive/changes.go
@@ -1,6 +1,7 @@
package archive
import (
+ "archive/tar"
"bytes"
"fmt"
"io"
@@ -11,9 +12,7 @@ import (
"syscall"
"time"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
-
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/system"
)
@@ -175,10 +174,6 @@ func (info *FileInfo) path() string {
return filepath.Join(info.parent.path(), info.name)
}
-func (info *FileInfo) isDir() bool {
- return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR == syscall.S_IFDIR
-}
-
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
sizeAtEntry := len(*changes)
@@ -215,13 +210,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
// be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting
// back mtime
- if oldStat.Mode() != newStat.Mode() ||
- oldStat.Uid() != newStat.Uid() ||
- oldStat.Gid() != newStat.Gid() ||
- oldStat.Rdev() != newStat.Rdev() ||
- // Don't look at size for dirs, its not a good measure of change
- (oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
- (!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) ||
+ if statDifferent(oldStat, newStat) ||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
change := Change{
Path: newChild.path(),
@@ -401,22 +390,22 @@ func ExportChanges(dir string, changes []Change) (Archive, error) {
ChangeTime: timestamp,
}
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
- log.Debugf("Can't write whiteout header: %s", err)
+ logrus.Debugf("Can't write whiteout header: %s", err)
}
} else {
path := filepath.Join(dir, change.Path)
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
- log.Debugf("Can't add file %s to tar: %s", path, err)
+ logrus.Debugf("Can't add file %s to tar: %s", path, err)
}
}
}
// Make sure to check the error on Close.
if err := ta.TarWriter.Close(); err != nil {
- log.Debugf("Can't close layer: %s", err)
+ logrus.Debugf("Can't close layer: %s", err)
}
if err := writer.Close(); err != nil {
- log.Debugf("failed close Changes writer: %s", err)
+ logrus.Debugf("failed close Changes writer: %s", err)
}
}()
return reader, nil
diff --git a/pkg/archive/changes_test.go b/pkg/archive/changes_test.go
index 53ec575b6..290b2dd40 100644
--- a/pkg/archive/changes_test.go
+++ b/pkg/archive/changes_test.go
@@ -6,6 +6,7 @@ import (
"os/exec"
"path"
"sort"
+ "syscall"
"testing"
"time"
)
@@ -91,17 +92,130 @@ func createSampleDir(t *testing.T, root string) {
}
}
+func TestChangeString(t *testing.T) {
+ modifiyChange := Change{"change", ChangeModify}
+ toString := modifiyChange.String()
+ if toString != "C change" {
+ t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString)
+ }
+ addChange := Change{"change", ChangeAdd}
+ toString = addChange.String()
+ if toString != "A change" {
+ t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString)
+ }
+ deleteChange := Change{"change", ChangeDelete}
+ toString = deleteChange.String()
+ if toString != "D change" {
+ t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString)
+ }
+}
+
+func TestChangesWithNoChanges(t *testing.T) {
+ rwLayer, err := ioutil.TempDir("", "docker-changes-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(rwLayer)
+ layer, err := ioutil.TempDir("", "docker-changes-test-layer")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(layer)
+ createSampleDir(t, layer)
+ changes, err := Changes([]string{layer}, rwLayer)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(changes) != 0 {
+ t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes))
+ }
+}
+
+func TestChangesWithChanges(t *testing.T) {
+ rwLayer, err := ioutil.TempDir("", "docker-changes-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(rwLayer)
+ // Create a folder
+ dir1 := path.Join(rwLayer, "dir1")
+ os.MkdirAll(dir1, 0740)
+ deletedFile := path.Join(dir1, ".wh.file1-2")
+ ioutil.WriteFile(deletedFile, []byte{}, 0600)
+ modifiedFile := path.Join(dir1, "file1-1")
+ ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444)
+ // Let's add a subfolder for a newFile
+ subfolder := path.Join(dir1, "subfolder")
+ os.MkdirAll(subfolder, 0740)
+ newFile := path.Join(subfolder, "newFile")
+ ioutil.WriteFile(newFile, []byte{}, 0740)
+ // Let's create folders that with have the role of layers with the same data
+ layer, err := ioutil.TempDir("", "docker-changes-test-layer")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(layer)
+ createSampleDir(t, layer)
+ os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740)
+
+ // Let's modify modtime for dir1 to be sure it's the same for the two layer (to not having false positive)
+ fi, err := os.Stat(dir1)
+ if err != nil {
+ return
+ }
+ mtime := fi.ModTime()
+ stat := fi.Sys().(*syscall.Stat_t)
+ atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
+
+ layerDir1 := path.Join(layer, "dir1")
+ os.Chtimes(layerDir1, atime, mtime)
+
+ changes, err := Changes([]string{layer}, rwLayer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sort.Sort(changesByPath(changes))
+
+ expectedChanges := []Change{
+ {"/dir1/file1-1", ChangeModify},
+ {"/dir1/file1-2", ChangeDelete},
+ {"/dir1/subfolder", ChangeModify},
+ {"/dir1/subfolder/newFile", ChangeAdd},
+ }
+
+ for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
+ if i >= len(expectedChanges) {
+ t.Fatalf("unexpected change %s\n", changes[i].String())
+ }
+ if i >= len(changes) {
+ t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
+ }
+ if changes[i].Path == expectedChanges[i].Path {
+ if changes[i] != expectedChanges[i] {
+ t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
+ }
+ } else if changes[i].Path < expectedChanges[i].Path {
+ t.Fatalf("unexpected change %s\n", changes[i].String())
+ } else {
+ t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
+ }
+ }
+}
+
// Create an directory, copy it, make sure we report no changes between the two
func TestChangesDirsEmpty(t *testing.T) {
src, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
+ defer os.RemoveAll(src)
createSampleDir(t, src)
dst := src + "-copy"
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
+ defer os.RemoveAll(dst)
changes, err := ChangesDirs(dst, src)
if err != nil {
t.Fatal(err)
@@ -291,3 +405,41 @@ func TestApplyLayer(t *testing.T) {
t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2)
}
}
+
+func TestChangesSizeWithNoChanges(t *testing.T) {
+ size := ChangesSize("/tmp", nil)
+ if size != 0 {
+ t.Fatalf("ChangesSizes with no changes should be 0, was %d", size)
+ }
+}
+
+func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) {
+ changes := []Change{
+ {Path: "deletedPath", Kind: ChangeDelete},
+ }
+ size := ChangesSize("/tmp", changes)
+ if size != 0 {
+ t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size)
+ }
+}
+
+func TestChangesSize(t *testing.T) {
+ parentPath, err := ioutil.TempDir("", "docker-changes-test")
+ defer os.RemoveAll(parentPath)
+ addition := path.Join(parentPath, "addition")
+ if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
+ t.Fatal(err)
+ }
+ modification := path.Join(parentPath, "modification")
+ if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
+ t.Fatal(err)
+ }
+ changes := []Change{
+ {Path: "addition", Kind: ChangeAdd},
+ {Path: "modification", Kind: ChangeModify},
+ }
+ size := ChangesSize(parentPath, changes)
+ if size != 6 {
+ t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size)
+ }
+}
diff --git a/pkg/archive/changes_unix.go b/pkg/archive/changes_unix.go
new file mode 100644
index 000000000..d780f1639
--- /dev/null
+++ b/pkg/archive/changes_unix.go
@@ -0,0 +1,27 @@
+// +build !windows
+
+package archive
+
+import (
+ "syscall"
+
+ "github.com/docker/docker/pkg/system"
+)
+
+func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
+ // Don't look at size for dirs, its not a good measure of change
+ if oldStat.Mode() != newStat.Mode() ||
+ oldStat.Uid() != newStat.Uid() ||
+ oldStat.Gid() != newStat.Gid() ||
+ oldStat.Rdev() != newStat.Rdev() ||
+ // Don't look at size for dirs, its not a good measure of change
+ (oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
+ (!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) {
+ return true
+ }
+ return false
+}
+
+func (info *FileInfo) isDir() bool {
+ return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0
+}
diff --git a/pkg/archive/changes_windows.go b/pkg/archive/changes_windows.go
new file mode 100644
index 000000000..4809b7a59
--- /dev/null
+++ b/pkg/archive/changes_windows.go
@@ -0,0 +1,20 @@
+package archive
+
+import (
+ "github.com/docker/docker/pkg/system"
+)
+
+func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
+
+ // Don't look at size for dirs, its not a good measure of change
+ if oldStat.ModTime() != newStat.ModTime() ||
+ oldStat.Mode() != newStat.Mode() ||
+ oldStat.Size() != newStat.Size() && !oldStat.IsDir() {
+ return true
+ }
+ return false
+}
+
+func (info *FileInfo) isDir() bool {
+ return info.parent == nil || info.stat.IsDir()
+}
diff --git a/pkg/archive/diff.go b/pkg/archive/diff.go
index b5eb63fd4..fd4946078 100644
--- a/pkg/archive/diff.go
+++ b/pkg/archive/diff.go
@@ -1,6 +1,7 @@
package archive
import (
+ "archive/tar"
"fmt"
"io"
"io/ioutil"
@@ -9,8 +10,6 @@ import (
"strings"
"syscall"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
-
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/system"
)
@@ -48,7 +47,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
- err = os.MkdirAll(parentPath, 0600)
+ err = system.MkdirAll(parentPath, 0600)
if err != nil {
return 0, err
}
diff --git a/pkg/archive/diff_test.go b/pkg/archive/diff_test.go
index 758c4115d..01ed43728 100644
--- a/pkg/archive/diff_test.go
+++ b/pkg/archive/diff_test.go
@@ -1,9 +1,8 @@
package archive
import (
+ "archive/tar"
"testing"
-
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
func TestApplyLayerInvalidFilenames(t *testing.T) {
diff --git a/pkg/archive/utils_test.go b/pkg/archive/utils_test.go
index 904802720..2a266c2fd 100644
--- a/pkg/archive/utils_test.go
+++ b/pkg/archive/utils_test.go
@@ -1,6 +1,7 @@
package archive
import (
+ "archive/tar"
"bytes"
"fmt"
"io"
@@ -8,8 +9,6 @@ import (
"os"
"path/filepath"
"time"
-
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
var testUntarFns = map[string]func(string, io.Reader) error{
diff --git a/pkg/archive/wrap.go b/pkg/archive/wrap.go
index b8b60197a..dfb335c0b 100644
--- a/pkg/archive/wrap.go
+++ b/pkg/archive/wrap.go
@@ -1,8 +1,8 @@
package archive
import (
+ "archive/tar"
"bytes"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
"io/ioutil"
)
diff --git a/pkg/archive/wrap_test.go b/pkg/archive/wrap_test.go
new file mode 100644
index 000000000..46ab36697
--- /dev/null
+++ b/pkg/archive/wrap_test.go
@@ -0,0 +1,98 @@
+package archive
+
+import (
+ "archive/tar"
+ "bytes"
+ "io"
+ "testing"
+)
+
+func TestGenerateEmptyFile(t *testing.T) {
+ archive, err := Generate("emptyFile")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if archive == nil {
+ t.Fatal("The generated archive should not be nil.")
+ }
+
+ expectedFiles := [][]string{
+ {"emptyFile", ""},
+ }
+
+ tr := tar.NewReader(archive)
+ actualFiles := make([][]string, 0, 10)
+ i := 0
+ for {
+ hdr, err := tr.Next()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ buf := new(bytes.Buffer)
+ buf.ReadFrom(tr)
+ content := buf.String()
+ actualFiles = append(actualFiles, []string{hdr.Name, content})
+ i++
+ }
+ if len(actualFiles) != len(expectedFiles) {
+ t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles))
+ }
+ for i := 0; i < len(expectedFiles); i++ {
+ actual := actualFiles[i]
+ expected := expectedFiles[i]
+ if actual[0] != expected[0] {
+ t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0])
+ }
+ if actual[1] != expected[1] {
+ t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1])
+ }
+ }
+}
+
+func TestGenerateWithContent(t *testing.T) {
+ archive, err := Generate("file", "content")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if archive == nil {
+ t.Fatal("The generated archive should not be nil.")
+ }
+
+ expectedFiles := [][]string{
+ {"file", "content"},
+ }
+
+ tr := tar.NewReader(archive)
+ actualFiles := make([][]string, 0, 10)
+ i := 0
+ for {
+ hdr, err := tr.Next()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ buf := new(bytes.Buffer)
+ buf.ReadFrom(tr)
+ content := buf.String()
+ actualFiles = append(actualFiles, []string{hdr.Name, content})
+ i++
+ }
+ if len(actualFiles) != len(expectedFiles) {
+ t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles))
+ }
+ for i := 0; i < len(expectedFiles); i++ {
+ actual := actualFiles[i]
+ expected := expectedFiles[i]
+ if actual[0] != expected[0] {
+ t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0])
+ }
+ if actual[1] != expected[1] {
+ t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1])
+ }
+ }
+}
diff --git a/pkg/broadcastwriter/broadcastwriter.go b/pkg/broadcastwriter/broadcastwriter.go
index 9a0b17624..bd9b67555 100644
--- a/pkg/broadcastwriter/broadcastwriter.go
+++ b/pkg/broadcastwriter/broadcastwriter.go
@@ -6,8 +6,9 @@ import (
"sync"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/jsonlog"
+ "github.com/docker/docker/pkg/timeutils"
)
// BroadcastWriter accumulate multiple io.WriteCloser by stream.
@@ -33,7 +34,6 @@ func (w *BroadcastWriter) AddWriter(writer io.WriteCloser, stream string) {
// Write writes bytes to all writers. Failed writers will be evicted during
// this call.
func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
- created := time.Now().UTC()
w.Lock()
if writers, ok := w.streams[""]; ok {
for sw := range writers {
@@ -42,26 +42,47 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
delete(writers, sw)
}
}
+ if len(w.streams) == 1 {
+ if w.buf.Len() >= 4096 {
+ w.buf.Reset()
+ } else {
+ w.buf.Write(p)
+ }
+ w.Unlock()
+ return len(p), nil
+ }
}
if w.jsLogBuf == nil {
w.jsLogBuf = new(bytes.Buffer)
w.jsLogBuf.Grow(1024)
}
+ var timestamp string
+ created := time.Now().UTC()
w.buf.Write(p)
for {
- line, err := w.buf.ReadString('\n')
- if err != nil {
- w.buf.WriteString(line)
+ if n := w.buf.Len(); n == 0 {
break
}
+ i := bytes.IndexByte(w.buf.Bytes(), '\n')
+ if i < 0 {
+ break
+ }
+ lineBytes := w.buf.Next(i + 1)
+ if timestamp == "" {
+ timestamp, err = timeutils.FastMarshalJSON(created)
+ if err != nil {
+ continue
+ }
+ }
+
for stream, writers := range w.streams {
if stream == "" {
continue
}
- jsonLog := jsonlog.JSONLog{Log: line, Stream: stream, Created: created}
+ jsonLog := jsonlog.JSONLogBytes{Log: lineBytes, Stream: stream, Created: timestamp}
err = jsonLog.MarshalJSONBuf(w.jsLogBuf)
if err != nil {
- log.Errorf("Error making JSON log line: %s", err)
+ logrus.Errorf("Error making JSON log line: %s", err)
continue
}
w.jsLogBuf.WriteByte('\n')
diff --git a/pkg/broadcastwriter/broadcastwriter_test.go b/pkg/broadcastwriter/broadcastwriter_test.go
index 62ca12659..71227821b 100644
--- a/pkg/broadcastwriter/broadcastwriter_test.go
+++ b/pkg/broadcastwriter/broadcastwriter_test.go
@@ -142,3 +142,33 @@ func BenchmarkBroadcastWriter(b *testing.B) {
b.StartTimer()
}
}
+
+func BenchmarkBroadcastWriterWithoutStdoutStderr(b *testing.B) {
+ writer := New()
+ setUpWriter := func() {
+ for i := 0; i < 100; i++ {
+ writer.AddWriter(devNullCloser(0), "")
+ }
+ }
+ testLine := "Line that thinks that it is log line from docker"
+ var buf bytes.Buffer
+ for i := 0; i < 100; i++ {
+ buf.Write([]byte(testLine + "\n"))
+ }
+ // line without eol
+ buf.Write([]byte(testLine))
+ testText := buf.Bytes()
+ b.SetBytes(int64(5 * len(testText)))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ setUpWriter()
+
+ for j := 0; j < 5; j++ {
+ if _, err := writer.Write(testText); err != nil {
+ b.Fatal(err)
+ }
+ }
+
+ writer.Clean()
+ }
+}
diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go
index 17d3739d1..49d19175d 100644
--- a/pkg/chrootarchive/archive.go
+++ b/pkg/chrootarchive/archive.go
@@ -1,6 +1,7 @@
package chrootarchive
import (
+ "bytes"
"encoding/json"
"flag"
"fmt"
@@ -29,7 +30,8 @@ func untar() {
var options *archive.TarOptions
- if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil {
+ //read the options from the pipe "ExtraFiles"
+ if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {
fatal(err)
}
@@ -62,28 +64,39 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error
}
}
- // We can't pass the exclude list directly via cmd line
- // because we easily overrun the shell max argument list length
- // when the full image list is passed (e.g. when this is used
- // by `docker load`). Instead we will add the JSON marshalled
- // and placed in the env, which has significantly larger
- // max size
- data, err := json.Marshal(options)
- if err != nil {
- return fmt.Errorf("Untar json encode: %v", err)
- }
decompressedArchive, err := archive.DecompressStream(tarArchive)
if err != nil {
return err
}
defer decompressedArchive.Close()
+ // We can't pass a potentially large exclude list directly via cmd line
+ // because we easily overrun the kernel's max argument/environment size
+ // when the full image list is passed (e.g. when this is used by
+ // `docker load`). We will marshall the options via a pipe to the
+ // child
+ r, w, err := os.Pipe()
+ if err != nil {
+ return fmt.Errorf("Untar pipe failure: %v", err)
+ }
cmd := reexec.Command("docker-untar", dest)
cmd.Stdin = decompressedArchive
- cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data))
- out, err := cmd.CombinedOutput()
- if err != nil {
- return fmt.Errorf("Untar %s %s", err, out)
+ cmd.ExtraFiles = append(cmd.ExtraFiles, r)
+ output := bytes.NewBuffer(nil)
+ cmd.Stdout = output
+ cmd.Stderr = output
+
+ if err := cmd.Start(); err != nil {
+ return fmt.Errorf("Untar error on re-exec cmd: %v", err)
+ }
+ //write the options to the pipe for the untar exec to read
+ if err := json.NewEncoder(w).Encode(options); err != nil {
+ return fmt.Errorf("Untar json encode to pipe failed: %v", err)
+ }
+ w.Close()
+
+ if err := cmd.Wait(); err != nil {
+ return fmt.Errorf("Untar re-exec error: %v: output: %s", err, output)
}
return nil
}
diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go
index fb4c5c4e4..f9b5b0970 100644
--- a/pkg/chrootarchive/archive_test.go
+++ b/pkg/chrootarchive/archive_test.go
@@ -1,10 +1,15 @@
package chrootarchive
import (
+ "bytes"
+ "fmt"
+ "hash/crc32"
"io"
"io/ioutil"
"os"
+ "path"
"path/filepath"
+ "strings"
"testing"
"time"
@@ -45,6 +50,255 @@ func TestChrootTarUntar(t *testing.T) {
}
}
+// gh#10426: Verify the fix for having a huge excludes list (like on `docker load` with large # of
+// local images)
+func TestChrootUntarWithHugeExcludesList(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarHugeExcludes")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ src := filepath.Join(tmpdir, "src")
+ if err := os.MkdirAll(src, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := ioutil.WriteFile(filepath.Join(src, "toto"), []byte("hello toto"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ stream, err := archive.Tar(src, archive.Uncompressed)
+ if err != nil {
+ t.Fatal(err)
+ }
+ dest := filepath.Join(tmpdir, "dest")
+ if err := os.MkdirAll(dest, 0700); err != nil {
+ t.Fatal(err)
+ }
+ options := &archive.TarOptions{}
+ //65534 entries of 64-byte strings ~= 4MB of environment space which should overflow
+ //on most systems when passed via environment or command line arguments
+ excludes := make([]string, 65534, 65534)
+ for i := 0; i < 65534; i++ {
+ excludes[i] = strings.Repeat(string(i), 64)
+ }
+ options.ExcludePatterns = excludes
+ if err := Untar(stream, dest, options); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestChrootUntarEmptyArchive(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarEmptyArchive")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ if err := Untar(nil, tmpdir, nil); err == nil {
+ t.Fatal("expected error on empty archive")
+ }
+}
+
+func prepareSourceDirectory(numberOfFiles int, targetPath string, makeSymLinks bool) (int, error) {
+ fileData := []byte("fooo")
+ for n := 0; n < numberOfFiles; n++ {
+ fileName := fmt.Sprintf("file-%d", n)
+ if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
+ return 0, err
+ }
+ if makeSymLinks {
+ if err := os.Symlink(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
+ return 0, err
+ }
+ }
+ }
+ totalSize := numberOfFiles * len(fileData)
+ return totalSize, nil
+}
+
+func getHash(filename string) (uint32, error) {
+ stream, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return 0, err
+ }
+ hash := crc32.NewIEEE()
+ hash.Write(stream)
+ return hash.Sum32(), nil
+}
+
+func compareDirectories(src string, dest string) error {
+ changes, err := archive.ChangesDirs(dest, src)
+ if err != nil {
+ return err
+ }
+ if len(changes) > 0 {
+ return fmt.Errorf("Unexpected differences after untar: %v", changes)
+ }
+ return nil
+}
+
+func compareFiles(src string, dest string) error {
+ srcHash, err := getHash(src)
+ if err != nil {
+ return err
+ }
+ destHash, err := getHash(dest)
+ if err != nil {
+ return err
+ }
+ if srcHash != destHash {
+ return fmt.Errorf("%s is different from %s", src, dest)
+ }
+ return nil
+}
+
+func TestChrootTarUntarWithSymlink(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSymlink")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ src := filepath.Join(tmpdir, "src")
+ if err := os.MkdirAll(src, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := prepareSourceDirectory(10, src, true); err != nil {
+ t.Fatal(err)
+ }
+ dest := filepath.Join(tmpdir, "dest")
+ if err := TarUntar(src, dest); err != nil {
+ t.Fatal(err)
+ }
+ if err := compareDirectories(src, dest); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestChrootCopyWithTar(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-TestChrootCopyWithTar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ src := filepath.Join(tmpdir, "src")
+ if err := os.MkdirAll(src, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := prepareSourceDirectory(10, src, true); err != nil {
+ t.Fatal(err)
+ }
+
+ // Copy directory
+ dest := filepath.Join(tmpdir, "dest")
+ if err := CopyWithTar(src, dest); err != nil {
+ t.Fatal(err)
+ }
+ if err := compareDirectories(src, dest); err != nil {
+ t.Fatal(err)
+ }
+
+ // Copy file
+ srcfile := filepath.Join(src, "file-1")
+ dest = filepath.Join(tmpdir, "destFile")
+ destfile := filepath.Join(dest, "file-1")
+ if err := CopyWithTar(srcfile, destfile); err != nil {
+ t.Fatal(err)
+ }
+ if err := compareFiles(srcfile, destfile); err != nil {
+ t.Fatal(err)
+ }
+
+ // Copy symbolic link
+ srcLinkfile := filepath.Join(src, "file-1-link")
+ dest = filepath.Join(tmpdir, "destSymlink")
+ destLinkfile := filepath.Join(dest, "file-1-link")
+ if err := CopyWithTar(srcLinkfile, destLinkfile); err != nil {
+ t.Fatal(err)
+ }
+ if err := compareFiles(srcLinkfile, destLinkfile); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestChrootCopyFileWithTar(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-TestChrootCopyFileWithTar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ src := filepath.Join(tmpdir, "src")
+ if err := os.MkdirAll(src, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := prepareSourceDirectory(10, src, true); err != nil {
+ t.Fatal(err)
+ }
+
+ // Copy directory
+ dest := filepath.Join(tmpdir, "dest")
+ if err := CopyFileWithTar(src, dest); err == nil {
+ t.Fatal("Expected error on copying directory")
+ }
+
+ // Copy file
+ srcfile := filepath.Join(src, "file-1")
+ dest = filepath.Join(tmpdir, "destFile")
+ destfile := filepath.Join(dest, "file-1")
+ if err := CopyFileWithTar(srcfile, destfile); err != nil {
+ t.Fatal(err)
+ }
+ if err := compareFiles(srcfile, destfile); err != nil {
+ t.Fatal(err)
+ }
+
+ // Copy symbolic link
+ srcLinkfile := filepath.Join(src, "file-1-link")
+ dest = filepath.Join(tmpdir, "destSymlink")
+ destLinkfile := filepath.Join(dest, "file-1-link")
+ if err := CopyFileWithTar(srcLinkfile, destLinkfile); err != nil {
+ t.Fatal(err)
+ }
+ if err := compareFiles(srcLinkfile, destLinkfile); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestChrootUntarPath(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarPath")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ src := filepath.Join(tmpdir, "src")
+ if err := os.MkdirAll(src, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := prepareSourceDirectory(10, src, true); err != nil {
+ t.Fatal(err)
+ }
+ dest := filepath.Join(tmpdir, "dest")
+ // Untar a directory
+ if err := UntarPath(src, dest); err == nil {
+ t.Fatal("Expected error on untaring a directory")
+ }
+
+ // Untar a tar file
+ stream, err := archive.Tar(src, archive.Uncompressed)
+ if err != nil {
+ t.Fatal(err)
+ }
+ buf := new(bytes.Buffer)
+ buf.ReadFrom(stream)
+ tarfile := filepath.Join(tmpdir, "src.tar")
+ if err := ioutil.WriteFile(tarfile, buf.Bytes(), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := UntarPath(tarfile, dest); err != nil {
+ t.Fatal(err)
+ }
+ if err := compareDirectories(src, dest); err != nil {
+ t.Fatal(err)
+ }
+}
+
type slowEmptyTarReader struct {
size int
offset int
diff --git a/pkg/devicemapper/attach_loopback.go b/pkg/devicemapper/attach_loopback.go
index d39cbc6cf..424a97468 100644
--- a/pkg/devicemapper/attach_loopback.go
+++ b/pkg/devicemapper/attach_loopback.go
@@ -7,7 +7,7 @@ import (
"os"
"syscall"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
func stringToLoopName(src string) [LoNameSize]uint8 {
@@ -39,20 +39,20 @@ func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.Fil
fi, err := os.Stat(target)
if err != nil {
if os.IsNotExist(err) {
- log.Errorf("There are no more loopback devices available.")
+ logrus.Errorf("There are no more loopback devices available.")
}
return nil, ErrAttachLoopbackDevice
}
if fi.Mode()&os.ModeDevice != os.ModeDevice {
- log.Errorf("Loopback device %s is not a block device.", target)
+ logrus.Errorf("Loopback device %s is not a block device.", target)
continue
}
// OpenFile adds O_CLOEXEC
loopFile, err = os.OpenFile(target, os.O_RDWR, 0644)
if err != nil {
- log.Errorf("Error opening loopback device: %s", err)
+ logrus.Errorf("Error opening loopback device: %s", err)
return nil, ErrAttachLoopbackDevice
}
@@ -62,7 +62,7 @@ func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.Fil
// If the error is EBUSY, then try the next loopback
if err != syscall.EBUSY {
- log.Errorf("Cannot set up loopback device %s: %s", target, err)
+ logrus.Errorf("Cannot set up loopback device %s: %s", target, err)
return nil, ErrAttachLoopbackDevice
}
@@ -75,7 +75,7 @@ func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.Fil
// This can't happen, but let's be sure
if loopFile == nil {
- log.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name())
+ logrus.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name())
return nil, ErrAttachLoopbackDevice
}
@@ -91,13 +91,13 @@ func AttachLoopDevice(sparseName string) (loop *os.File, err error) {
// loopback from index 0.
startIndex, err := getNextFreeLoopbackIndex()
if err != nil {
- log.Debugf("Error retrieving the next available loopback: %s", err)
+ logrus.Debugf("Error retrieving the next available loopback: %s", err)
}
// OpenFile adds O_CLOEXEC
sparseFile, err := os.OpenFile(sparseName, os.O_RDWR, 0644)
if err != nil {
- log.Errorf("Error opening sparse file %s: %s", sparseName, err)
+ logrus.Errorf("Error opening sparse file %s: %s", sparseName, err)
return nil, ErrAttachLoopbackDevice
}
defer sparseFile.Close()
@@ -115,11 +115,11 @@ func AttachLoopDevice(sparseName string) (loop *os.File, err error) {
}
if err := ioctlLoopSetStatus64(loopFile.Fd(), loopInfo); err != nil {
- log.Errorf("Cannot set up loopback device info: %s", err)
+ logrus.Errorf("Cannot set up loopback device info: %s", err)
// If the call failed, then free the loopback device
if err := ioctlLoopClrFd(loopFile.Fd()); err != nil {
- log.Errorf("Error while cleaning up the loopback device")
+ logrus.Errorf("Error while cleaning up the loopback device")
}
loopFile.Close()
return nil, ErrAttachLoopbackDevice
diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go
index f3b55c853..e7f17b88c 100644
--- a/pkg/devicemapper/devmapper.go
+++ b/pkg/devicemapper/devmapper.go
@@ -8,8 +8,9 @@ import (
"os"
"runtime"
"syscall"
+ "unsafe"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
type DevmapperLogger interface {
@@ -54,6 +55,7 @@ var (
ErrTaskGetDeps = errors.New("dm_task_get_deps failed")
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
+ ErrTaskDeferredRemove = errors.New("dm_task_deferred_remove failed")
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
ErrNilCookie = errors.New("cookie ptr can't be nil")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
@@ -68,9 +70,11 @@ var (
ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity")
ErrBusy = errors.New("Device is Busy")
ErrDeviceIdExists = errors.New("Device Id Exists")
+ ErrEnxio = errors.New("No such device or address")
dmSawBusy bool
dmSawExist bool
+ dmSawEnxio bool // No Such Device or Address
)
type (
@@ -83,16 +87,17 @@ type (
Device []uint64
}
Info struct {
- Exists int
- Suspended int
- LiveTable int
- InactiveTable int
- OpenCount int32
- EventNr uint32
- Major uint32
- Minor uint32
- ReadOnly int
- TargetCount int32
+ Exists int
+ Suspended int
+ LiveTable int
+ InactiveTable int
+ OpenCount int32
+ EventNr uint32
+ Major uint32
+ Minor uint32
+ ReadOnly int
+ TargetCount int32
+ DeferredRemove int
}
TaskType int
AddNodeType int
@@ -218,6 +223,14 @@ func (t *Task) GetInfo() (*Info, error) {
return info, nil
}
+func (t *Task) GetInfoWithDeferred() (*Info, error) {
+ info := &Info{}
+ if res := DmTaskGetInfoWithDeferred(t.unmanaged, info); res != 1 {
+ return nil, ErrTaskGetInfo
+ }
+ return info, nil
+}
+
func (t *Task) GetDriverVersion() (string, error) {
res := DmTaskGetDriverVersion(t.unmanaged)
if res == "" {
@@ -226,7 +239,7 @@ func (t *Task) GetDriverVersion() (string, error) {
return res, nil
}
-func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
+func (t *Task) GetNextTarget(next unsafe.Pointer) (nextPtr unsafe.Pointer, start uint64,
length uint64, targetType string, params string) {
return DmGetNextTarget(t.unmanaged, next, &start, &length,
@@ -237,7 +250,7 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
loopInfo, err := ioctlLoopGetStatus64(file.Fd())
if err != nil {
- log.Errorf("Error get loopback backing file: %s", err)
+ logrus.Errorf("Error get loopback backing file: %s", err)
return 0, 0, ErrGetLoopbackBackingFile
}
return loopInfo.loDevice, loopInfo.loInode, nil
@@ -245,7 +258,7 @@ func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
func LoopbackSetCapacity(file *os.File) error {
if err := ioctlLoopSetCapacity(file.Fd(), 0); err != nil {
- log.Errorf("Error loopbackSetCapacity: %s", err)
+ logrus.Errorf("Error loopbackSetCapacity: %s", err)
return ErrLoopbackSetCapacity
}
return nil
@@ -283,9 +296,9 @@ func FindLoopDeviceFor(file *os.File) *os.File {
return nil
}
-func UdevWait(cookie uint) error {
- if res := DmUdevWait(cookie); res != 1 {
- log.Debugf("Failed to wait on udev cookie %d", cookie)
+func UdevWait(cookie *uint) error {
+ if res := DmUdevWait(*cookie); res != 1 {
+ logrus.Debugf("Failed to wait on udev cookie %d", *cookie)
return ErrUdevWait
}
return nil
@@ -305,7 +318,7 @@ func LogInit(logger DevmapperLogger) {
func SetDevDir(dir string) error {
if res := DmSetDevDir(dir); res != 1 {
- log.Debugf("Error dm_set_dev_dir")
+ logrus.Debugf("Error dm_set_dev_dir")
return ErrSetDevDir
}
return nil
@@ -348,8 +361,6 @@ func CookieSupported() bool {
// Useful helper for cleanup
func RemoveDevice(name string) error {
- log.Debugf("[devmapper] RemoveDevice START(%s)", name)
- defer log.Debugf("[devmapper] RemoveDevice END(%s)", name)
task, err := TaskCreateNamed(DeviceRemove, name)
if task == nil {
return err
@@ -359,7 +370,7 @@ func RemoveDevice(name string) error {
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can not set cookie: %s", err)
}
- defer UdevWait(cookie)
+ defer UdevWait(&cookie)
dmSawBusy = false // reset before the task is run
if err = task.Run(); err != nil {
@@ -372,10 +383,59 @@ func RemoveDevice(name string) error {
return nil
}
+func RemoveDeviceDeferred(name string) error {
+ logrus.Debugf("[devmapper] RemoveDeviceDeferred START(%s)", name)
+ defer logrus.Debugf("[devmapper] RemoveDeviceDeferred END(%s)", name)
+ task, err := TaskCreateNamed(DeviceRemove, name)
+ if task == nil {
+ return err
+ }
+
+ if err := DmTaskDeferredRemove(task.unmanaged); err != 1 {
+ return ErrTaskDeferredRemove
+ }
+
+ if err = task.Run(); err != nil {
+ return fmt.Errorf("Error running RemoveDeviceDeferred %s", err)
+ }
+
+ return nil
+}
+
+// Useful helper for cleanup
+func CancelDeferredRemove(deviceName string) error {
+ task, err := TaskCreateNamed(DeviceTargetMsg, deviceName)
+ if task == nil {
+ return err
+ }
+
+ if err := task.SetSector(0); err != nil {
+ return fmt.Errorf("Can't set sector %s", err)
+ }
+
+ if err := task.SetMessage(fmt.Sprintf("@cancel_deferred_remove")); err != nil {
+ return fmt.Errorf("Can't set message %s", err)
+ }
+
+ dmSawBusy = false
+ dmSawEnxio = false
+ if err := task.Run(); err != nil {
+ // A device might be being deleted already
+ if dmSawBusy {
+ return ErrBusy
+ } else if dmSawEnxio {
+ return ErrEnxio
+ }
+ return fmt.Errorf("Error running CancelDeferredRemove %s", err)
+
+ }
+ return nil
+}
+
func GetBlockDeviceSize(file *os.File) (uint64, error) {
size, err := ioctlBlkGetSize64(file.Fd())
if err != nil {
- log.Errorf("Error getblockdevicesize: %s", err)
+ logrus.Errorf("Error getblockdevicesize: %s", err)
return 0, ErrGetBlockSize
}
return uint64(size), nil
@@ -426,7 +486,7 @@ func CreatePool(poolName string, dataFile, metadataFile *os.File, poolBlockSize
if err := task.SetCookie(&cookie, flags); err != nil {
return fmt.Errorf("Can't set cookie %s", err)
}
- defer UdevWait(cookie)
+ defer UdevWait(&cookie)
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate (CreatePool) %s", err)
@@ -480,6 +540,17 @@ func GetInfo(name string) (*Info, error) {
return task.GetInfo()
}
+func GetInfoWithDeferred(name string) (*Info, error) {
+ task, err := TaskCreateNamed(DeviceInfo, name)
+ if task == nil {
+ return nil, err
+ }
+ if err := task.Run(); err != nil {
+ return nil, err
+ }
+ return task.GetInfoWithDeferred()
+}
+
func GetDriverVersion() (string, error) {
task := TaskCreate(DeviceVersion)
if task == nil {
@@ -494,25 +565,25 @@ func GetDriverVersion() (string, error) {
func GetStatus(name string) (uint64, uint64, string, string, error) {
task, err := TaskCreateNamed(DeviceStatus, name)
if task == nil {
- log.Debugf("GetStatus: Error TaskCreateNamed: %s", err)
+ logrus.Debugf("GetStatus: Error TaskCreateNamed: %s", err)
return 0, 0, "", "", err
}
if err := task.Run(); err != nil {
- log.Debugf("GetStatus: Error Run: %s", err)
+ logrus.Debugf("GetStatus: Error Run: %s", err)
return 0, 0, "", "", err
}
devinfo, err := task.GetInfo()
if err != nil {
- log.Debugf("GetStatus: Error GetInfo: %s", err)
+ logrus.Debugf("GetStatus: Error GetInfo: %s", err)
return 0, 0, "", "", err
}
if devinfo.Exists == 0 {
- log.Debugf("GetStatus: Non existing device %s", name)
+ logrus.Debugf("GetStatus: Non existing device %s", name)
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
}
- _, start, length, targetType, params := task.GetNextTarget(0)
+ _, start, length, targetType, params := task.GetNextTarget(unsafe.Pointer(nil))
return start, length, targetType, params, nil
}
@@ -557,7 +628,7 @@ func ResumeDevice(name string) error {
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie %s", err)
}
- defer UdevWait(cookie)
+ defer UdevWait(&cookie)
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceResume %s", err)
@@ -567,7 +638,7 @@ func ResumeDevice(name string) error {
}
func CreateDevice(poolName string, deviceId int) error {
- log.Debugf("[devmapper] CreateDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
+ logrus.Debugf("[devmapper] CreateDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
task, err := TaskCreateNamed(DeviceTargetMsg, poolName)
if task == nil {
return err
@@ -586,9 +657,10 @@ func CreateDevice(poolName string, deviceId int) error {
// Caller wants to know about ErrDeviceIdExists so that it can try with a different device id.
if dmSawExist {
return ErrDeviceIdExists
- } else {
- return fmt.Errorf("Error running CreateDevice %s", err)
}
+
+ return fmt.Errorf("Error running CreateDevice %s", err)
+
}
return nil
}
@@ -632,7 +704,7 @@ func ActivateDevice(poolName string, name string, deviceId int, size uint64) err
return fmt.Errorf("Can't set cookie %s", err)
}
- defer UdevWait(cookie)
+ defer UdevWait(&cookie)
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate (ActivateDevice) %s", err)
@@ -681,9 +753,10 @@ func CreateSnapDevice(poolName string, deviceId int, baseName string, baseDevice
// Caller wants to know about ErrDeviceIdExists so that it can try with a different device id.
if dmSawExist {
return ErrDeviceIdExists
- } else {
- return fmt.Errorf("Error running DeviceCreate (createSnapDevice) %s", err)
}
+
+ return fmt.Errorf("Error running DeviceCreate (createSnapDevice) %s", err)
+
}
if doSuspend {
diff --git a/pkg/devicemapper/devmapper_log.go b/pkg/devicemapper/devmapper_log.go
index d6550bd62..f66a20884 100644
--- a/pkg/devicemapper/devmapper_log.go
+++ b/pkg/devicemapper/devmapper_log.go
@@ -22,6 +22,10 @@ func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_cla
if strings.Contains(msg, "File exists") {
dmSawExist = true
}
+
+ if strings.Contains(msg, "No such device or address") {
+ dmSawEnxio = true
+ }
}
if dmLogger != nil {
diff --git a/pkg/devicemapper/devmapper_wrapper.go b/pkg/devicemapper/devmapper_wrapper.go
index ae4f30fb3..87c200376 100644
--- a/pkg/devicemapper/devmapper_wrapper.go
+++ b/pkg/devicemapper/devmapper_wrapper.go
@@ -90,28 +90,30 @@ const (
)
var (
- DmGetLibraryVersion = dmGetLibraryVersionFct
- DmGetNextTarget = dmGetNextTargetFct
- DmLogInitVerbose = dmLogInitVerboseFct
- DmSetDevDir = dmSetDevDirFct
- DmTaskAddTarget = dmTaskAddTargetFct
- DmTaskCreate = dmTaskCreateFct
- DmTaskDestroy = dmTaskDestroyFct
- DmTaskGetDeps = dmTaskGetDepsFct
- DmTaskGetInfo = dmTaskGetInfoFct
- DmTaskGetDriverVersion = dmTaskGetDriverVersionFct
- DmTaskRun = dmTaskRunFct
- DmTaskSetAddNode = dmTaskSetAddNodeFct
- DmTaskSetCookie = dmTaskSetCookieFct
- DmTaskSetMessage = dmTaskSetMessageFct
- DmTaskSetName = dmTaskSetNameFct
- DmTaskSetRo = dmTaskSetRoFct
- DmTaskSetSector = dmTaskSetSectorFct
- DmUdevWait = dmUdevWaitFct
- DmUdevSetSyncSupport = dmUdevSetSyncSupportFct
- DmUdevGetSyncSupport = dmUdevGetSyncSupportFct
- DmCookieSupported = dmCookieSupportedFct
- LogWithErrnoInit = logWithErrnoInitFct
+ DmGetLibraryVersion = dmGetLibraryVersionFct
+ DmGetNextTarget = dmGetNextTargetFct
+ DmLogInitVerbose = dmLogInitVerboseFct
+ DmSetDevDir = dmSetDevDirFct
+ DmTaskAddTarget = dmTaskAddTargetFct
+ DmTaskCreate = dmTaskCreateFct
+ DmTaskDestroy = dmTaskDestroyFct
+ DmTaskGetDeps = dmTaskGetDepsFct
+ DmTaskGetInfo = dmTaskGetInfoFct
+ DmTaskGetDriverVersion = dmTaskGetDriverVersionFct
+ DmTaskRun = dmTaskRunFct
+ DmTaskSetAddNode = dmTaskSetAddNodeFct
+ DmTaskSetCookie = dmTaskSetCookieFct
+ DmTaskSetMessage = dmTaskSetMessageFct
+ DmTaskSetName = dmTaskSetNameFct
+ DmTaskSetRo = dmTaskSetRoFct
+ DmTaskSetSector = dmTaskSetSectorFct
+ DmUdevWait = dmUdevWaitFct
+ DmUdevSetSyncSupport = dmUdevSetSyncSupportFct
+ DmUdevGetSyncSupport = dmUdevGetSyncSupportFct
+ DmCookieSupported = dmCookieSupportedFct
+ LogWithErrnoInit = logWithErrnoInitFct
+ DmTaskDeferredRemove = dmTaskDeferredRemoveFct
+ DmTaskGetInfoWithDeferred = dmTaskGetInfoWithDeferredFct
)
func free(p *C.char) {
@@ -219,7 +221,7 @@ func dmTaskGetDriverVersionFct(task *CDmTask) string {
return C.GoString((*C.char)(buffer))
}
-func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
+func dmGetNextTargetFct(task *CDmTask, next unsafe.Pointer, start, length *uint64, target, params *string) unsafe.Pointer {
var (
Cstart, Clength C.uint64_t
CtargetType, Cparams *C.char
@@ -231,8 +233,8 @@ func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, targ
*params = C.GoString(Cparams)
}()
- nextp := C.dm_get_next_target((*C.struct_dm_task)(task), unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
- return uintptr(nextp)
+ nextp := C.dm_get_next_target((*C.struct_dm_task)(task), next, &Cstart, &Clength, &CtargetType, &Cparams)
+ return nextp
}
func dmUdevSetSyncSupportFct(syncWithUdev int) {
diff --git a/pkg/devicemapper/devmapper_wrapper_deferred_remove.go b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go
new file mode 100644
index 000000000..ced482c96
--- /dev/null
+++ b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go
@@ -0,0 +1,33 @@
+// +build linux,!libdm_no_deferred_remove
+
+package devicemapper
+
+/*
+#cgo LDFLAGS: -L. -ldevmapper
+#include
+*/
+import "C"
+
+const LibraryDeferredRemovalSupport = true
+
+func dmTaskDeferredRemoveFct(task *CDmTask) int {
+ return int(C.dm_task_deferred_remove((*C.struct_dm_task)(task)))
+}
+
+func dmTaskGetInfoWithDeferredFct(task *CDmTask, info *Info) int {
+ Cinfo := C.struct_dm_info{}
+ defer func() {
+ info.Exists = int(Cinfo.exists)
+ info.Suspended = int(Cinfo.suspended)
+ info.LiveTable = int(Cinfo.live_table)
+ info.InactiveTable = int(Cinfo.inactive_table)
+ info.OpenCount = int32(Cinfo.open_count)
+ info.EventNr = uint32(Cinfo.event_nr)
+ info.Major = uint32(Cinfo.major)
+ info.Minor = uint32(Cinfo.minor)
+ info.ReadOnly = int(Cinfo.read_only)
+ info.TargetCount = int32(Cinfo.target_count)
+ info.DeferredRemove = int(Cinfo.deferred_remove)
+ }()
+ return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
+}
diff --git a/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go b/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go
new file mode 100644
index 000000000..16631bf19
--- /dev/null
+++ b/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go
@@ -0,0 +1,14 @@
+// +build linux,libdm_no_deferred_remove
+
+package devicemapper
+
+const LibraryDeferredRemovalSupport = false
+
+func dmTaskDeferredRemoveFct(task *CDmTask) int {
+ // Error. Nobody should be calling it.
+ return -1
+}
+
+func dmTaskGetInfoWithDeferredFct(task *CDmTask, info *Info) int {
+ return -1
+}
diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go
index 4e4a91b91..fdafb53c7 100644
--- a/pkg/fileutils/fileutils.go
+++ b/pkg/fileutils/fileutils.go
@@ -1,26 +1,170 @@
package fileutils
import (
- log "github.com/Sirupsen/logrus"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
"path/filepath"
+ "strings"
+
+ "github.com/Sirupsen/logrus"
)
-// Matches returns true if relFilePath matches any of the patterns
-func Matches(relFilePath string, patterns []string) (bool, error) {
- for _, exclude := range patterns {
- matched, err := filepath.Match(exclude, relFilePath)
+func Exclusion(pattern string) bool {
+ return pattern[0] == '!'
+}
+
+func Empty(pattern string) bool {
+ return pattern == ""
+}
+
+// Cleanpatterns takes a slice of patterns returns a new
+// slice of patterns cleaned with filepath.Clean, stripped
+// of any empty patterns and lets the caller know whether the
+// slice contains any exception patterns (prefixed with !).
+func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) {
+ // Loop over exclusion patterns and:
+ // 1. Clean them up.
+ // 2. Indicate whether we are dealing with any exception rules.
+ // 3. Error if we see a single exclusion marker on it's own (!).
+ cleanedPatterns := []string{}
+ patternDirs := [][]string{}
+ exceptions := false
+ for _, pattern := range patterns {
+ // Eliminate leading and trailing whitespace.
+ pattern = strings.TrimSpace(pattern)
+ if Empty(pattern) {
+ continue
+ }
+ if Exclusion(pattern) {
+ if len(pattern) == 1 {
+ logrus.Errorf("Illegal exclusion pattern: %s", pattern)
+ return nil, nil, false, errors.New("Illegal exclusion pattern: !")
+ }
+ exceptions = true
+ }
+ pattern = filepath.Clean(pattern)
+ cleanedPatterns = append(cleanedPatterns, pattern)
+ if Exclusion(pattern) {
+ pattern = pattern[1:]
+ }
+ patternDirs = append(patternDirs, strings.Split(pattern, "/"))
+ }
+
+ return cleanedPatterns, patternDirs, exceptions, nil
+}
+
+// Matches returns true if file matches any of the patterns
+// and isn't excluded by any of the subsequent patterns.
+func Matches(file string, patterns []string) (bool, error) {
+ file = filepath.Clean(file)
+
+ if file == "." {
+ // Don't let them exclude everything, kind of silly.
+ return false, nil
+ }
+
+ patterns, patDirs, _, err := CleanPatterns(patterns)
+ if err != nil {
+ return false, err
+ }
+
+ return OptimizedMatches(file, patterns, patDirs)
+}
+
+// Matches is basically the same as fileutils.Matches() but optimized for archive.go.
+// It will assume that the inputs have been preprocessed and therefore the function
+// doen't need to do as much error checking and clean-up. This was done to avoid
+// repeating these steps on each file being checked during the archive process.
+// The more generic fileutils.Matches() can't make these assumptions.
+func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) {
+ matched := false
+ parentPath := filepath.Dir(file)
+ parentPathDirs := strings.Split(parentPath, "/")
+
+ for i, pattern := range patterns {
+ negative := false
+
+ if Exclusion(pattern) {
+ negative = true
+ pattern = pattern[1:]
+ }
+
+ match, err := filepath.Match(pattern, file)
if err != nil {
- log.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude)
+ logrus.Errorf("Error matching: %s (pattern: %s)", file, pattern)
return false, err
}
- if matched {
- if filepath.Clean(relFilePath) == "." {
- log.Errorf("Can't exclude whole path, excluding pattern: %s", exclude)
- continue
+
+ if !match && parentPath != "." {
+ // Check to see if the pattern matches one of our parent dirs.
+ if len(patDirs[i]) <= len(parentPathDirs) {
+ match, _ = filepath.Match(strings.Join(patDirs[i], "/"),
+ strings.Join(parentPathDirs[:len(patDirs[i])], "/"))
}
- log.Debugf("Skipping excluded path: %s", relFilePath)
- return true, nil
+ }
+
+ if match {
+ matched = !negative
}
}
- return false, nil
+
+ if matched {
+ logrus.Debugf("Skipping excluded path: %s", file)
+ }
+ return matched, nil
+}
+
+func CopyFile(src, dst string) (int64, error) {
+ cleanSrc := filepath.Clean(src)
+ cleanDst := filepath.Clean(dst)
+ if cleanSrc == cleanDst {
+ return 0, nil
+ }
+ sf, err := os.Open(cleanSrc)
+ if err != nil {
+ return 0, err
+ }
+ defer sf.Close()
+ if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
+ return 0, err
+ }
+ df, err := os.Create(cleanDst)
+ if err != nil {
+ return 0, err
+ }
+ defer df.Close()
+ return io.Copy(df, sf)
+}
+
+func GetTotalUsedFds() int {
+ if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
+ logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
+ } else {
+ return len(fds)
+ }
+ return -1
+}
+
+// ReadSymlinkedDirectory returns the target directory of a symlink.
+// The target of the symbolic link may not be a file.
+func ReadSymlinkedDirectory(path string) (string, error) {
+ var realPath string
+ var err error
+ if realPath, err = filepath.Abs(path); err != nil {
+ return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
+ }
+ if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
+ return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
+ }
+ realPathInfo, err := os.Stat(realPath)
+ if err != nil {
+ return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
+ }
+ if !realPathInfo.Mode().IsDir() {
+ return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
+ }
+ return realPath, nil
}
diff --git a/pkg/fileutils/fileutils_test.go b/pkg/fileutils/fileutils_test.go
new file mode 100644
index 000000000..ef931684c
--- /dev/null
+++ b/pkg/fileutils/fileutils_test.go
@@ -0,0 +1,357 @@
+package fileutils
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+ "testing"
+)
+
+// CopyFile with invalid src
+func TestCopyFileWithInvalidSrc(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
+ defer os.RemoveAll(tempFolder)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bytes, err := CopyFile("/invalid/file/path", path.Join(tempFolder, "dest"))
+ if err == nil {
+ t.Fatal("Should have fail to copy an invalid src file")
+ }
+ if bytes != 0 {
+ t.Fatal("Should have written 0 bytes")
+ }
+
+}
+
+// CopyFile with invalid dest
+func TestCopyFileWithInvalidDest(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
+ defer os.RemoveAll(tempFolder)
+ if err != nil {
+ t.Fatal(err)
+ }
+ src := path.Join(tempFolder, "file")
+ err = ioutil.WriteFile(src, []byte("content"), 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bytes, err := CopyFile(src, path.Join(tempFolder, "/invalid/dest/path"))
+ if err == nil {
+ t.Fatal("Should have fail to copy an invalid src file")
+ }
+ if bytes != 0 {
+ t.Fatal("Should have written 0 bytes")
+ }
+
+}
+
+// CopyFile with same src and dest
+func TestCopyFileWithSameSrcAndDest(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
+ defer os.RemoveAll(tempFolder)
+ if err != nil {
+ t.Fatal(err)
+ }
+ file := path.Join(tempFolder, "file")
+ err = ioutil.WriteFile(file, []byte("content"), 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bytes, err := CopyFile(file, file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if bytes != 0 {
+ t.Fatal("Should have written 0 bytes as it is the same file.")
+ }
+}
+
+// CopyFile with same src and dest but path is different and not clean
+func TestCopyFileWithSameSrcAndDestWithPathNameDifferent(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
+ defer os.RemoveAll(tempFolder)
+ if err != nil {
+ t.Fatal(err)
+ }
+ testFolder := path.Join(tempFolder, "test")
+ err = os.MkdirAll(testFolder, 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ file := path.Join(testFolder, "file")
+ sameFile := testFolder + "/../test/file"
+ err = ioutil.WriteFile(file, []byte("content"), 0740)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bytes, err := CopyFile(file, sameFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if bytes != 0 {
+ t.Fatal("Should have written 0 bytes as it is the same file.")
+ }
+}
+
+func TestCopyFile(t *testing.T) {
+ tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
+ defer os.RemoveAll(tempFolder)
+ if err != nil {
+ t.Fatal(err)
+ }
+ src := path.Join(tempFolder, "src")
+ dest := path.Join(tempFolder, "dest")
+ ioutil.WriteFile(src, []byte("content"), 0777)
+ ioutil.WriteFile(dest, []byte("destContent"), 0777)
+ bytes, err := CopyFile(src, dest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if bytes != 7 {
+ t.Fatalf("Should have written %d bytes but wrote %d", 7, bytes)
+ }
+ actual, err := ioutil.ReadFile(dest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(actual) != "content" {
+ t.Fatalf("Dest content was '%s', expected '%s'", string(actual), "content")
+ }
+}
+
+// Reading a symlink to a directory must return the directory
+func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) {
+ var err error
+ if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil {
+ t.Errorf("failed to create directory: %s", err)
+ }
+
+ if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil {
+ t.Errorf("failed to create symlink: %s", err)
+ }
+
+ var path string
+ if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil {
+ t.Fatalf("failed to read symlink to directory: %s", err)
+ }
+
+ if path != "/tmp/testReadSymlinkToExistingDirectory" {
+ t.Fatalf("symlink returned unexpected directory: %s", path)
+ }
+
+ if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil {
+ t.Errorf("failed to remove temporary directory: %s", err)
+ }
+
+ if err = os.Remove("/tmp/dirLinkTest"); err != nil {
+ t.Errorf("failed to remove symlink: %s", err)
+ }
+}
+
+// Reading a non-existing symlink must fail
+func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) {
+ var path string
+ var err error
+ if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil {
+ t.Fatalf("error expected for non-existing symlink")
+ }
+
+ if path != "" {
+ t.Fatalf("expected empty path, but '%s' was returned", path)
+ }
+}
+
+// Reading a symlink to a file must fail
+func TestReadSymlinkedDirectoryToFile(t *testing.T) {
+ var err error
+ var file *os.File
+
+ if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil {
+ t.Fatalf("failed to create file: %s", err)
+ }
+
+ file.Close()
+
+ if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil {
+ t.Errorf("failed to create symlink: %s", err)
+ }
+
+ var path string
+ if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil {
+ t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed")
+ }
+
+ if path != "" {
+ t.Fatalf("path should've been empty: %s", path)
+ }
+
+ if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil {
+ t.Errorf("failed to remove file: %s", err)
+ }
+
+ if err = os.Remove("/tmp/fileLinkTest"); err != nil {
+ t.Errorf("failed to remove symlink: %s", err)
+ }
+}
+
+func TestWildcardMatches(t *testing.T) {
+ match, _ := Matches("fileutils.go", []string{"*"})
+ if match != true {
+ t.Errorf("failed to get a wildcard match, got %v", match)
+ }
+}
+
+// A simple pattern match should return true.
+func TestPatternMatches(t *testing.T) {
+ match, _ := Matches("fileutils.go", []string{"*.go"})
+ if match != true {
+ t.Errorf("failed to get a match, got %v", match)
+ }
+}
+
+// An exclusion followed by an inclusion should return true.
+func TestExclusionPatternMatchesPatternBefore(t *testing.T) {
+ match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"})
+ if match != true {
+ t.Errorf("failed to get true match on exclusion pattern, got %v", match)
+ }
+}
+
+// A folder pattern followed by an exception should return false.
+func TestPatternMatchesFolderExclusions(t *testing.T) {
+ match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"})
+ if match != false {
+ t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
+ }
+}
+
+// A folder pattern followed by an exception should return false.
+func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) {
+ match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"})
+ if match != false {
+ t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
+ }
+}
+
+// A folder pattern followed by an exception should return false.
+func TestPatternMatchesFolderWildcardExclusions(t *testing.T) {
+ match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"})
+ if match != false {
+ t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
+ }
+}
+
+// A pattern followed by an exclusion should return false.
+func TestExclusionPatternMatchesPatternAfter(t *testing.T) {
+ match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"})
+ if match != false {
+ t.Errorf("failed to get false match on exclusion pattern, got %v", match)
+ }
+}
+
+// A filename evaluating to . should return false.
+func TestExclusionPatternMatchesWholeDirectory(t *testing.T) {
+ match, _ := Matches(".", []string{"*.go"})
+ if match != false {
+ t.Errorf("failed to get false match on ., got %v", match)
+ }
+}
+
+// A single ! pattern should return an error.
+func TestSingleExclamationError(t *testing.T) {
+ _, err := Matches("fileutils.go", []string{"!"})
+ if err == nil {
+ t.Errorf("failed to get an error for a single exclamation point, got %v", err)
+ }
+}
+
+// A string preceded with a ! should return true from Exclusion.
+func TestExclusion(t *testing.T) {
+ exclusion := Exclusion("!")
+ if !exclusion {
+ t.Errorf("failed to get true for a single !, got %v", exclusion)
+ }
+}
+
+// Matches with no patterns
+func TestMatchesWithNoPatterns(t *testing.T) {
+ matches, err := Matches("/any/path/there", []string{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if matches {
+ t.Fatalf("Should not have match anything")
+ }
+}
+
+// Matches with malformed patterns
+func TestMatchesWithMalformedPatterns(t *testing.T) {
+ matches, err := Matches("/any/path/there", []string{"["})
+ if err == nil {
+ t.Fatal("Should have failed because of a malformed syntax in the pattern")
+ }
+ if matches {
+ t.Fatalf("Should not have match anything")
+ }
+}
+
+// An empty string should return true from Empty.
+func TestEmpty(t *testing.T) {
+ empty := Empty("")
+ if !empty {
+ t.Errorf("failed to get true for an empty string, got %v", empty)
+ }
+}
+
+func TestCleanPatterns(t *testing.T) {
+ cleaned, _, _, _ := CleanPatterns([]string{"docs", "config"})
+ if len(cleaned) != 2 {
+ t.Errorf("expected 2 element slice, got %v", len(cleaned))
+ }
+}
+
+func TestCleanPatternsStripEmptyPatterns(t *testing.T) {
+ cleaned, _, _, _ := CleanPatterns([]string{"docs", "config", ""})
+ if len(cleaned) != 2 {
+ t.Errorf("expected 2 element slice, got %v", len(cleaned))
+ }
+}
+
+func TestCleanPatternsExceptionFlag(t *testing.T) {
+ _, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md"})
+ if !exceptions {
+ t.Errorf("expected exceptions to be true, got %v", exceptions)
+ }
+}
+
+func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) {
+ _, _, exceptions, _ := CleanPatterns([]string{"docs", " !docs/README.md"})
+ if !exceptions {
+ t.Errorf("expected exceptions to be true, got %v", exceptions)
+ }
+}
+
+func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) {
+ _, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md "})
+ if !exceptions {
+ t.Errorf("expected exceptions to be true, got %v", exceptions)
+ }
+}
+
+func TestCleanPatternsErrorSingleException(t *testing.T) {
+ _, _, _, err := CleanPatterns([]string{"!"})
+ if err == nil {
+ t.Errorf("expected error on single exclamation point, got %v", err)
+ }
+}
+
+func TestCleanPatternsFolderSplit(t *testing.T) {
+ _, dirs, _, _ := CleanPatterns([]string{"docs/config/CONFIG.md"})
+ if dirs[0][0] != "docs" {
+ t.Errorf("expected first element in dirs slice to be docs, got %v", dirs[0][1])
+ }
+ if dirs[0][1] != "config" {
+ t.Errorf("expected first element in dirs slice to be config, got %v", dirs[0][1])
+ }
+}
diff --git a/pkg/graphdb/graphdb.go b/pkg/graphdb/graphdb.go
index c6f13eda2..b9433dbdd 100644
--- a/pkg/graphdb/graphdb.go
+++ b/pkg/graphdb/graphdb.go
@@ -378,12 +378,22 @@ func (db *Database) Purge(id string) (int, error) {
tx.Rollback()
return -1, err
}
-
changes, err := rows.RowsAffected()
if err != nil {
return -1, err
}
+ // Clear who's using this id as parent
+ refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id)
+ if err != nil {
+ tx.Rollback()
+ return -1, err
+ }
+ refsCount, err := refs.RowsAffected()
+ if err != nil {
+ return -1, err
+ }
+
// Delete entity
if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil {
tx.Rollback()
@@ -394,7 +404,7 @@ func (db *Database) Purge(id string) (int, error) {
return -1, err
}
- return int(changes), nil
+ return int(changes + refsCount), nil
}
// Rename an edge for a given path
diff --git a/pkg/graphdb/graphdb_test.go b/pkg/graphdb/graphdb_test.go
index f22828560..1cd223bd9 100644
--- a/pkg/graphdb/graphdb_test.go
+++ b/pkg/graphdb/graphdb_test.go
@@ -52,7 +52,7 @@ func TestGetRootEntity(t *testing.T) {
t.Fatal("Entity should not be nil")
}
if e.ID() != "0" {
- t.Fatalf("Enity id should be 0, got %s", e.ID())
+ t.Fatalf("Entity id should be 0, got %s", e.ID())
}
}
@@ -74,7 +74,7 @@ func TestSetDuplicateEntity(t *testing.T) {
t.Fatal(err)
}
if _, err := db.Set("/foo", "43"); err == nil {
- t.Fatalf("Creating an entry with a duplciate path did not cause an error")
+ t.Fatalf("Creating an entry with a duplicate path did not cause an error")
}
}
@@ -472,8 +472,8 @@ func TestPurgeId(t *testing.T) {
db.Set("/webapp", "1")
- if db.Refs("1") != 1 {
- t.Fatal("Expect reference count to be 1")
+ if c := db.Refs("1"); c != 1 {
+ t.Fatalf("Expect reference count to be 1, got %d", c)
}
db.Set("/db", "2")
@@ -484,7 +484,45 @@ func TestPurgeId(t *testing.T) {
t.Fatal(err)
}
if count != 2 {
- t.Fatal("Expected 2 references to be removed")
+ t.Fatalf("Expected 2 references to be removed, got %d", count)
+ }
+}
+
+// Regression test https://github.com/docker/docker/issues/12334
+func TestPurgeIdRefPaths(t *testing.T) {
+ db, dbpath := newTestDb(t)
+ defer destroyTestDb(dbpath)
+
+ db.Set("/webapp", "1")
+ db.Set("/db", "2")
+
+ db.Set("/db/webapp", "1")
+
+ if c := db.Refs("1"); c != 2 {
+ t.Fatalf("Expected 2 reference for webapp, got %d", c)
+ }
+ if c := db.Refs("2"); c != 1 {
+ t.Fatalf("Expected 1 reference for db, got %d", c)
+ }
+
+ if rp := db.RefPaths("2"); len(rp) != 1 {
+ t.Fatalf("Expected 1 reference path for db, got %d", len(rp))
+ }
+
+ count, err := db.Purge("2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if count != 2 {
+ t.Fatalf("Expected 2 rows to be removed, got %d", count)
+ }
+
+ if c := db.Refs("2"); c != 0 {
+ t.Fatalf("Expected 0 reference for db, got %d", c)
+ }
+ if c := db.Refs("1"); c != 1 {
+ t.Fatalf("Expected 1 reference for webapp, got %d", c)
}
}
diff --git a/pkg/httputils/httputils.go b/pkg/httputils/httputils.go
new file mode 100644
index 000000000..1c922240e
--- /dev/null
+++ b/pkg/httputils/httputils.go
@@ -0,0 +1,26 @@
+package httputils
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/docker/docker/pkg/jsonmessage"
+)
+
+// Request a given URL and return an io.Reader
+func Download(url string) (resp *http.Response, err error) {
+ if resp, err = http.Get(url); err != nil {
+ return nil, err
+ }
+ if resp.StatusCode >= 400 {
+ return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status)
+ }
+ return resp, nil
+}
+
+func NewHTTPRequestError(msg string, res *http.Response) error {
+ return &jsonmessage.JSONError{
+ Message: msg,
+ Code: res.StatusCode,
+ }
+}
diff --git a/pkg/httputils/resumablerequestreader.go b/pkg/httputils/resumablerequestreader.go
index 10edd43a9..f690d0e0e 100644
--- a/pkg/httputils/resumablerequestreader.go
+++ b/pkg/httputils/resumablerequestreader.go
@@ -6,7 +6,7 @@ import (
"net/http"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
type resumableRequestReader struct {
@@ -72,7 +72,7 @@ func (r *resumableRequestReader) Read(p []byte) (n int, err error) {
r.cleanUpResponse()
}
if err != nil && err != io.EOF {
- log.Infof("encountered error during pull and clearing it before resume: %s", err)
+ logrus.Infof("encountered error during pull and clearing it before resume: %s", err)
err = nil
}
return n, err
diff --git a/pkg/httputils/resumablerequestreader_test.go b/pkg/httputils/resumablerequestreader_test.go
new file mode 100644
index 000000000..35338600d
--- /dev/null
+++ b/pkg/httputils/resumablerequestreader_test.go
@@ -0,0 +1,83 @@
+package httputils
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestResumableRequestReader(t *testing.T) {
+
+ srvtxt := "some response text data"
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, srvtxt)
+ }))
+ defer ts.Close()
+
+ var req *http.Request
+ req, err := http.NewRequest("GET", ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client := &http.Client{}
+ retries := uint32(5)
+ imgSize := int64(len(srvtxt))
+
+ resreq := ResumableRequestReader(client, req, retries, imgSize)
+ defer resreq.Close()
+
+ data, err := ioutil.ReadAll(resreq)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ resstr := strings.TrimSuffix(string(data), "\n")
+
+ if resstr != srvtxt {
+ t.Errorf("resstr != srvtxt")
+ }
+}
+
+func TestResumableRequestReaderWithInitialResponse(t *testing.T) {
+
+ srvtxt := "some response text data"
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, srvtxt)
+ }))
+ defer ts.Close()
+
+ var req *http.Request
+ req, err := http.NewRequest("GET", ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client := &http.Client{}
+ retries := uint32(5)
+ imgSize := int64(len(srvtxt))
+
+ res, err := client.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ resreq := ResumableRequestReaderWithInitialResponse(client, req, retries, imgSize, res)
+ defer resreq.Close()
+
+ data, err := ioutil.ReadAll(resreq)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ resstr := strings.TrimSuffix(string(data), "\n")
+
+ if resstr != srvtxt {
+ t.Errorf("resstr != srvtxt")
+ }
+}
diff --git a/pkg/ioutils/readers.go b/pkg/ioutils/readers.go
index 58ff1af63..0e542cbad 100644
--- a/pkg/ioutils/readers.go
+++ b/pkg/ioutils/readers.go
@@ -3,6 +3,8 @@ package ioutils
import (
"bytes"
"crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
"io"
"math/big"
"sync"
@@ -215,3 +217,11 @@ func (r *bufReader) Close() error {
}
return closer.Close()
}
+
+func HashData(src io.Reader) (string, error) {
+ h := sha256.New()
+ if _, err := io.Copy(h, src); err != nil {
+ return "", err
+ }
+ return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
+}
diff --git a/pkg/ioutils/readers_test.go b/pkg/ioutils/readers_test.go
index 0af978e06..d220487ad 100644
--- a/pkg/ioutils/readers_test.go
+++ b/pkg/ioutils/readers_test.go
@@ -2,11 +2,91 @@ package ioutils
import (
"bytes"
+ "fmt"
"io"
"io/ioutil"
+ "strings"
"testing"
)
+// Implement io.Reader
+type errorReader struct{}
+
+func (r *errorReader) Read(p []byte) (int, error) {
+ return 0, fmt.Errorf("Error reader always fail.")
+}
+
+func TestReadCloserWrapperClose(t *testing.T) {
+ reader := strings.NewReader("A string reader")
+ wrapper := NewReadCloserWrapper(reader, func() error {
+ return fmt.Errorf("This will be called when closing")
+ })
+ err := wrapper.Close()
+ if err == nil || !strings.Contains(err.Error(), "This will be called when closing") {
+ t.Fatalf("readCloserWrapper should have call the anonymous func and thus, fail.")
+ }
+}
+
+func TestReaderErrWrapperReadOnError(t *testing.T) {
+ called := false
+ reader := &errorReader{}
+ wrapper := NewReaderErrWrapper(reader, func() {
+ called = true
+ })
+ _, err := wrapper.Read([]byte{})
+ if err == nil || !strings.Contains(err.Error(), "Error reader always fail.") {
+ t.Fatalf("readErrWrapper should returned an error")
+ }
+ if !called {
+ t.Fatalf("readErrWrapper should have call the anonymous function on failure")
+ }
+}
+
+func TestReaderErrWrapperRead(t *testing.T) {
+ reader := strings.NewReader("a string reader.")
+ wrapper := NewReaderErrWrapper(reader, func() {
+ t.Fatalf("readErrWrapper should not have called the anonymous function on failure")
+ })
+ // Read 20 byte (should be ok with the string above)
+ num, err := wrapper.Read(make([]byte, 20))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if num != 16 {
+ t.Fatalf("readerErrWrapper should have read 16 byte, but read %d", num)
+ }
+}
+
+func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) {
+ reader, writer := io.Pipe()
+
+ drainBuffer := make([]byte, 1024)
+ buffer := bytes.Buffer{}
+ bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, &buffer)
+
+ // Write everything down to a Pipe
+ // Usually, a pipe should block but because of the buffered reader,
+ // the writes will go through
+ done := make(chan bool)
+ go func() {
+ writer.Write([]byte("hello world"))
+ writer.Close()
+ done <- true
+ }()
+
+ // Drain the reader *after* everything has been written, just to verify
+ // it is indeed buffering
+ <-done
+
+ output, err := ioutil.ReadAll(bufreader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(output, []byte("hello world")) {
+ t.Error(string(output))
+ }
+}
+
func TestBufReader(t *testing.T) {
reader, writer := io.Pipe()
bufreader := NewBufReader(reader)
@@ -33,6 +113,50 @@ func TestBufReader(t *testing.T) {
}
}
+func TestBufReaderCloseWithNonReaderCloser(t *testing.T) {
+ reader := strings.NewReader("buffer")
+ bufreader := NewBufReader(reader)
+
+ if err := bufreader.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+}
+
+// implements io.ReadCloser
+type simpleReaderCloser struct{}
+
+func (r *simpleReaderCloser) Read(p []byte) (n int, err error) {
+ return 0, nil
+}
+
+func (r *simpleReaderCloser) Close() error {
+ return nil
+}
+
+func TestBufReaderCloseWithReaderCloser(t *testing.T) {
+ reader := &simpleReaderCloser{}
+ bufreader := NewBufReader(reader)
+
+ err := bufreader.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+}
+
+func TestHashData(t *testing.T) {
+ reader := strings.NewReader("hash-me")
+ actual, err := HashData(reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected := "sha256:4d11186aed035cc624d553e10db358492c84a7cd6b9670d92123c144930450aa"
+ if actual != expected {
+ t.Fatalf("Expecting %s, got %s", expected, actual)
+ }
+}
+
type repeatedReader struct {
readCount int
maxReads int
diff --git a/pkg/ioutils/writeflusher.go b/pkg/ioutils/writeflusher.go
new file mode 100644
index 000000000..25095474d
--- /dev/null
+++ b/pkg/ioutils/writeflusher.go
@@ -0,0 +1,47 @@
+package ioutils
+
+import (
+ "io"
+ "net/http"
+ "sync"
+)
+
+type WriteFlusher struct {
+ sync.Mutex
+ w io.Writer
+ flusher http.Flusher
+ flushed bool
+}
+
+func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
+ wf.Lock()
+ defer wf.Unlock()
+ n, err = wf.w.Write(b)
+ wf.flushed = true
+ wf.flusher.Flush()
+ return n, err
+}
+
+// Flush the stream immediately.
+func (wf *WriteFlusher) Flush() {
+ wf.Lock()
+ defer wf.Unlock()
+ wf.flushed = true
+ wf.flusher.Flush()
+}
+
+func (wf *WriteFlusher) Flushed() bool {
+ wf.Lock()
+ defer wf.Unlock()
+ return wf.flushed
+}
+
+func NewWriteFlusher(w io.Writer) *WriteFlusher {
+ var flusher http.Flusher
+ if f, ok := w.(http.Flusher); ok {
+ flusher = f
+ } else {
+ flusher = &NopFlusher{}
+ }
+ return &WriteFlusher{w: w, flusher: flusher}
+}
diff --git a/pkg/ioutils/writers.go b/pkg/ioutils/writers.go
index c0b3608fe..43fdc44ea 100644
--- a/pkg/ioutils/writers.go
+++ b/pkg/ioutils/writers.go
@@ -37,3 +37,24 @@ func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
closer: closer,
}
}
+
+// Wrap a concrete io.Writer and hold a count of the number
+// of bytes written to the writer during a "session".
+// This can be convenient when write return is masked
+// (e.g., json.Encoder.Encode())
+type WriteCounter struct {
+ Count int64
+ Writer io.Writer
+}
+
+func NewWriteCounter(w io.Writer) *WriteCounter {
+ return &WriteCounter{
+ Writer: w,
+ }
+}
+
+func (wc *WriteCounter) Write(p []byte) (count int, err error) {
+ count, err = wc.Writer.Write(p)
+ wc.Count += int64(count)
+ return
+}
diff --git a/pkg/ioutils/writers_test.go b/pkg/ioutils/writers_test.go
new file mode 100644
index 000000000..564b1cd4f
--- /dev/null
+++ b/pkg/ioutils/writers_test.go
@@ -0,0 +1,65 @@
+package ioutils
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+func TestWriteCloserWrapperClose(t *testing.T) {
+ called := false
+ writer := bytes.NewBuffer([]byte{})
+ wrapper := NewWriteCloserWrapper(writer, func() error {
+ called = true
+ return nil
+ })
+ if err := wrapper.Close(); err != nil {
+ t.Fatal(err)
+ }
+ if !called {
+ t.Fatalf("writeCloserWrapper should have call the anonymous function.")
+ }
+}
+
+func TestNopWriteCloser(t *testing.T) {
+ writer := bytes.NewBuffer([]byte{})
+ wrapper := NopWriteCloser(writer)
+ if err := wrapper.Close(); err != nil {
+ t.Fatal("NopWriteCloser always return nil on Close.")
+ }
+
+}
+
+func TestNopWriter(t *testing.T) {
+ nw := &NopWriter{}
+ l, err := nw.Write([]byte{'c'})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if l != 1 {
+ t.Fatalf("Expected 1 got %d", l)
+ }
+}
+
+func TestWriteCounter(t *testing.T) {
+ dummy1 := "This is a dummy string."
+ dummy2 := "This is another dummy string."
+ totalLength := int64(len(dummy1) + len(dummy2))
+
+ reader1 := strings.NewReader(dummy1)
+ reader2 := strings.NewReader(dummy2)
+
+ var buffer bytes.Buffer
+ wc := NewWriteCounter(&buffer)
+
+ reader1.WriteTo(wc)
+ reader2.WriteTo(wc)
+
+ if wc.Count != totalLength {
+ t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength)
+ }
+
+ if buffer.String() != dummy1+dummy2 {
+ t.Error("Wrong message written")
+ }
+}
diff --git a/pkg/jsonlog/jsonlog.go b/pkg/jsonlog/jsonlog.go
index e2c2a2cab..85afb3b50 100644
--- a/pkg/jsonlog/jsonlog.go
+++ b/pkg/jsonlog/jsonlog.go
@@ -5,8 +5,6 @@ import (
"fmt"
"io"
"time"
-
- log "github.com/Sirupsen/logrus"
)
type JSONLog struct {
@@ -32,16 +30,21 @@ func (jl *JSONLog) Reset() {
jl.Created = time.Time{}
}
-func WriteLog(src io.Reader, dst io.Writer, format string) error {
+func WriteLog(src io.Reader, dst io.Writer, format string, since time.Time) error {
dec := json.NewDecoder(src)
l := &JSONLog{}
for {
- if err := dec.Decode(l); err == io.EOF {
- return nil
- } else if err != nil {
- log.Printf("Error streaming logs: %s", err)
+ l.Reset()
+ if err := dec.Decode(l); err != nil {
+ if err == io.EOF {
+ return nil
+ }
return err
}
+ if !since.IsZero() && l.Created.Before(since) {
+ continue
+ }
+
line, err := l.Format(format)
if err != nil {
return err
@@ -49,6 +52,5 @@ func WriteLog(src io.Reader, dst io.Writer, format string) error {
if _, err := io.WriteString(dst, line); err != nil {
return err
}
- l.Reset()
}
}
diff --git a/pkg/jsonlog/jsonlog_marshalling.go b/pkg/jsonlog/jsonlog_marshalling.go
index 6244eb01a..abaa8a73b 100644
--- a/pkg/jsonlog/jsonlog_marshalling.go
+++ b/pkg/jsonlog/jsonlog_marshalling.go
@@ -65,8 +65,7 @@ import (
func (mj *JSONLog) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.Grow(1024)
- err := mj.MarshalJSONBuf(&buf)
- if err != nil {
+ if err := mj.MarshalJSONBuf(&buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
diff --git a/pkg/jsonlog/jsonlog_test.go b/pkg/jsonlog/jsonlog_test.go
index fa53825b9..d4b26fcb4 100644
--- a/pkg/jsonlog/jsonlog_test.go
+++ b/pkg/jsonlog/jsonlog_test.go
@@ -21,7 +21,7 @@ func TestWriteLog(t *testing.T) {
}
w := bytes.NewBuffer(nil)
format := timeutils.RFC3339NanoFixed
- if err := WriteLog(&buf, w, format); err != nil {
+ if err := WriteLog(&buf, w, format, time.Time{}); err != nil {
t.Fatal(err)
}
res := w.String()
@@ -52,7 +52,7 @@ func BenchmarkWriteLog(b *testing.B) {
b.SetBytes(int64(r.Len()))
b.ResetTimer()
for i := 0; i < b.N; i++ {
- if err := WriteLog(r, w, format); err != nil {
+ if err := WriteLog(r, w, format, time.Time{}); err != nil {
b.Fatal(err)
}
b.StopTimer()
diff --git a/pkg/jsonlog/jsonlogbytes.go b/pkg/jsonlog/jsonlogbytes.go
new file mode 100644
index 000000000..0d8fd9c82
--- /dev/null
+++ b/pkg/jsonlog/jsonlogbytes.go
@@ -0,0 +1,115 @@
+package jsonlog
+
+import (
+ "bytes"
+ "unicode/utf8"
+)
+
+// JSONLogBytes is based on JSONLog.
+// It allows marshalling JSONLog from Log as []byte
+// and an already marshalled Created timestamp.
+type JSONLogBytes struct {
+ Log []byte `json:"log,omitempty"`
+ Stream string `json:"stream,omitempty"`
+ Created string `json:"time"`
+}
+
+// MarshalJSONBuf is based on the same method from JSONLog
+// It has been modified to take into account the necessary changes.
+func (mj *JSONLogBytes) MarshalJSONBuf(buf *bytes.Buffer) error {
+ var first = true
+
+ buf.WriteString(`{`)
+ if len(mj.Log) != 0 {
+ if first == true {
+ first = false
+ } else {
+ buf.WriteString(`,`)
+ }
+ buf.WriteString(`"log":`)
+ ffjson_WriteJsonBytesAsString(buf, mj.Log)
+ }
+ if len(mj.Stream) != 0 {
+ if first == true {
+ first = false
+ } else {
+ buf.WriteString(`,`)
+ }
+ buf.WriteString(`"stream":`)
+ ffjson_WriteJsonString(buf, mj.Stream)
+ }
+ if first == true {
+ first = false
+ } else {
+ buf.WriteString(`,`)
+ }
+ buf.WriteString(`"time":`)
+ buf.WriteString(mj.Created)
+ buf.WriteString(`}`)
+ return nil
+}
+
+// This is based on ffjson_WriteJsonString. It has been changed
+// to accept a string passed as a slice of bytes.
+func ffjson_WriteJsonBytesAsString(buf *bytes.Buffer, s []byte) {
+ const hex = "0123456789abcdef"
+
+ buf.WriteByte('"')
+ start := 0
+ for i := 0; i < len(s); {
+ if b := s[i]; b < utf8.RuneSelf {
+ if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
+ i++
+ continue
+ }
+ if start < i {
+ buf.Write(s[start:i])
+ }
+ switch b {
+ case '\\', '"':
+ buf.WriteByte('\\')
+ buf.WriteByte(b)
+ case '\n':
+ buf.WriteByte('\\')
+ buf.WriteByte('n')
+ case '\r':
+ buf.WriteByte('\\')
+ buf.WriteByte('r')
+ default:
+
+ buf.WriteString(`\u00`)
+ buf.WriteByte(hex[b>>4])
+ buf.WriteByte(hex[b&0xF])
+ }
+ i++
+ start = i
+ continue
+ }
+ c, size := utf8.DecodeRune(s[i:])
+ if c == utf8.RuneError && size == 1 {
+ if start < i {
+ buf.Write(s[start:i])
+ }
+ buf.WriteString(`\ufffd`)
+ i += size
+ start = i
+ continue
+ }
+
+ if c == '\u2028' || c == '\u2029' {
+ if start < i {
+ buf.Write(s[start:i])
+ }
+ buf.WriteString(`\u202`)
+ buf.WriteByte(hex[c&0xF])
+ i += size
+ start = i
+ continue
+ }
+ i += size
+ }
+ if start < len(s) {
+ buf.Write(s[start:])
+ }
+ buf.WriteByte('"')
+}
diff --git a/utils/jsonmessage.go b/pkg/jsonmessage/jsonmessage.go
similarity index 99%
rename from utils/jsonmessage.go
rename to pkg/jsonmessage/jsonmessage.go
index 74d311271..7db1626e4 100644
--- a/utils/jsonmessage.go
+++ b/pkg/jsonmessage/jsonmessage.go
@@ -1,4 +1,4 @@
-package utils
+package jsonmessage
import (
"encoding/json"
diff --git a/utils/jsonmessage_test.go b/pkg/jsonmessage/jsonmessage_test.go
similarity index 97%
rename from utils/jsonmessage_test.go
rename to pkg/jsonmessage/jsonmessage_test.go
index b9103da1a..4c3f5666b 100644
--- a/utils/jsonmessage_test.go
+++ b/pkg/jsonmessage/jsonmessage_test.go
@@ -1,4 +1,4 @@
-package utils
+package jsonmessage
import (
"testing"
diff --git a/pkg/listenbuffer/README.md b/pkg/listenbuffer/README.md
new file mode 100644
index 000000000..227350981
--- /dev/null
+++ b/pkg/listenbuffer/README.md
@@ -0,0 +1,27 @@
+# listenbuffer
+
+listenbuffer uses the kernel's listening backlog functionality to queue
+connections, allowing applications to start listening immediately and handle
+connections later. This is signaled by closing the activation channel passed to
+the constructor.
+
+The maximum amount of queued connections depends on the configuration of your
+kernel (typically called SOMAXXCON) and cannot be configured in Go with the
+net package. See `src/net/sock_platform.go` in the Go tree or consult your
+kernel's manual.
+
+ activator := make(chan struct{})
+ buffer, err := NewListenBuffer("tcp", "localhost:4000", activator)
+ if err != nil {
+ panic(err)
+ }
+
+ // will block until activator has been closed or is sent an event
+ client, err := buffer.Accept()
+
+Somewhere else in your application once it's been booted:
+
+ close(activator)
+
+`buffer.Accept()` will return the first client in the kernel listening queue, or
+continue to block until a client connects or an error occurs.
diff --git a/pkg/listenbuffer/buffer.go b/pkg/listenbuffer/buffer.go
index 17572c8a0..97d622c15 100644
--- a/pkg/listenbuffer/buffer.go
+++ b/pkg/listenbuffer/buffer.go
@@ -1,14 +1,38 @@
/*
- Package to allow go applications to immediately start
- listening on a socket, unix, tcp, udp but hold connections
- until the application has booted and is ready to accept them
+listenbuffer uses the kernel's listening backlog functionality to queue
+connections, allowing applications to start listening immediately and handle
+connections later. This is signaled by closing the activation channel passed to
+the constructor.
+
+The maximum amount of queued connections depends on the configuration of your
+kernel (typically called SOMAXXCON) and cannot be configured in Go with the
+net package. See `src/net/sock_platform.go` in the Go tree or consult your
+kernel's manual.
+
+ activator := make(chan struct{})
+ buffer, err := NewListenBuffer("tcp", "localhost:4000", activator)
+ if err != nil {
+ panic(err)
+ }
+
+ // will block until activator has been closed or is sent an event
+ client, err := buffer.Accept()
+
+Somewhere else in your application once it's been booted:
+
+ close(activator)
+
+`buffer.Accept()` will return the first client in the kernel listening queue, or
+continue to block until a client connects or an error occurs.
*/
package listenbuffer
import "net"
-// NewListenBuffer returns a listener listening on addr with the protocol.
-func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, error) {
+// NewListenBuffer returns a net.Listener listening on addr with the protocol
+// passed. The channel passed is used to activate the listenbuffer when the
+// caller is ready to accept connections.
+func NewListenBuffer(proto, addr string, activate <-chan struct{}) (net.Listener, error) {
wrapped, err := net.Listen(proto, addr)
if err != nil {
return nil, err
@@ -20,20 +44,26 @@ func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener,
}, nil
}
+// defaultListener is the buffered wrapper around the net.Listener
type defaultListener struct {
- wrapped net.Listener // the real listener to wrap
- ready bool // is the listner ready to start accpeting connections
- activate chan struct{}
+ wrapped net.Listener // The net.Listener wrapped by listenbuffer
+ ready bool // Whether the listenbuffer has been activated
+ activate <-chan struct{} // Channel to control activation of the listenbuffer
}
+// Close closes the wrapped socket.
func (l *defaultListener) Close() error {
return l.wrapped.Close()
}
+// Addr returns the listening address of the wrapped socket.
func (l *defaultListener) Addr() net.Addr {
return l.wrapped.Addr()
}
+// Accept returns a client connection on the wrapped socket if the listen buffer
+// has been activated. To active the listenbuffer the activation channel passed
+// to NewListenBuffer must have been closed or sent an event.
func (l *defaultListener) Accept() (net.Conn, error) {
// if the listen has been told it is ready then we can go ahead and
// start returning connections
diff --git a/pkg/listenbuffer/listen_buffer_test.go b/pkg/listenbuffer/listen_buffer_test.go
new file mode 100644
index 000000000..6ffd2f798
--- /dev/null
+++ b/pkg/listenbuffer/listen_buffer_test.go
@@ -0,0 +1,41 @@
+package listenbuffer
+
+import (
+ "io/ioutil"
+ "net"
+ "testing"
+)
+
+func TestListenBufferAllowsAcceptingWhenActivated(t *testing.T) {
+ lock := make(chan struct{})
+ buffer, err := NewListenBuffer("tcp", "", lock)
+ if err != nil {
+ t.Fatal("Unable to create listen buffer: ", err)
+ }
+
+ go func() {
+ conn, err := net.Dial("tcp", buffer.Addr().String())
+ if err != nil {
+ t.Fatal("Client failed to establish connection to server: ", err)
+ }
+
+ conn.Write([]byte("ping"))
+ conn.Close()
+ }()
+
+ close(lock)
+
+ client, err := buffer.Accept()
+ if err != nil {
+ t.Fatal("Failed to accept client: ", err)
+ }
+
+ response, err := ioutil.ReadAll(client)
+ if err != nil {
+ t.Fatal("Failed to read from client: ", err)
+ }
+
+ if string(response) != "ping" {
+ t.Fatal("Expected to receive ping from client, received: ", string(response))
+ }
+}
diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go
index b35692bfd..ce003cd07 100644
--- a/pkg/mflag/flag.go
+++ b/pkg/mflag/flag.go
@@ -486,8 +486,7 @@ func (f *FlagSet) Set(name, value string) error {
if !ok {
return fmt.Errorf("no such flag -%v", name)
}
- err := flag.Value.Set(value)
- if err != nil {
+ if err := flag.Value.Set(value); err != nil {
return err
}
if f.actual == nil {
@@ -561,7 +560,7 @@ func defaultUsage(f *FlagSet) {
// Usage prints to standard error a usage message documenting all defined command-line flags.
// The function is a variable that may be changed to point to a custom function.
var Usage = func() {
- fmt.Fprintf(CommandLine.output, "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(CommandLine.Out(), "Usage of %s:\n", os.Args[0])
PrintDefaults()
}
@@ -941,11 +940,11 @@ func (f *FlagSet) parseOne() (bool, string, error) {
// it's a flag. does it have an argument?
f.args = f.args[1:]
- has_value := false
+ hasValue := false
value := ""
if i := strings.Index(name, "="); i != -1 {
value = trimQuotes(name[i+1:])
- has_value = true
+ hasValue = true
name = name[:i]
}
@@ -962,7 +961,7 @@ func (f *FlagSet) parseOne() (bool, string, error) {
return false, name, ErrRetry
}
if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg
- if has_value {
+ if hasValue {
if err := fv.Set(value); err != nil {
return false, "", f.failf("invalid boolean value %q for -%s: %v", value, name, err)
}
@@ -971,12 +970,12 @@ func (f *FlagSet) parseOne() (bool, string, error) {
}
} else {
// It must have a value, which might be the next argument.
- if !has_value && len(f.args) > 0 {
+ if !hasValue && len(f.args) > 0 {
// value is the next arg
- has_value = true
+ hasValue = true
value, f.args = f.args[0], f.args[1:]
}
- if !has_value {
+ if !hasValue {
return false, "", f.failf("flag needs an argument: -%s", name)
}
if err := flag.Value.Set(value); err != nil {
@@ -1054,6 +1053,42 @@ func (f *FlagSet) Parse(arguments []string) error {
return nil
}
+// ParseFlags is a utility function that adds a help flag if withHelp is true,
+// calls cmd.Parse(args) and prints a relevant error message if there are
+// incorrect number of arguments. It returns error only if error handling is
+// set to ContinueOnError and parsing fails. If error handling is set to
+// ExitOnError, it's safe to ignore the return value.
+func (cmd *FlagSet) ParseFlags(args []string, withHelp bool) error {
+ var help *bool
+ if withHelp {
+ help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage")
+ }
+ if err := cmd.Parse(args); err != nil {
+ return err
+ }
+ if help != nil && *help {
+ cmd.Usage()
+ // just in case Usage does not exit
+ os.Exit(0)
+ }
+ if str := cmd.CheckArgs(); str != "" {
+ cmd.ReportError(str, withHelp)
+ }
+ return nil
+}
+
+func (cmd *FlagSet) ReportError(str string, withHelp bool) {
+ if withHelp {
+ if os.Args[0] == cmd.Name() {
+ str += ". See '" + os.Args[0] + " --help'"
+ } else {
+ str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'"
+ }
+ }
+ fmt.Fprintf(cmd.Out(), "docker: %s\n", str)
+ os.Exit(1)
+}
+
// Parsed reports whether f.Parse has been called.
func (f *FlagSet) Parsed() bool {
return f.parsed
diff --git a/pkg/mount/flags_freebsd.go b/pkg/mount/flags_freebsd.go
index a59b58960..f166cb2f7 100644
--- a/pkg/mount/flags_freebsd.go
+++ b/pkg/mount/flags_freebsd.go
@@ -8,12 +8,25 @@ package mount
import "C"
const (
- RDONLY = C.MNT_RDONLY
- NOSUID = C.MNT_NOSUID
- NOEXEC = C.MNT_NOEXEC
- SYNCHRONOUS = C.MNT_SYNCHRONOUS
- NOATIME = C.MNT_NOATIME
+ // RDONLY will mount the filesystem as read-only.
+ RDONLY = C.MNT_RDONLY
+ // NOSUID will not allow set-user-identifier or set-group-identifier bits to
+ // take effect.
+ NOSUID = C.MNT_NOSUID
+
+ // NOEXEC will not allow execution of any binaries on the mounted file system.
+ NOEXEC = C.MNT_NOEXEC
+
+ // SYNCHRONOUS will allow any I/O to the file system to be done synchronously.
+ SYNCHRONOUS = C.MNT_SYNCHRONOUS
+
+ // NOATIME will not update the file access time when reading from a file.
+ NOATIME = C.MNT_NOATIME
+)
+
+// These flags are unsupported.
+const (
BIND = 0
DIRSYNC = 0
MANDLOCK = 0
diff --git a/pkg/mount/flags_linux.go b/pkg/mount/flags_linux.go
index 9986621c8..2f9f5c58e 100644
--- a/pkg/mount/flags_linux.go
+++ b/pkg/mount/flags_linux.go
@@ -5,26 +5,81 @@ import (
)
const (
- RDONLY = syscall.MS_RDONLY
- NOSUID = syscall.MS_NOSUID
- NODEV = syscall.MS_NODEV
- NOEXEC = syscall.MS_NOEXEC
+ // RDONLY will mount the file system read-only.
+ RDONLY = syscall.MS_RDONLY
+
+ // NOSUID will not allow set-user-identifier or set-group-identifier bits to
+ // take effect.
+ NOSUID = syscall.MS_NOSUID
+
+ // NODEV will not interpret character or block special devices on the file
+ // system.
+ NODEV = syscall.MS_NODEV
+
+ // NOEXEC will not allow execution of any binaries on the mounted file system.
+ NOEXEC = syscall.MS_NOEXEC
+
+ // SYNCHRONOUS will allow I/O to the file system to be done synchronously.
SYNCHRONOUS = syscall.MS_SYNCHRONOUS
- DIRSYNC = syscall.MS_DIRSYNC
- REMOUNT = syscall.MS_REMOUNT
- MANDLOCK = syscall.MS_MANDLOCK
- NOATIME = syscall.MS_NOATIME
- NODIRATIME = syscall.MS_NODIRATIME
- BIND = syscall.MS_BIND
- RBIND = syscall.MS_BIND | syscall.MS_REC
- UNBINDABLE = syscall.MS_UNBINDABLE
+
+ // DIRSYNC will force all directory updates within the file system to be done
+ // synchronously. This affects the following system calls: creat, link,
+ // unlink, symlink, mkdir, rmdir, mknod and rename.
+ DIRSYNC = syscall.MS_DIRSYNC
+
+ // REMOUNT will attempt to remount an already-mounted file system. This is
+ // commonly used to change the mount flags for a file system, especially to
+ // make a readonly file system writeable. It does not change device or mount
+ // point.
+ REMOUNT = syscall.MS_REMOUNT
+
+ // MANDLOCK will force mandatory locks on a filesystem.
+ MANDLOCK = syscall.MS_MANDLOCK
+
+ // NOATIME will not update the file access time when reading from a file.
+ NOATIME = syscall.MS_NOATIME
+
+ // NODIRATIME will not update the directory access time.
+ NODIRATIME = syscall.MS_NODIRATIME
+
+ // BIND remounts a subtree somewhere else.
+ BIND = syscall.MS_BIND
+
+ // RBIND remounts a subtree and all possible submounts somewhere else.
+ RBIND = syscall.MS_BIND | syscall.MS_REC
+
+ // UNBINDABLE creates a mount which cannot be cloned through a bind operation.
+ UNBINDABLE = syscall.MS_UNBINDABLE
+
+ // RUNBINDABLE marks the entire mount tree as UNBINDABLE.
RUNBINDABLE = syscall.MS_UNBINDABLE | syscall.MS_REC
- PRIVATE = syscall.MS_PRIVATE
- RPRIVATE = syscall.MS_PRIVATE | syscall.MS_REC
- SLAVE = syscall.MS_SLAVE
- RSLAVE = syscall.MS_SLAVE | syscall.MS_REC
- SHARED = syscall.MS_SHARED
- RSHARED = syscall.MS_SHARED | syscall.MS_REC
- RELATIME = syscall.MS_RELATIME
+
+ // PRIVATE creates a mount which carries no propagation abilities.
+ PRIVATE = syscall.MS_PRIVATE
+
+ // RPRIVATE marks the entire mount tree as PRIVATE.
+ RPRIVATE = syscall.MS_PRIVATE | syscall.MS_REC
+
+ // SLAVE creates a mount which receives propagation from its master, but not
+ // vice versa.
+ SLAVE = syscall.MS_SLAVE
+
+ // RSLAVE marks the entire mount tree as SLAVE.
+ RSLAVE = syscall.MS_SLAVE | syscall.MS_REC
+
+ // SHARED creates a mount which provides the ability to create mirrors of
+ // that mount such that mounts and unmounts within any of the mirrors
+ // propagate to the other mirrors.
+ SHARED = syscall.MS_SHARED
+
+ // RSHARED marks the entire mount tree as SHARED.
+ RSHARED = syscall.MS_SHARED | syscall.MS_REC
+
+ // RELATIME updates inode access times relative to modify or change time.
+ RELATIME = syscall.MS_RELATIME
+
+ // STRICTATIME allows to explicitly request full atime updates. This makes
+ // it possible for the kernel to default to relatime or noatime but still
+ // allow userspace to override it.
STRICTATIME = syscall.MS_STRICTATIME
)
diff --git a/pkg/mount/flags_unsupported.go b/pkg/mount/flags_unsupported.go
index c4f82176b..a90d3d115 100644
--- a/pkg/mount/flags_unsupported.go
+++ b/pkg/mount/flags_unsupported.go
@@ -2,6 +2,7 @@
package mount
+// These flags are unsupported.
const (
BIND = 0
DIRSYNC = 0
diff --git a/pkg/mount/mount.go b/pkg/mount/mount.go
index 5ca731601..9a20df219 100644
--- a/pkg/mount/mount.go
+++ b/pkg/mount/mount.go
@@ -4,11 +4,12 @@ import (
"time"
)
+// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*MountInfo, error) {
return parseMountTable()
}
-// Looks at /proc/self/mountinfo to determine of the specified
+// Mounted looks at /proc/self/mountinfo to determine of the specified
// mountpoint has been mounted
func Mounted(mountpoint string) (bool, error) {
entries, err := parseMountTable()
@@ -25,9 +26,10 @@ func Mounted(mountpoint string) (bool, error) {
return false, nil
}
-// Mount the specified options at the target path only if
-// the target is not mounted
-// Options must be specified as fstab style
+// Mount will mount filesystem according to the specified configuration, on the
+// condition that the target path is *not* already mounted. Options must be
+// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
+// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
flag, _ := parseOptions(options)
if flag&REMOUNT != REMOUNT {
@@ -38,9 +40,10 @@ func Mount(device, target, mType, options string) error {
return ForceMount(device, target, mType, options)
}
-// Mount the specified options at the target path
-// reguardless if the target is mounted or not
-// Options must be specified as fstab style
+// ForceMount will mount a filesystem according to the specified configuration,
+// *regardless* if the target path is not already mounted. Options must be
+// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
+// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
flag, data := parseOptions(options)
if err := mount(device, target, mType, uintptr(flag), data); err != nil {
@@ -49,7 +52,7 @@ func ForceMount(device, target, mType, options string) error {
return nil
}
-// Unmount the target only if it is mounted
+// Unmount will unmount the target filesystem, so long as it is mounted.
func Unmount(target string) error {
if mounted, err := Mounted(target); err != nil || !mounted {
return err
@@ -57,7 +60,8 @@ func Unmount(target string) error {
return ForceUnmount(target)
}
-// Unmount the target reguardless if it is mounted or not
+// ForceUnmount will force an unmount of the target filesystem, regardless if
+// it is mounted or not.
func ForceUnmount(target string) (err error) {
// Simple retry logic for unmount
for i := 0; i < 10; i++ {
diff --git a/pkg/mount/mountinfo.go b/pkg/mount/mountinfo.go
index ec8e8bca2..8ea08648c 100644
--- a/pkg/mount/mountinfo.go
+++ b/pkg/mount/mountinfo.go
@@ -1,7 +1,40 @@
package mount
+// MountInfo reveals information about a particular mounted filesystem. This
+// struct is populated from the content in the /proc//mountinfo file.
type MountInfo struct {
- Id, Parent, Major, Minor int
- Root, Mountpoint, Opts, Optional string
- Fstype, Source, VfsOpts string
+ // Id is a unique identifier of the mount (may be reused after umount).
+ Id int
+
+ // Parent indicates the ID of the mount parent (or of self for the top of the
+ // mount tree).
+ Parent int
+
+ // Major indicates one half of the device ID which identifies the device class.
+ Major int
+
+ // Minor indicates one half of the device ID which identifies a specific
+ // instance of device.
+ Minor int
+
+ // Root of the mount within the filesystem.
+ Root string
+
+ // Mountpoint indicates the mount point relative to the process's root.
+ Mountpoint string
+
+ // Opts represents mount-specific options.
+ Opts string
+
+ // Optional represents optional fields.
+ Optional string
+
+ // Fstype indicates the type of filesystem, such as EXT3.
+ Fstype string
+
+ // Source indicates filesystem specific information or "none".
+ Source string
+
+ // VfsOpts represents per super block options.
+ VfsOpts string
}
diff --git a/pkg/mount/mountinfo_freebsd.go b/pkg/mount/mountinfo_freebsd.go
index 2fe91862d..add7c3b0e 100644
--- a/pkg/mount/mountinfo_freebsd.go
+++ b/pkg/mount/mountinfo_freebsd.go
@@ -13,7 +13,8 @@ import (
"unsafe"
)
-// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts
+// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
+// bind mounts.
func parseMountTable() ([]*MountInfo, error) {
var rawEntries *C.struct_statfs
diff --git a/pkg/mount/mountinfo_linux.go b/pkg/mount/mountinfo_linux.go
index 0eb018e23..351a58ea0 100644
--- a/pkg/mount/mountinfo_linux.go
+++ b/pkg/mount/mountinfo_linux.go
@@ -28,7 +28,8 @@ const (
mountinfoFormat = "%d %d %d:%d %s %s %s %s"
)
-// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts
+// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
+// bind mounts
func parseMountTable() ([]*MountInfo, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
@@ -80,7 +81,9 @@ func parseInfoFile(r io.Reader) ([]*MountInfo, error) {
return out, nil
}
-// PidMountInfo collects the mounts for a specific Pid
+// PidMountInfo collects the mounts for a specific process ID. If the process
+// ID is unknown, it is better to use `GetMounts` which will inspect
+// "/proc/self/mountinfo" instead.
func PidMountInfo(pid int) ([]*MountInfo, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
diff --git a/pkg/mount/sharedsubtree_linux.go b/pkg/mount/sharedsubtree_linux.go
index cd9b86cef..47303bbcb 100644
--- a/pkg/mount/sharedsubtree_linux.go
+++ b/pkg/mount/sharedsubtree_linux.go
@@ -2,34 +2,50 @@
package mount
+// MakeShared ensures a mounted filesystem has the SHARED mount option enabled.
+// See the supported options in flags.go for further reference.
func MakeShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "shared")
}
+// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled.
+// See the supported options in flags.go for further reference.
func MakeRShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "rshared")
}
+// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled.
+// See the supported options in flags.go for further reference.
func MakePrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "private")
}
+// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option
+// enabled. See the supported options in flags.go for further reference.
func MakeRPrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "rprivate")
}
+// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled.
+// See the supported options in flags.go for further reference.
func MakeSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "slave")
}
+// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled.
+// See the supported options in flags.go for further reference.
func MakeRSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "rslave")
}
+// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option
+// enabled. See the supported options in flags.go for further reference.
func MakeUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "unbindable")
}
+// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount
+// option enabled. See the supported options in flags.go for further reference.
func MakeRUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "runbindable")
}
diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go
index 0a1eee3b1..b081cc7db 100644
--- a/pkg/namesgenerator/names-generator.go
+++ b/pkg/namesgenerator/names-generator.go
@@ -3,7 +3,8 @@ package namesgenerator
import (
"fmt"
"math/rand"
- "time"
+
+ "github.com/docker/docker/pkg/random"
)
var (
@@ -119,6 +120,9 @@ var (
// Gerty Theresa Cori - American biochemist who became the third woman—and first American woman—to win a Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology or Medicine. Cori was born in Prague. https://en.wikipedia.org/wiki/Gerty_Cori
"cori",
+ // Seymour Roger Cray was an American electrical engineer and supercomputer architect who designed a series of computers that were the fastest in the world for decades. https://en.wikipedia.org/wiki/Seymour_Cray
+ "cray",
+
// Marie Curie discovered radioactivity. https://en.wikipedia.org/wiki/Marie_Curie.
"curie",
@@ -185,6 +189,12 @@ var (
// Karen Spärck Jones came up with the concept of inverse document frequency, which is used in most search engines today. https://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones
"jones",
+ // Jack Kilby and Robert Noyce have invented silicone integrated circuits and gave Silicon Valley its name.
+ // - https://en.wikipedia.org/wiki/Jack_Kilby
+ // - https://en.wikipedia.org/wiki/Robert_Noyce
+ "kilby",
+ "noyce",
+
// Maria Kirch - German astronomer and first woman to discover a comet - https://en.wikipedia.org/wiki/Maria_Margarethe_Kirch
"kirch",
@@ -299,19 +309,19 @@ var (
// Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath
"yonath",
}
+
+ rnd = rand.New(random.NewSource())
)
func GetRandomName(retry int) string {
- rand.Seed(time.Now().UnixNano())
-
begin:
- name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))])
+ name := fmt.Sprintf("%s_%s", left[rnd.Intn(len(left))], right[rnd.Intn(len(right))])
if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
goto begin
}
if retry > 0 {
- name = fmt.Sprintf("%s%d", name, rand.Intn(10))
+ name = fmt.Sprintf("%s%d", name, rnd.Intn(10))
}
return name
}
diff --git a/pkg/parsers/filters/parse.go b/pkg/parsers/filters/parse.go
index 9c056bb3c..df5486d51 100644
--- a/pkg/parsers/filters/parse.go
+++ b/pkg/parsers/filters/parse.go
@@ -58,8 +58,7 @@ func FromParam(p string) (Args, error) {
if len(p) == 0 {
return args, nil
}
- err := json.Unmarshal([]byte(p), &args)
- if err != nil {
+ if err := json.NewDecoder(strings.NewReader(p)).Decode(&args); err != nil {
return nil, err
}
return args, nil
diff --git a/pkg/parsers/kernel/kernel.go b/pkg/parsers/kernel/kernel.go
index 70d09003a..5f7930684 100644
--- a/pkg/parsers/kernel/kernel.go
+++ b/pkg/parsers/kernel/kernel.go
@@ -1,3 +1,5 @@
+// +build !windows
+
package kernel
import (
diff --git a/pkg/parsers/kernel/kernel_windows.go b/pkg/parsers/kernel/kernel_windows.go
new file mode 100644
index 000000000..399d63e5f
--- /dev/null
+++ b/pkg/parsers/kernel/kernel_windows.go
@@ -0,0 +1,65 @@
+package kernel
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+type KernelVersionInfo struct {
+ kvi string
+ major int
+ minor int
+ build int
+}
+
+func (k *KernelVersionInfo) String() string {
+ return fmt.Sprintf("%d.%d %d (%s)", k.major, k.minor, k.build, k.kvi)
+}
+
+func GetKernelVersion() (*KernelVersionInfo, error) {
+
+ var (
+ h syscall.Handle
+ dwVersion uint32
+ err error
+ )
+
+ KVI := &KernelVersionInfo{"Unknown", 0, 0, 0}
+
+ if err = syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
+ syscall.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`),
+ 0,
+ syscall.KEY_READ,
+ &h); err != nil {
+ return KVI, err
+ }
+ defer syscall.RegCloseKey(h)
+
+ var buf [1 << 10]uint16
+ var typ uint32
+ n := uint32(len(buf) * 2) // api expects array of bytes, not uint16
+
+ if err = syscall.RegQueryValueEx(h,
+ syscall.StringToUTF16Ptr("BuildLabEx"),
+ nil,
+ &typ,
+ (*byte)(unsafe.Pointer(&buf[0])),
+ &n); err != nil {
+ return KVI, err
+ }
+
+ KVI.kvi = syscall.UTF16ToString(buf[:])
+
+ // Important - docker.exe MUST be manifested for this API to return
+ // the correct information.
+ if dwVersion, err = syscall.GetVersion(); err != nil {
+ return KVI, err
+ }
+
+ KVI.major = int(dwVersion & 0xFF)
+ KVI.minor = int((dwVersion & 0XFF00) >> 8)
+ KVI.build = int((dwVersion & 0xFFFF0000) >> 16)
+
+ return KVI, nil
+}
diff --git a/pkg/parsers/operatingsystem/operatingsystem.go b/pkg/parsers/operatingsystem/operatingsystem_linux.go
similarity index 100%
rename from pkg/parsers/operatingsystem/operatingsystem.go
rename to pkg/parsers/operatingsystem/operatingsystem_linux.go
diff --git a/pkg/parsers/operatingsystem/operatingsystem_windows.go b/pkg/parsers/operatingsystem/operatingsystem_windows.go
new file mode 100644
index 000000000..c843c6f84
--- /dev/null
+++ b/pkg/parsers/operatingsystem/operatingsystem_windows.go
@@ -0,0 +1,47 @@
+package operatingsystem
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+// See https://code.google.com/p/go/source/browse/src/pkg/mime/type_windows.go?r=d14520ac25bf6940785aabb71f5be453a286f58c
+// for a similar sample
+
+func GetOperatingSystem() (string, error) {
+
+ var h syscall.Handle
+
+ // Default return value
+ ret := "Unknown Operating System"
+
+ if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
+ syscall.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`),
+ 0,
+ syscall.KEY_READ,
+ &h); err != nil {
+ return ret, err
+ }
+ defer syscall.RegCloseKey(h)
+
+ var buf [1 << 10]uint16
+ var typ uint32
+ n := uint32(len(buf) * 2) // api expects array of bytes, not uint16
+
+ if err := syscall.RegQueryValueEx(h,
+ syscall.StringToUTF16Ptr("ProductName"),
+ nil,
+ &typ,
+ (*byte)(unsafe.Pointer(&buf[0])),
+ &n); err != nil {
+ return ret, err
+ }
+ ret = syscall.UTF16ToString(buf[:])
+
+ return ret, nil
+}
+
+// No-op on Windows
+func IsContainerized() (bool, error) {
+ return false, nil
+}
diff --git a/pkg/parsers/parsers.go b/pkg/parsers/parsers.go
index 59e294dc2..32d87734f 100644
--- a/pkg/parsers/parsers.go
+++ b/pkg/parsers/parsers.go
@@ -2,6 +2,7 @@ package parsers
import (
"fmt"
+ "runtime"
"strconv"
"strings"
)
@@ -10,7 +11,12 @@ import (
func ParseHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) {
addr = strings.TrimSpace(addr)
if addr == "" {
- addr = fmt.Sprintf("unix://%s", defaultUnixAddr)
+ if runtime.GOOS != "windows" {
+ addr = fmt.Sprintf("unix://%s", defaultUnixAddr)
+ } else {
+ // Note - defaultTCPAddr already includes tcp:// prefix
+ addr = fmt.Sprintf("%s", defaultTCPAddr)
+ }
}
addrParts := strings.Split(addr, "://")
if len(addrParts) == 1 {
@@ -135,3 +141,17 @@ func ParsePortRange(ports string) (uint64, uint64, error) {
}
return start, end, nil
}
+
+func ParseLink(val string) (string, string, error) {
+ if val == "" {
+ return "", "", fmt.Errorf("empty string specified for links")
+ }
+ arr := strings.Split(val, ":")
+ if len(arr) > 2 {
+ return "", "", fmt.Errorf("bad format for links: %s", val)
+ }
+ if len(arr) == 1 {
+ return val, val, nil
+ }
+ return arr[0], arr[1], nil
+}
diff --git a/pkg/parsers/parsers_test.go b/pkg/parsers/parsers_test.go
index bc9a1e943..89f4ae023 100644
--- a/pkg/parsers/parsers_test.go
+++ b/pkg/parsers/parsers_test.go
@@ -123,3 +123,35 @@ func TestParsePortRangeIncorrectStartRange(t *testing.T) {
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
}
}
+
+func TestParseLink(t *testing.T) {
+ name, alias, err := ParseLink("name:alias")
+ if err != nil {
+ t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
+ }
+ if name != "name" {
+ t.Fatalf("Link name should have been name, got %s instead", name)
+ }
+ if alias != "alias" {
+ t.Fatalf("Link alias should have been alias, got %s instead", alias)
+ }
+ // short format definition
+ name, alias, err = ParseLink("name")
+ if err != nil {
+ t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
+ }
+ if name != "name" {
+ t.Fatalf("Link name should have been name, got %s instead", name)
+ }
+ if alias != "name" {
+ t.Fatalf("Link alias should have been name, got %s instead", alias)
+ }
+ // empty string link definition is not allowed
+ if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
+ t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
+ }
+ // more than two colons are not allowed
+ if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
+ t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
+ }
+}
diff --git a/pkg/pidfile/pidfile.go b/pkg/pidfile/pidfile.go
new file mode 100644
index 000000000..3e5707365
--- /dev/null
+++ b/pkg/pidfile/pidfile.go
@@ -0,0 +1,42 @@
+package pidfile
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+)
+
+type PidFile struct {
+ path string
+}
+
+func checkPidFileAlreadyExists(path string) error {
+ if pidString, err := ioutil.ReadFile(path); err == nil {
+ if pid, err := strconv.Atoi(string(pidString)); err == nil {
+ if _, err := os.Stat(filepath.Join("/proc", string(pid))); err == nil {
+ return fmt.Errorf("pid file found, ensure docker is not running or delete %s", path)
+ }
+ }
+ }
+ return nil
+}
+
+func New(path string) (*PidFile, error) {
+ if err := checkPidFileAlreadyExists(path); err != nil {
+ return nil, err
+ }
+ if err := ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil {
+ return nil, err
+ }
+
+ return &PidFile{path: path}, nil
+}
+
+func (file PidFile) Remove() error {
+ if err := os.Remove(file.path); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/pkg/pidfile/pidfile_test.go b/pkg/pidfile/pidfile_test.go
new file mode 100644
index 000000000..6ed9cfc38
--- /dev/null
+++ b/pkg/pidfile/pidfile_test.go
@@ -0,0 +1,32 @@
+package pidfile
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestNewAndRemove(t *testing.T) {
+ dir, err := ioutil.TempDir(os.TempDir(), "test-pidfile")
+ if err != nil {
+ t.Fatal("Could not create test directory")
+ }
+
+ file, err := New(filepath.Join(dir, "testfile"))
+ if err != nil {
+ t.Fatal("Could not create test file", err)
+ }
+
+ if err := file.Remove(); err != nil {
+ t.Fatal("Could not delete created test file")
+ }
+}
+
+func TestRemoveInvalidPath(t *testing.T) {
+ file := PidFile{path: filepath.Join("foo", "bar")}
+
+ if err := file.Remove(); err == nil {
+ t.Fatal("Non-existing file doesn't give an error on delete")
+ }
+}
diff --git a/pkg/plugins/client.go b/pkg/plugins/client.go
new file mode 100644
index 000000000..d531fa46f
--- /dev/null
+++ b/pkg/plugins/client.go
@@ -0,0 +1,112 @@
+package plugins
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/Sirupsen/logrus"
+)
+
+const (
+ versionMimetype = "application/vnd.docker.plugins.v1+json"
+ defaultTimeOut = 30
+)
+
+func NewClient(addr string) *Client {
+ tr := &http.Transport{}
+ protoAndAddr := strings.Split(addr, "://")
+ configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
+ return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}
+}
+
+type Client struct {
+ http *http.Client
+ addr string
+}
+
+func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
+ return c.callWithRetry(serviceMethod, args, ret, true)
+}
+
+func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret interface{}, retry bool) error {
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(args); err != nil {
+ return err
+ }
+
+ req, err := http.NewRequest("POST", "/"+serviceMethod, &buf)
+ if err != nil {
+ return err
+ }
+ req.Header.Add("Accept", versionMimetype)
+ req.URL.Scheme = "http"
+ req.URL.Host = c.addr
+
+ var retries int
+ start := time.Now()
+
+ for {
+ resp, err := c.http.Do(req)
+ if err != nil {
+ if !retry {
+ return err
+ }
+
+ timeOff := backoff(retries)
+ if abort(start, timeOff) {
+ return err
+ }
+ retries++
+ logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
+ time.Sleep(timeOff)
+ continue
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ remoteErr, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil
+ }
+ return fmt.Errorf("Plugin Error: %s", remoteErr)
+ }
+
+ return json.NewDecoder(resp.Body).Decode(&ret)
+ }
+}
+
+func backoff(retries int) time.Duration {
+ b, max := 1, defaultTimeOut
+ for b < max && retries > 0 {
+ b *= 2
+ retries--
+ }
+ if b > max {
+ b = max
+ }
+ return time.Duration(b) * time.Second
+}
+
+func abort(start time.Time, timeOff time.Duration) bool {
+ return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second
+}
+
+func configureTCPTransport(tr *http.Transport, proto, addr string) {
+ // Why 32? See https://github.com/docker/docker/pull/8035.
+ timeout := 32 * time.Second
+ if proto == "unix" {
+ // No need for compression in local communications.
+ tr.DisableCompression = true
+ tr.Dial = func(_, _ string) (net.Conn, error) {
+ return net.DialTimeout(proto, addr, timeout)
+ }
+ } else {
+ tr.Proxy = http.ProxyFromEnvironment
+ tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
+ }
+}
diff --git a/pkg/plugins/client_test.go b/pkg/plugins/client_test.go
new file mode 100644
index 000000000..0f7cd34df
--- /dev/null
+++ b/pkg/plugins/client_test.go
@@ -0,0 +1,105 @@
+package plugins
+
+import (
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+ "time"
+)
+
+var (
+ mux *http.ServeMux
+ server *httptest.Server
+)
+
+func setupRemotePluginServer() string {
+ mux = http.NewServeMux()
+ server = httptest.NewServer(mux)
+ return server.URL
+}
+
+func teardownRemotePluginServer() {
+ if server != nil {
+ server.Close()
+ }
+}
+
+func TestFailedConnection(t *testing.T) {
+ c := NewClient("tcp://127.0.0.1:1")
+ err := c.callWithRetry("Service.Method", nil, nil, false)
+ if err == nil {
+ t.Fatal("Unexpected successful connection")
+ }
+}
+
+func TestEchoInputOutput(t *testing.T) {
+ addr := setupRemotePluginServer()
+ defer teardownRemotePluginServer()
+
+ m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}}
+
+ mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ t.Fatalf("Expected POST, got %s\n", r.Method)
+ }
+
+ header := w.Header()
+ header.Set("Content-Type", versionMimetype)
+
+ io.Copy(w, r.Body)
+ })
+
+ c := NewClient(addr)
+ var output Manifest
+ err := c.Call("Test.Echo", m, &output)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(output, m) {
+ t.Fatalf("Expected %v, was %v\n", m, output)
+ }
+}
+
+func TestBackoff(t *testing.T) {
+ cases := []struct {
+ retries int
+ expTimeOff time.Duration
+ }{
+ {0, time.Duration(1)},
+ {1, time.Duration(2)},
+ {2, time.Duration(4)},
+ {4, time.Duration(16)},
+ {6, time.Duration(30)},
+ {10, time.Duration(30)},
+ }
+
+ for _, c := range cases {
+ s := c.expTimeOff * time.Second
+ if d := backoff(c.retries); d != s {
+ t.Fatalf("Retry %v, expected %v, was %v\n", c.retries, s, d)
+ }
+ }
+}
+
+func TestAbortRetry(t *testing.T) {
+ cases := []struct {
+ timeOff time.Duration
+ expAbort bool
+ }{
+ {time.Duration(1), false},
+ {time.Duration(2), false},
+ {time.Duration(10), false},
+ {time.Duration(30), true},
+ {time.Duration(40), true},
+ }
+
+ for _, c := range cases {
+ s := c.timeOff * time.Second
+ if a := abort(time.Now(), s); a != c.expAbort {
+ t.Fatalf("Duration %v, expected %v, was %v\n", c.timeOff, s, a)
+ }
+ }
+}
diff --git a/pkg/plugins/discovery.go b/pkg/plugins/discovery.go
new file mode 100644
index 000000000..3a42ba6d1
--- /dev/null
+++ b/pkg/plugins/discovery.go
@@ -0,0 +1,78 @@
+package plugins
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+const defaultLocalRegistry = "/usr/share/docker/plugins"
+
+var (
+ ErrNotFound = errors.New("Plugin not found")
+)
+
+type Registry interface {
+ Plugins() ([]*Plugin, error)
+ Plugin(name string) (*Plugin, error)
+}
+
+type LocalRegistry struct {
+ path string
+}
+
+func newLocalRegistry(path string) *LocalRegistry {
+ if len(path) == 0 {
+ path = defaultLocalRegistry
+ }
+
+ return &LocalRegistry{path}
+}
+
+func (l *LocalRegistry) Plugin(name string) (*Plugin, error) {
+ filepath := filepath.Join(l.path, name)
+ specpath := filepath + ".spec"
+ if fi, err := os.Stat(specpath); err == nil {
+ return readPluginInfo(specpath, fi)
+ }
+ socketpath := filepath + ".sock"
+ if fi, err := os.Stat(socketpath); err == nil {
+ return readPluginInfo(socketpath, fi)
+ }
+ return nil, ErrNotFound
+}
+
+func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
+ name := strings.Split(fi.Name(), ".")[0]
+
+ if fi.Mode()&os.ModeSocket != 0 {
+ return &Plugin{
+ Name: name,
+ Addr: "unix://" + path,
+ }, nil
+ }
+
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ addr := strings.TrimSpace(string(content))
+
+ u, err := url.Parse(addr)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(u.Scheme) == 0 {
+ return nil, fmt.Errorf("Unknown protocol")
+ }
+
+ return &Plugin{
+ Name: name,
+ Addr: addr,
+ }, nil
+}
diff --git a/pkg/plugins/discovery_test.go b/pkg/plugins/discovery_test.go
new file mode 100644
index 000000000..b6e66e289
--- /dev/null
+++ b/pkg/plugins/discovery_test.go
@@ -0,0 +1,108 @@
+package plugins
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+func TestUnknownLocalPath(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ l := newLocalRegistry(filepath.Join(tmpdir, "unknown"))
+ _, err = l.Plugin("foo")
+ if err == nil || err != ErrNotFound {
+ t.Fatalf("Expected error for unknown directory")
+ }
+}
+
+func TestLocalSocket(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ l, err := net.Listen("unix", filepath.Join(tmpdir, "echo.sock"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer l.Close()
+
+ r := newLocalRegistry(tmpdir)
+ p, err := r.Plugin("echo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ pp, err := r.Plugin("echo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(p, pp) {
+ t.Fatalf("Expected %v, was %v\n", p, pp)
+ }
+
+ if p.Name != "echo" {
+ t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
+ }
+
+ addr := fmt.Sprintf("unix://%s/echo.sock", tmpdir)
+ if p.Addr != addr {
+ t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr)
+ }
+}
+
+func TestFileSpecPlugin(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ path string
+ name string
+ addr string
+ fail bool
+ }{
+ {filepath.Join(tmpdir, "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false},
+ {filepath.Join(tmpdir, "foo.spec"), "foo", "tcp://localhost:8080", false},
+ {filepath.Join(tmpdir, "bar.spec"), "bar", "localhost:8080", true}, // unknown transport
+ }
+
+ for _, c := range cases {
+ if err = os.MkdirAll(path.Dir(c.path), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err = ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ r := newLocalRegistry(tmpdir)
+ p, err := r.Plugin(c.name)
+ if c.fail && err == nil {
+ continue
+ }
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if p.Name != c.name {
+ t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
+ }
+
+ if p.Addr != c.addr {
+ t.Fatalf("Expected plugin addr `%s`, got %s\n", c.addr, p.Addr)
+ }
+ os.Remove(c.path)
+ }
+}
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
new file mode 100644
index 000000000..47519486b
--- /dev/null
+++ b/pkg/plugins/plugins.go
@@ -0,0 +1,100 @@
+package plugins
+
+import (
+ "errors"
+ "sync"
+
+ "github.com/Sirupsen/logrus"
+)
+
+var (
+ ErrNotImplements = errors.New("Plugin does not implement the requested driver")
+)
+
+type plugins struct {
+ sync.Mutex
+ plugins map[string]*Plugin
+}
+
+var (
+ storage = plugins{plugins: make(map[string]*Plugin)}
+ extpointHandlers = make(map[string]func(string, *Client))
+)
+
+type Manifest struct {
+ Implements []string
+}
+
+type Plugin struct {
+ Name string
+ Addr string
+ Client *Client
+ Manifest *Manifest
+}
+
+func (p *Plugin) activate() error {
+ m := new(Manifest)
+ p.Client = NewClient(p.Addr)
+ err := p.Client.Call("Plugin.Activate", nil, m)
+ if err != nil {
+ return err
+ }
+
+ logrus.Debugf("%s's manifest: %v", p.Name, m)
+ p.Manifest = m
+ for _, iface := range m.Implements {
+ handler, handled := extpointHandlers[iface]
+ if !handled {
+ continue
+ }
+ handler(p.Name, p.Client)
+ }
+ return nil
+}
+
+func load(name string) (*Plugin, error) {
+ registry := newLocalRegistry("")
+ pl, err := registry.Plugin(name)
+ if err != nil {
+ return nil, err
+ }
+ if err := pl.activate(); err != nil {
+ return nil, err
+ }
+ return pl, nil
+}
+
+func get(name string) (*Plugin, error) {
+ storage.Lock()
+ defer storage.Unlock()
+ pl, ok := storage.plugins[name]
+ if ok {
+ return pl, nil
+ }
+ pl, err := load(name)
+ if err != nil {
+ return nil, err
+ }
+
+ logrus.Debugf("Plugin: %v", pl)
+ storage.plugins[name] = pl
+ return pl, nil
+}
+
+func Get(name, imp string) (*Plugin, error) {
+ pl, err := get(name)
+ if err != nil {
+ return nil, err
+ }
+ for _, driver := range pl.Manifest.Implements {
+ logrus.Debugf("%s implements: %s", name, driver)
+ if driver == imp {
+ return pl, nil
+ }
+ }
+ return nil, ErrNotImplements
+}
+
+func Handle(iface string, fn func(string, *Client)) {
+ extpointHandlers[iface] = fn
+}
diff --git a/pkg/pools/pools.go b/pkg/pools/pools.go
index 5338a0cfb..f366fa67a 100644
--- a/pkg/pools/pools.go
+++ b/pkg/pools/pools.go
@@ -1,5 +1,3 @@
-// +build go1.3
-
// Package pools provides a collection of pools which provide various
// data types with buffers. These can be used to lower the number of
// memory allocations and reuse buffers.
diff --git a/pkg/pools/pools_nopool.go b/pkg/pools/pools_nopool.go
deleted file mode 100644
index 48903c239..000000000
--- a/pkg/pools/pools_nopool.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// +build !go1.3
-
-package pools
-
-import (
- "bufio"
- "io"
-
- "github.com/docker/docker/pkg/ioutils"
-)
-
-var (
- BufioReader32KPool *BufioReaderPool
- BufioWriter32KPool *BufioWriterPool
-)
-
-const buffer32K = 32 * 1024
-
-type BufioReaderPool struct {
- size int
-}
-
-func init() {
- BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K)
- BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K)
-}
-
-func newBufioReaderPoolWithSize(size int) *BufioReaderPool {
- return &BufioReaderPool{size: size}
-}
-
-func (bufPool *BufioReaderPool) Get(r io.Reader) *bufio.Reader {
- return bufio.NewReaderSize(r, bufPool.size)
-}
-
-func (bufPool *BufioReaderPool) Put(b *bufio.Reader) {
- b.Reset(nil)
-}
-
-func (bufPool *BufioReaderPool) NewReadCloserWrapper(buf *bufio.Reader, r io.Reader) io.ReadCloser {
- return ioutils.NewReadCloserWrapper(r, func() error {
- if readCloser, ok := r.(io.ReadCloser); ok {
- return readCloser.Close()
- }
- return nil
- })
-}
-
-type BufioWriterPool struct {
- size int
-}
-
-func newBufioWriterPoolWithSize(size int) *BufioWriterPool {
- return &BufioWriterPool{size: size}
-}
-
-func (bufPool *BufioWriterPool) Get(w io.Writer) *bufio.Writer {
- return bufio.NewWriterSize(w, bufPool.size)
-}
-
-func (bufPool *BufioWriterPool) Put(b *bufio.Writer) {
- b.Reset(nil)
-}
-
-func (bufPool *BufioWriterPool) NewWriteCloserWrapper(buf *bufio.Writer, w io.Writer) io.WriteCloser {
- return ioutils.NewWriteCloserWrapper(w, func() error {
- buf.Flush()
- if writeCloser, ok := w.(io.WriteCloser); ok {
- return writeCloser.Close()
- }
- return nil
- })
-}
diff --git a/pkg/progressreader/progressreader.go b/pkg/progressreader/progressreader.go
index f6ad06a3b..652831bff 100644
--- a/pkg/progressreader/progressreader.go
+++ b/pkg/progressreader/progressreader.go
@@ -2,36 +2,16 @@ package progressreader
import (
"io"
+
+ "github.com/docker/docker/pkg/jsonmessage"
+ "github.com/docker/docker/pkg/streamformatter"
)
-type StreamFormatter interface {
- FormatProg(string, string, interface{}) []byte
- FormatStatus(string, string, ...interface{}) []byte
- FormatError(error) []byte
-}
-
-type PR_JSONProgress interface {
- GetCurrent() int
- GetTotal() int
-}
-
-type JSONProg struct {
- Current int
- Total int
-}
-
-func (j *JSONProg) GetCurrent() int {
- return j.Current
-}
-func (j *JSONProg) GetTotal() int {
- return j.Total
-}
-
// Reader with progress bar
type Config struct {
In io.ReadCloser // Stream to read from
Out io.Writer // Where to send progress bar to
- Formatter StreamFormatter
+ Formatter *streamformatter.StreamFormatter
Size int
Current int
LastUpdate int
@@ -54,7 +34,7 @@ func (config *Config) Read(p []byte) (n int, err error) {
}
}
if config.Current-config.LastUpdate > updateEvery || err != nil {
- config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size}))
+ config.Out.Write(config.Formatter.FormatProgress(config.ID, config.Action, &jsonmessage.JSONProgress{Current: config.Current, Total: config.Size}))
config.LastUpdate = config.Current
}
// Send newline when complete
@@ -65,6 +45,6 @@ func (config *Config) Read(p []byte) (n int, err error) {
}
func (config *Config) Close() error {
config.Current = config.Size
- config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size}))
+ config.Out.Write(config.Formatter.FormatProgress(config.ID, config.Action, &jsonmessage.JSONProgress{Current: config.Current, Total: config.Size}))
return config.In.Close()
}
diff --git a/pkg/proxy/tcp_proxy.go b/pkg/proxy/tcp_proxy.go
index eacf1427a..9942e6d90 100644
--- a/pkg/proxy/tcp_proxy.go
+++ b/pkg/proxy/tcp_proxy.go
@@ -5,7 +5,7 @@ import (
"net"
"syscall"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
type TCPProxy struct {
@@ -31,7 +31,7 @@ func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
if err != nil {
- log.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err)
+ logrus.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err)
client.Close()
return
}
@@ -78,7 +78,7 @@ func (proxy *TCPProxy) Run() {
for {
client, err := proxy.listener.Accept()
if err != nil {
- log.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
+ logrus.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
return
}
go proxy.clientLoop(client.(*net.TCPConn), quit)
diff --git a/pkg/proxy/udp_proxy.go b/pkg/proxy/udp_proxy.go
index a3fcf116e..2a073dfe8 100644
--- a/pkg/proxy/udp_proxy.go
+++ b/pkg/proxy/udp_proxy.go
@@ -8,7 +8,7 @@ import (
"syscall"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
const (
@@ -105,7 +105,7 @@ func (proxy *UDPProxy) Run() {
// ECONNREFUSED like Read do (see comment in
// UDPProxy.replyLoop)
if !isClosedError(err) {
- log.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
+ logrus.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
}
break
}
@@ -116,7 +116,7 @@ func (proxy *UDPProxy) Run() {
if !hit {
proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
if err != nil {
- log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
+ logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
proxy.connTrackLock.Unlock()
continue
}
@@ -127,7 +127,7 @@ func (proxy *UDPProxy) Run() {
for i := 0; i != read; {
written, err := proxyConn.Write(readBuf[i:read])
if err != nil {
- log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
+ logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
break
}
i += written
diff --git a/pkg/random/random.go b/pkg/random/random.go
new file mode 100644
index 000000000..05b7f7fb3
--- /dev/null
+++ b/pkg/random/random.go
@@ -0,0 +1,34 @@
+package random
+
+import (
+ "math/rand"
+ "sync"
+ "time"
+)
+
+// copypaste from standard math/rand
+type lockedSource struct {
+ lk sync.Mutex
+ src rand.Source
+}
+
+func (r *lockedSource) Int63() (n int64) {
+ r.lk.Lock()
+ n = r.src.Int63()
+ r.lk.Unlock()
+ return
+}
+
+func (r *lockedSource) Seed(seed int64) {
+ r.lk.Lock()
+ r.src.Seed(seed)
+ r.lk.Unlock()
+}
+
+// NewSource returns math/rand.Source safe for concurrent use and initialized
+// with current unix-nano timestamp
+func NewSource() rand.Source {
+ return &lockedSource{
+ src: rand.NewSource(time.Now().UnixNano()),
+ }
+}
diff --git a/pkg/random/random_test.go b/pkg/random/random_test.go
new file mode 100644
index 000000000..cf405f78c
--- /dev/null
+++ b/pkg/random/random_test.go
@@ -0,0 +1,22 @@
+package random
+
+import (
+ "math/rand"
+ "sync"
+ "testing"
+)
+
+// for go test -v -race
+func TestConcurrency(t *testing.T) {
+ rnd := rand.New(NewSource())
+ var wg sync.WaitGroup
+
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ rnd.Int63()
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+}
diff --git a/pkg/reexec/command_unsupported.go b/pkg/reexec/command_unsupported.go
index a579318e8..4adcd8f13 100644
--- a/pkg/reexec/command_unsupported.go
+++ b/pkg/reexec/command_unsupported.go
@@ -1,4 +1,4 @@
-// +build !linux
+// +build !linux,!windows
package reexec
diff --git a/pkg/reexec/command_windows.go b/pkg/reexec/command_windows.go
new file mode 100644
index 000000000..124d42fc6
--- /dev/null
+++ b/pkg/reexec/command_windows.go
@@ -0,0 +1,14 @@
+// +build windows
+
+package reexec
+
+import (
+ "os/exec"
+)
+
+func Command(args ...string) *exec.Cmd {
+ return &exec.Cmd{
+ Path: Self(),
+ Args: args,
+ }
+}
diff --git a/pkg/signal/README.md b/pkg/signal/README.md
new file mode 100644
index 000000000..2b237a594
--- /dev/null
+++ b/pkg/signal/README.md
@@ -0,0 +1 @@
+This package provides helper functions for dealing with signals across various operating systems
\ No newline at end of file
diff --git a/pkg/signal/trap.go b/pkg/signal/trap.go
index 78a709b30..3772db5e1 100644
--- a/pkg/signal/trap.go
+++ b/pkg/signal/trap.go
@@ -3,10 +3,11 @@ package signal
import (
"os"
gosignal "os/signal"
+ "runtime"
"sync/atomic"
"syscall"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
// Trap sets up a simplified signal "trap", appropriate for common
@@ -14,41 +15,50 @@ import (
// (and the Docker engine in particular).
//
// * If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated.
-// * If SIGINT or SIGTERM are repeated 3 times before cleanup is complete, then cleanup is
-// skipped and the process terminated directly.
-// * If "DEBUG" is set in the environment, SIGQUIT causes an exit without cleanup.
+// * If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is
+// skipped and the process is terminated immediately (allows force quit of stuck daemon)
+// * A SIGQUIT always causes an exit without cleanup, with a goroutine dump preceding exit.
//
func Trap(cleanup func()) {
c := make(chan os.Signal, 1)
- signals := []os.Signal{os.Interrupt, syscall.SIGTERM}
- if os.Getenv("DEBUG") == "" {
- signals = append(signals, syscall.SIGQUIT)
- }
+ // we will handle INT, TERM, QUIT here
+ signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}
gosignal.Notify(c, signals...)
go func() {
interruptCount := uint32(0)
for sig := range c {
go func(sig os.Signal) {
- log.Infof("Received signal '%v', starting shutdown of docker...", sig)
+ logrus.Infof("Processing signal '%v'", sig)
switch sig {
case os.Interrupt, syscall.SIGTERM:
- // If the user really wants to interrupt, let him do so.
if atomic.LoadUint32(&interruptCount) < 3 {
// Initiate the cleanup only once
if atomic.AddUint32(&interruptCount, 1) == 1 {
- // Call cleanup handler
+ // Call the provided cleanup handler
cleanup()
os.Exit(0)
} else {
return
}
} else {
- log.Infof("Force shutdown of docker, interrupting cleanup")
+ // 3 SIGTERM/INT signals received; force exit without cleanup
+ logrus.Infof("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
}
case syscall.SIGQUIT:
+ DumpStacks()
+ logrus.Infof("Forcing docker daemon shutdown without cleanup on SIGQUIT")
}
+ //for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal #
os.Exit(128 + int(sig.(syscall.Signal)))
}(sig)
}
}()
}
+
+func DumpStacks() {
+ buf := make([]byte, 16384)
+ buf = buf[:runtime.Stack(buf, true)]
+ // Note that if the daemon is started with a less-verbose log-level than "info" (the default), the goroutine
+ // traces won't show up in the log.
+ logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
+}
diff --git a/pkg/sockets/README.md b/pkg/sockets/README.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/pkg/sockets/tcp_socket.go b/pkg/sockets/tcp_socket.go
new file mode 100644
index 000000000..ac9edaebd
--- /dev/null
+++ b/pkg/sockets/tcp_socket.go
@@ -0,0 +1,69 @@
+package sockets
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+
+ "github.com/docker/docker/pkg/listenbuffer"
+)
+
+type TlsConfig struct {
+ CA string
+ Certificate string
+ Key string
+ Verify bool
+}
+
+func NewTlsConfig(tlsCert, tlsKey, tlsCA string, verify bool) *TlsConfig {
+ return &TlsConfig{
+ Verify: verify,
+ Certificate: tlsCert,
+ Key: tlsKey,
+ CA: tlsCA,
+ }
+}
+
+func NewTcpSocket(addr string, config *TlsConfig, activate <-chan struct{}) (net.Listener, error) {
+ l, err := listenbuffer.NewListenBuffer("tcp", addr, activate)
+ if err != nil {
+ return nil, err
+ }
+ if config != nil {
+ if l, err = setupTls(l, config); err != nil {
+ return nil, err
+ }
+ }
+ return l, nil
+}
+
+func setupTls(l net.Listener, config *TlsConfig) (net.Listener, error) {
+ tlsCert, err := tls.LoadX509KeyPair(config.Certificate, config.Key)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", config.Certificate, config.Key, err)
+ }
+ return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %q. Make sure the key is encrypted.",
+ config.Certificate, config.Key, err)
+ }
+ tlsConfig := &tls.Config{
+ NextProtos: []string{"http/1.1"},
+ Certificates: []tls.Certificate{tlsCert},
+ // Avoid fallback on insecure SSL protocols
+ MinVersion: tls.VersionTLS10,
+ }
+ if config.CA != "" {
+ certPool := x509.NewCertPool()
+ file, err := ioutil.ReadFile(config.CA)
+ if err != nil {
+ return nil, fmt.Errorf("Could not read CA certificate: %v", err)
+ }
+ certPool.AppendCertsFromPEM(file)
+ tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
+ tlsConfig.ClientCAs = certPool
+ }
+ return tls.NewListener(l, tlsConfig), nil
+}
diff --git a/pkg/sockets/unix_socket.go b/pkg/sockets/unix_socket.go
new file mode 100644
index 000000000..0536382c8
--- /dev/null
+++ b/pkg/sockets/unix_socket.go
@@ -0,0 +1,80 @@
+// +build linux
+
+package sockets
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "strconv"
+ "syscall"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/pkg/listenbuffer"
+ "github.com/docker/libcontainer/user"
+)
+
+func NewUnixSocket(path, group string, activate <-chan struct{}) (net.Listener, error) {
+ if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+ mask := syscall.Umask(0777)
+ defer syscall.Umask(mask)
+ l, err := listenbuffer.NewListenBuffer("unix", path, activate)
+ if err != nil {
+ return nil, err
+ }
+ if err := setSocketGroup(path, group); err != nil {
+ l.Close()
+ return nil, err
+ }
+ if err := os.Chmod(path, 0660); err != nil {
+ l.Close()
+ return nil, err
+ }
+ return l, nil
+}
+
+func setSocketGroup(path, group string) error {
+ if group == "" {
+ return nil
+ }
+ if err := changeGroup(path, group); err != nil {
+ if group != "docker" {
+ return err
+ }
+ logrus.Debugf("Warning: could not change group %s to docker: %v", path, err)
+ }
+ return nil
+}
+
+func changeGroup(path string, nameOrGid string) error {
+ gid, err := lookupGidByName(nameOrGid)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("%s group found. gid: %d", nameOrGid, gid)
+ return os.Chown(path, 0, gid)
+}
+
+func lookupGidByName(nameOrGid string) (int, error) {
+ groupFile, err := user.GetGroupPath()
+ if err != nil {
+ return -1, err
+ }
+ groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool {
+ return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid
+ })
+ if err != nil {
+ return -1, err
+ }
+ if groups != nil && len(groups) > 0 {
+ return groups[0].Gid, nil
+ }
+ gid, err := strconv.Atoi(nameOrGid)
+ if err == nil {
+ logrus.Warnf("Could not find GID %d", gid)
+ return gid, nil
+ }
+ return -1, fmt.Errorf("Group %s not found", nameOrGid)
+}
diff --git a/pkg/stdcopy/stdcopy.go b/pkg/stdcopy/stdcopy.go
index a61779ce5..dbb74e5a2 100644
--- a/pkg/stdcopy/stdcopy.go
+++ b/pkg/stdcopy/stdcopy.go
@@ -5,7 +5,7 @@ import (
"errors"
"io"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
const (
@@ -52,12 +52,8 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) {
// and written to the underlying `w` stream.
// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
// `t` indicates the id of the stream to encapsulate.
-// It can be utils.Stdin, utils.Stdout, utils.Stderr.
+// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr.
func NewStdWriter(w io.Writer, t StdType) *StdWriter {
- if len(t) != StdWriterPrefixLen {
- return nil
- }
-
return &StdWriter{
Writer: w,
prefix: t,
@@ -95,13 +91,13 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
nr += nr2
if er == io.EOF {
if nr < StdWriterPrefixLen {
- log.Debugf("Corrupted prefix: %v", buf[:nr])
+ logrus.Debugf("Corrupted prefix: %v", buf[:nr])
return written, nil
}
break
}
if er != nil {
- log.Debugf("Error reading header: %s", er)
+ logrus.Debugf("Error reading header: %s", er)
return 0, er
}
}
@@ -117,18 +113,18 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
// Write on stderr
out = dsterr
default:
- log.Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex])
+ logrus.Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex])
return 0, ErrInvalidStdHeader
}
// Retrieve the size of the frame
frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4]))
- log.Debugf("framesize: %d", frameSize)
+ logrus.Debugf("framesize: %d", frameSize)
// Check if the buffer is big enough to read the frame.
// Extend it if necessary.
if frameSize+StdWriterPrefixLen > bufLen {
- log.Debugf("Extending buffer cap by %d (was %d)", frameSize+StdWriterPrefixLen-bufLen+1, len(buf))
+ logrus.Debugf("Extending buffer cap by %d (was %d)", frameSize+StdWriterPrefixLen-bufLen+1, len(buf))
buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...)
bufLen = len(buf)
}
@@ -140,13 +136,13 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
nr += nr2
if er == io.EOF {
if nr < frameSize+StdWriterPrefixLen {
- log.Debugf("Corrupted frame: %v", buf[StdWriterPrefixLen:nr])
+ logrus.Debugf("Corrupted frame: %v", buf[StdWriterPrefixLen:nr])
return written, nil
}
break
}
if er != nil {
- log.Debugf("Error reading frame: %s", er)
+ logrus.Debugf("Error reading frame: %s", er)
return 0, er
}
}
@@ -154,12 +150,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
// Write the retrieved frame (without header)
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen])
if ew != nil {
- log.Debugf("Error writing frame: %s", ew)
+ logrus.Debugf("Error writing frame: %s", ew)
return 0, ew
}
// If the frame has not been fully written: error
if nw != frameSize {
- log.Debugf("Error Short Write: (%d on %d)", nw, frameSize)
+ logrus.Debugf("Error Short Write: (%d on %d)", nw, frameSize)
return 0, io.ErrShortWrite
}
written += int64(nw)
diff --git a/pkg/stdcopy/stdcopy_test.go b/pkg/stdcopy/stdcopy_test.go
index 14e6ed311..a9fd73a49 100644
--- a/pkg/stdcopy/stdcopy_test.go
+++ b/pkg/stdcopy/stdcopy_test.go
@@ -3,9 +3,74 @@ package stdcopy
import (
"bytes"
"io/ioutil"
+ "strings"
"testing"
)
+func TestNewStdWriter(t *testing.T) {
+ writer := NewStdWriter(ioutil.Discard, Stdout)
+ if writer == nil {
+ t.Fatalf("NewStdWriter with an invalid StdType should not return nil.")
+ }
+}
+
+func TestWriteWithUnitializedStdWriter(t *testing.T) {
+ writer := StdWriter{
+ Writer: nil,
+ prefix: Stdout,
+ sizeBuf: make([]byte, 4),
+ }
+ n, err := writer.Write([]byte("Something here"))
+ if n != 0 || err == nil {
+ t.Fatalf("Should fail when given an uncomplete or uninitialized StdWriter")
+ }
+}
+
+func TestWriteWithNilBytes(t *testing.T) {
+ writer := NewStdWriter(ioutil.Discard, Stdout)
+ n, err := writer.Write(nil)
+ if err != nil {
+ t.Fatalf("Shouldn't have fail when given no data")
+ }
+ if n > 0 {
+ t.Fatalf("Write should have written 0 byte, but has written %d", n)
+ }
+}
+
+func TestWrite(t *testing.T) {
+ writer := NewStdWriter(ioutil.Discard, Stdout)
+ data := []byte("Test StdWrite.Write")
+ n, err := writer.Write(data)
+ if err != nil {
+ t.Fatalf("Error while writing with StdWrite")
+ }
+ if n != len(data) {
+ t.Fatalf("Write should have writen %d byte but wrote %d.", len(data), n)
+ }
+}
+
+func TestStdCopyWithInvalidInputHeader(t *testing.T) {
+ dstOut := NewStdWriter(ioutil.Discard, Stdout)
+ dstErr := NewStdWriter(ioutil.Discard, Stderr)
+ src := strings.NewReader("Invalid input")
+ _, err := StdCopy(dstOut, dstErr, src)
+ if err == nil {
+ t.Fatal("StdCopy with invalid input header should fail.")
+ }
+}
+
+func TestStdCopyWithCorruptedPrefix(t *testing.T) {
+ data := []byte{0x01, 0x02, 0x03}
+ src := bytes.NewReader(data)
+ written, err := StdCopy(nil, nil, src)
+ if err != nil {
+ t.Fatalf("StdCopy should not return an error with corrupted prefix.")
+ }
+ if written != 0 {
+ t.Fatalf("StdCopy should have written 0, but has written %d", written)
+ }
+}
+
func BenchmarkWrite(b *testing.B) {
w := NewStdWriter(ioutil.Discard, Stdout)
data := []byte("Test line for testing stdwriter performance\n")
diff --git a/utils/streamformatter.go b/pkg/streamformatter/streamformatter.go
similarity index 67%
rename from utils/streamformatter.go
rename to pkg/streamformatter/streamformatter.go
index e5b15f983..792ce00f9 100644
--- a/utils/streamformatter.go
+++ b/pkg/streamformatter/streamformatter.go
@@ -1,18 +1,25 @@
-package utils
+package streamformatter
import (
"encoding/json"
"fmt"
- "github.com/docker/docker/pkg/progressreader"
"io"
+
+ "github.com/docker/docker/pkg/jsonmessage"
)
type StreamFormatter struct {
json bool
}
-func NewStreamFormatter(json bool) *StreamFormatter {
- return &StreamFormatter{json}
+// NewStreamFormatter returns a simple StreamFormatter
+func NewStreamFormatter() *StreamFormatter {
+ return &StreamFormatter{}
+}
+
+// NewJSONStreamFormatter returns a StreamFormatter configured to stream json
+func NewJSONStreamFormatter() *StreamFormatter {
+ return &StreamFormatter{true}
}
const streamNewline = "\r\n"
@@ -21,7 +28,7 @@ var streamNewlineBytes = []byte(streamNewline)
func (sf *StreamFormatter) FormatStream(str string) []byte {
if sf.json {
- b, err := json.Marshal(&JSONMessage{Stream: str})
+ b, err := json.Marshal(&jsonmessage.JSONMessage{Stream: str})
if err != nil {
return sf.FormatError(err)
}
@@ -33,7 +40,7 @@ func (sf *StreamFormatter) FormatStream(str string) []byte {
func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []byte {
str := fmt.Sprintf(format, a...)
if sf.json {
- b, err := json.Marshal(&JSONMessage{ID: id, Status: str})
+ b, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Status: str})
if err != nil {
return sf.FormatError(err)
}
@@ -44,33 +51,24 @@ func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []b
func (sf *StreamFormatter) FormatError(err error) []byte {
if sf.json {
- jsonError, ok := err.(*JSONError)
+ jsonError, ok := err.(*jsonmessage.JSONError)
if !ok {
- jsonError = &JSONError{Message: err.Error()}
+ jsonError = &jsonmessage.JSONError{Message: err.Error()}
}
- if b, err := json.Marshal(&JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
+ if b, err := json.Marshal(&jsonmessage.JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
return append(b, streamNewlineBytes...)
}
return []byte("{\"error\":\"format error\"}" + streamNewline)
}
return []byte("Error: " + err.Error() + streamNewline)
}
-func (sf *StreamFormatter) FormatProg(id, action string, p interface{}) []byte {
- switch progress := p.(type) {
- case *JSONProgress:
- return sf.FormatProgress(id, action, progress)
- case progressreader.PR_JSONProgress:
- return sf.FormatProgress(id, action, &JSONProgress{Current: progress.GetCurrent(), Total: progress.GetTotal()})
- }
- return nil
-}
-func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgress) []byte {
+
+func (sf *StreamFormatter) FormatProgress(id, action string, progress *jsonmessage.JSONProgress) []byte {
if progress == nil {
- progress = &JSONProgress{}
+ progress = &jsonmessage.JSONProgress{}
}
if sf.json {
-
- b, err := json.Marshal(&JSONMessage{
+ b, err := json.Marshal(&jsonmessage.JSONMessage{
Status: action,
ProgressMessage: progress.String(),
Progress: progress,
@@ -88,10 +86,6 @@ func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgr
return []byte(action + " " + progress.String() + endl)
}
-func (sf *StreamFormatter) Json() bool {
- return sf.json
-}
-
type StdoutFormater struct {
io.Writer
*StreamFormatter
diff --git a/utils/streamformatter_test.go b/pkg/streamformatter/streamformatter_test.go
similarity index 55%
rename from utils/streamformatter_test.go
rename to pkg/streamformatter/streamformatter_test.go
index 20610f6c0..acf81bef6 100644
--- a/utils/streamformatter_test.go
+++ b/pkg/streamformatter/streamformatter_test.go
@@ -1,54 +1,80 @@
-package utils
+package streamformatter
import (
"encoding/json"
"errors"
"reflect"
"testing"
+
+ "github.com/docker/docker/pkg/jsonmessage"
)
func TestFormatStream(t *testing.T) {
- sf := NewStreamFormatter(true)
+ sf := NewStreamFormatter()
+ res := sf.FormatStream("stream")
+ if string(res) != "stream"+"\r" {
+ t.Fatalf("%q", res)
+ }
+}
+
+func TestFormatJSONStatus(t *testing.T) {
+ sf := NewStreamFormatter()
+ res := sf.FormatStatus("ID", "%s%d", "a", 1)
+ if string(res) != "a1\r\n" {
+ t.Fatalf("%q", res)
+ }
+}
+
+func TestFormatSimpleError(t *testing.T) {
+ sf := NewStreamFormatter()
+ res := sf.FormatError(errors.New("Error for formatter"))
+ if string(res) != "Error: Error for formatter\r\n" {
+ t.Fatalf("%q", res)
+ }
+}
+
+func TestJSONFormatStream(t *testing.T) {
+ sf := NewJSONStreamFormatter()
res := sf.FormatStream("stream")
if string(res) != `{"stream":"stream"}`+"\r\n" {
t.Fatalf("%q", res)
}
}
-func TestFormatStatus(t *testing.T) {
- sf := NewStreamFormatter(true)
+func TestJSONFormatStatus(t *testing.T) {
+ sf := NewJSONStreamFormatter()
res := sf.FormatStatus("ID", "%s%d", "a", 1)
if string(res) != `{"status":"a1","id":"ID"}`+"\r\n" {
t.Fatalf("%q", res)
}
}
-func TestFormatSimpleError(t *testing.T) {
- sf := NewStreamFormatter(true)
+func TestJSONFormatSimpleError(t *testing.T) {
+ sf := NewJSONStreamFormatter()
res := sf.FormatError(errors.New("Error for formatter"))
if string(res) != `{"errorDetail":{"message":"Error for formatter"},"error":"Error for formatter"}`+"\r\n" {
t.Fatalf("%q", res)
}
}
-func TestFormatJSONError(t *testing.T) {
- sf := NewStreamFormatter(true)
- err := &JSONError{Code: 50, Message: "Json error"}
+func TestJSONFormatJSONError(t *testing.T) {
+ sf := NewJSONStreamFormatter()
+ err := &jsonmessage.JSONError{Code: 50, Message: "Json error"}
res := sf.FormatError(err)
if string(res) != `{"errorDetail":{"code":50,"message":"Json error"},"error":"Json error"}`+"\r\n" {
t.Fatalf("%q", res)
}
}
-func TestFormatProgress(t *testing.T) {
- sf := NewStreamFormatter(true)
- progress := &JSONProgress{
+func TestJSONFormatProgress(t *testing.T) {
+ sf := NewJSONStreamFormatter()
+ progress := &jsonmessage.JSONProgress{
Current: 15,
Total: 30,
Start: 1,
}
res := sf.FormatProgress("id", "action", progress)
- msg := &JSONMessage{}
+ msg := &jsonmessage.JSONMessage{}
if err := json.Unmarshal(res, msg); err != nil {
t.Fatal(err)
}
diff --git a/pkg/stringid/README.md b/pkg/stringid/README.md
new file mode 100644
index 000000000..37a5098fd
--- /dev/null
+++ b/pkg/stringid/README.md
@@ -0,0 +1 @@
+This package provides helper functions for dealing with string identifiers
diff --git a/pkg/common/randomid.go b/pkg/stringid/stringid.go
similarity index 76%
rename from pkg/common/randomid.go
rename to pkg/stringid/stringid.go
index 5c6d5920e..6a683b686 100644
--- a/pkg/common/randomid.go
+++ b/pkg/stringid/stringid.go
@@ -1,22 +1,32 @@
-package common
+package stringid
import (
"crypto/rand"
"encoding/hex"
"io"
+ "regexp"
"strconv"
)
+const shortLen = 12
+
+var validShortID = regexp.MustCompile("^[a-z0-9]{12}$")
+
+// Determine if an arbitrary string *looks like* a short ID.
+func IsShortID(id string) bool {
+ return validShortID.MatchString(id)
+}
+
// TruncateID returns a shorthand version of a string identifier for convenience.
// A collision with other shorthands is very unlikely, but possible.
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length Id.
func TruncateID(id string) string {
- shortLen := 12
+ trimTo := shortLen
if len(id) < shortLen {
- shortLen = len(id)
+ trimTo = len(id)
}
- return id[:shortLen]
+ return id[:trimTo]
}
// GenerateRandomID returns an unique id
@@ -36,12 +46,3 @@ func GenerateRandomID() string {
return value
}
}
-
-func RandomString() string {
- id := make([]byte, 32)
-
- if _, err := io.ReadFull(rand.Reader, id); err != nil {
- panic(err) // This shouldn't happen
- }
- return hex.EncodeToString(id)
-}
diff --git a/pkg/common/randomid_test.go b/pkg/stringid/stringid_test.go
similarity index 61%
rename from pkg/common/randomid_test.go
rename to pkg/stringid/stringid_test.go
index 1dba41254..bcb136549 100644
--- a/pkg/common/randomid_test.go
+++ b/pkg/stringid/stringid_test.go
@@ -1,9 +1,18 @@
-package common
+package stringid
import (
+ "strings"
"testing"
)
+func TestGenerateRandomID(t *testing.T) {
+ id := GenerateRandomID()
+
+ if len(id) != 64 {
+ t.Fatalf("Id returned is incorrect: %s", id)
+ }
+}
+
func TestShortenId(t *testing.T) {
id := GenerateRandomID()
truncID := TruncateID(id)
@@ -28,32 +37,20 @@ func TestShortenIdInvalid(t *testing.T) {
}
}
-func TestGenerateRandomID(t *testing.T) {
- id := GenerateRandomID()
-
- if len(id) != 64 {
- t.Fatalf("Id returned is incorrect: %s", id)
+func TestIsShortIDNonHex(t *testing.T) {
+ id := "some non-hex value"
+ if IsShortID(id) {
+ t.Fatalf("%s is not a short ID", id)
}
}
-func TestRandomString(t *testing.T) {
- id := RandomString()
- if len(id) != 64 {
- t.Fatalf("Id returned is incorrect: %s", id)
+func TestIsShortIDNotCorrectSize(t *testing.T) {
+ id := strings.Repeat("a", shortLen+1)
+ if IsShortID(id) {
+ t.Fatalf("%s is not a short ID", id)
}
-}
-
-func TestRandomStringUniqueness(t *testing.T) {
- repeats := 25
- set := make(map[string]struct{}, repeats)
- for i := 0; i < repeats; i = i + 1 {
- id := RandomString()
- if len(id) != 64 {
- t.Fatalf("Id returned is incorrect: %s", id)
- }
- if _, ok := set[id]; ok {
- t.Fatalf("Random number is repeated")
- }
- set[id] = struct{}{}
+ id = strings.Repeat("a", shortLen-1)
+ if IsShortID(id) {
+ t.Fatalf("%s is not a short ID", id)
}
}
diff --git a/pkg/stringutils/README.md b/pkg/stringutils/README.md
new file mode 100644
index 000000000..b3e454573
--- /dev/null
+++ b/pkg/stringutils/README.md
@@ -0,0 +1 @@
+This package provides helper functions for dealing with strings
diff --git a/pkg/stringutils/stringutils.go b/pkg/stringutils/stringutils.go
new file mode 100644
index 000000000..aee2648b2
--- /dev/null
+++ b/pkg/stringutils/stringutils.go
@@ -0,0 +1,87 @@
+package stringutils
+
+import (
+ "bytes"
+ "math/rand"
+ "strings"
+
+ "github.com/docker/docker/pkg/random"
+)
+
+// Generate alpha only random stirng with length n
+func GenerateRandomAlphaOnlyString(n int) string {
+ // make a really long string
+ letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ b := make([]byte, n)
+ r := rand.New(random.NewSource())
+ for i := range b {
+ b[i] = letters[r.Intn(len(letters))]
+ }
+ return string(b)
+}
+
+// Generate Ascii random stirng with length n
+func GenerateRandomAsciiString(n int) string {
+ chars := "abcdefghijklmnopqrstuvwxyz" +
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+ "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` "
+ res := make([]byte, n)
+ for i := 0; i < n; i++ {
+ res[i] = chars[rand.Intn(len(chars))]
+ }
+ return string(res)
+}
+
+// Truncate a string to maxlen
+func Truncate(s string, maxlen int) string {
+ if len(s) <= maxlen {
+ return s
+ }
+ return s[:maxlen]
+}
+
+// Test wheather a string is contained in a slice of strings or not.
+// Comparison is case insensitive
+func InSlice(slice []string, s string) bool {
+ for _, ss := range slice {
+ if strings.ToLower(s) == strings.ToLower(ss) {
+ return true
+ }
+ }
+ return false
+}
+
+func quote(word string, buf *bytes.Buffer) {
+ // Bail out early for "simple" strings
+ if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") {
+ buf.WriteString(word)
+ return
+ }
+
+ buf.WriteString("'")
+
+ for i := 0; i < len(word); i++ {
+ b := word[i]
+ if b == '\'' {
+ // Replace literal ' with a close ', a \', and a open '
+ buf.WriteString("'\\''")
+ } else {
+ buf.WriteByte(b)
+ }
+ }
+
+ buf.WriteString("'")
+}
+
+// Take a list of strings and escape them so they will be handled right
+// when passed as arguments to an program via a shell
+func ShellQuoteArguments(args []string) string {
+ var buf bytes.Buffer
+ for i, arg := range args {
+ if i != 0 {
+ buf.WriteByte(' ')
+ }
+ quote(arg, &buf)
+ }
+ return buf.String()
+}
diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go
new file mode 100644
index 000000000..8dcb4696b
--- /dev/null
+++ b/pkg/stringutils/stringutils_test.go
@@ -0,0 +1,87 @@
+package stringutils
+
+import "testing"
+
+func testLengthHelper(generator func(int) string, t *testing.T) {
+ expectedLength := 20
+ s := generator(expectedLength)
+ if len(s) != expectedLength {
+ t.Fatalf("Length of %s was %d but expected length %d", s, len(s), expectedLength)
+ }
+}
+
+func testUniquenessHelper(generator func(int) string, t *testing.T) {
+ repeats := 25
+ set := make(map[string]struct{}, repeats)
+ for i := 0; i < repeats; i = i + 1 {
+ str := generator(64)
+ if len(str) != 64 {
+ t.Fatalf("Id returned is incorrect: %s", str)
+ }
+ if _, ok := set[str]; ok {
+ t.Fatalf("Random number is repeated")
+ }
+ set[str] = struct{}{}
+ }
+}
+
+func isASCII(s string) bool {
+ for _, c := range s {
+ if c > 127 {
+ return false
+ }
+ }
+ return true
+}
+
+func TestGenerateRandomAlphaOnlyStringLength(t *testing.T) {
+ testLengthHelper(GenerateRandomAlphaOnlyString, t)
+}
+
+func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) {
+ testUniquenessHelper(GenerateRandomAlphaOnlyString, t)
+}
+
+func TestGenerateRandomAsciiStringLength(t *testing.T) {
+ testLengthHelper(GenerateRandomAsciiString, t)
+}
+
+func TestGenerateRandomAsciiStringUniqueness(t *testing.T) {
+ testUniquenessHelper(GenerateRandomAsciiString, t)
+}
+
+func TestGenerateRandomAsciiStringIsAscii(t *testing.T) {
+ str := GenerateRandomAsciiString(64)
+ if !isASCII(str) {
+ t.Fatalf("%s contained non-ascii characters", str)
+ }
+}
+
+func TestTruncate(t *testing.T) {
+ str := "teststring"
+ newstr := Truncate(str, 4)
+ if newstr != "test" {
+ t.Fatalf("Expected test, got %s", newstr)
+ }
+ newstr = Truncate(str, 20)
+ if newstr != "teststring" {
+ t.Fatalf("Expected teststring, got %s", newstr)
+ }
+}
+
+func TestInSlice(t *testing.T) {
+ slice := []string{"test", "in", "slice"}
+
+ test := InSlice(slice, "test")
+ if !test {
+ t.Fatalf("Expected string test to be in slice")
+ }
+ test = InSlice(slice, "SLICE")
+ if !test {
+ t.Fatalf("Expected string SLICE to be in slice")
+ }
+ test = InSlice(slice, "notinslice")
+ if test {
+ t.Fatalf("Expected string notinslice not to be in slice")
+ }
+}
diff --git a/pkg/sysinfo/README.md b/pkg/sysinfo/README.md
new file mode 100644
index 000000000..c1530cef0
--- /dev/null
+++ b/pkg/sysinfo/README.md
@@ -0,0 +1 @@
+SysInfo stores information about which features a kernel supports.
diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go
index 1d540d2e7..5b7eca245 100644
--- a/pkg/sysinfo/sysinfo.go
+++ b/pkg/sysinfo/sysinfo.go
@@ -1,47 +1,13 @@
package sysinfo
-import (
- "io/ioutil"
- "os"
- "path"
-
- log "github.com/Sirupsen/logrus"
- "github.com/docker/libcontainer/cgroups"
-)
-
+// SysInfo stores information about which features a kernel supports.
+// TODO Windows: Factor out platform specific capabilities.
type SysInfo struct {
MemoryLimit bool
SwapLimit bool
+ CpuCfsPeriod bool
+ CpuCfsQuota bool
IPv4ForwardingDisabled bool
AppArmor bool
-}
-
-func New(quiet bool) *SysInfo {
- sysInfo := &SysInfo{}
- if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil {
- if !quiet {
- log.Warnf("%s", err)
- }
- } else {
- _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes"))
- _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes"))
- sysInfo.MemoryLimit = err1 == nil && err2 == nil
- if !sysInfo.MemoryLimit && !quiet {
- log.Warnf("Your kernel does not support cgroup memory limit.")
- }
-
- _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes"))
- sysInfo.SwapLimit = err == nil
- if !sysInfo.SwapLimit && !quiet {
- log.Warnf("Your kernel does not support cgroup swap limit.")
- }
- }
-
- // Check if AppArmor seems to be enabled on this system.
- if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) {
- sysInfo.AppArmor = false
- } else {
- sysInfo.AppArmor = true
- }
- return sysInfo
+ OomKillDisable bool
}
diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go
new file mode 100644
index 000000000..396ea3b27
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_linux.go
@@ -0,0 +1,79 @@
+package sysinfo
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+ "strconv"
+ "strings"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/libcontainer/cgroups"
+)
+
+// New returns a new SysInfo, using the filesystem to detect which features the kernel supports.
+func New(quiet bool) *SysInfo {
+ sysInfo := &SysInfo{}
+ if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil {
+ if !quiet {
+ logrus.Warnf("Your kernel does not support cgroup memory limit: %v", err)
+ }
+ } else {
+ // If memory cgroup is mounted, MemoryLimit is always enabled.
+ sysInfo.MemoryLimit = true
+
+ _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes"))
+ sysInfo.SwapLimit = err1 == nil
+ if !sysInfo.SwapLimit && !quiet {
+ logrus.Warn("Your kernel does not support swap memory limit.")
+ }
+
+ _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.oom_control"))
+ sysInfo.OomKillDisable = err == nil
+ if !sysInfo.OomKillDisable && !quiet {
+ logrus.Warnf("Your kernel does not support oom control.")
+ }
+ }
+
+ if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil {
+ if !quiet {
+ logrus.Warnf("%v", err)
+ }
+ } else {
+ _, err := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_period_us"))
+ sysInfo.CpuCfsPeriod = err == nil
+ if !sysInfo.CpuCfsPeriod && !quiet {
+ logrus.Warn("Your kernel does not support cgroup cfs period")
+ }
+ _, err = ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us"))
+ sysInfo.CpuCfsQuota = err == nil
+ if !sysInfo.CpuCfsQuota && !quiet {
+ logrus.Warn("Your kernel does not support cgroup cfs quotas")
+ }
+ }
+
+ // Checek if ipv4_forward is disabled.
+ if data, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward"); os.IsNotExist(err) {
+ sysInfo.IPv4ForwardingDisabled = true
+ } else {
+ if enabled, _ := strconv.Atoi(strings.TrimSpace(string(data))); enabled == 0 {
+ sysInfo.IPv4ForwardingDisabled = true
+ } else {
+ sysInfo.IPv4ForwardingDisabled = false
+ }
+ }
+
+ // Check if AppArmor is supported.
+ if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) {
+ sysInfo.AppArmor = false
+ } else {
+ sysInfo.AppArmor = true
+ }
+
+ // Check if Devices cgroup is mounted, it is hard requirement for container security.
+ if _, err := cgroups.FindCgroupMountpoint("devices"); err != nil {
+ logrus.Fatalf("Error mounting devices cgroup: %v", err)
+ }
+
+ return sysInfo
+}
diff --git a/pkg/sysinfo/sysinfo_windows.go b/pkg/sysinfo/sysinfo_windows.go
new file mode 100644
index 000000000..b4d31519f
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_windows.go
@@ -0,0 +1,7 @@
+package sysinfo
+
+// TODO Windows
+func New(quiet bool) *SysInfo {
+ sysInfo := &SysInfo{}
+ return sysInfo
+}
diff --git a/pkg/system/filesys.go b/pkg/system/filesys.go
new file mode 100644
index 000000000..e1f70e8da
--- /dev/null
+++ b/pkg/system/filesys.go
@@ -0,0 +1,11 @@
+// +build !windows
+
+package system
+
+import (
+ "os"
+)
+
+func MkdirAll(path string, perm os.FileMode) error {
+ return os.MkdirAll(path, perm)
+}
diff --git a/pkg/system/filesys_windows.go b/pkg/system/filesys_windows.go
new file mode 100644
index 000000000..90b500608
--- /dev/null
+++ b/pkg/system/filesys_windows.go
@@ -0,0 +1,64 @@
+// +build windows
+
+package system
+
+import (
+ "os"
+ "regexp"
+ "syscall"
+)
+
+// MkdirAll implementation that is volume path aware for Windows.
+func MkdirAll(path string, perm os.FileMode) error {
+ if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
+ return nil
+ }
+
+ // The rest of this method is copied from os.MkdirAll and should be kept
+ // as-is to ensure compatibility.
+
+ // Fast path: if we can tell whether path is a directory or file, stop with success or error.
+ dir, err := os.Stat(path)
+ if err == nil {
+ if dir.IsDir() {
+ return nil
+ }
+ return &os.PathError{
+ Op: "mkdir",
+ Path: path,
+ Err: syscall.ENOTDIR,
+ }
+ }
+
+ // Slow path: make sure parent exists and then call Mkdir for path.
+ i := len(path)
+ for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
+ i--
+ }
+
+ j := i
+ for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
+ j--
+ }
+
+ if j > 1 {
+ // Create parent
+ err = MkdirAll(path[0:j-1], perm)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Parent now exists; invoke Mkdir and use its result.
+ err = os.Mkdir(path, perm)
+ if err != nil {
+ // Handle arguments like "foo/." by
+ // double-checking that directory doesn't exist.
+ dir, err1 := os.Lstat(path)
+ if err1 == nil && dir.IsDir() {
+ return nil
+ }
+ return err
+ }
+ return nil
+}
diff --git a/pkg/system/lstat.go b/pkg/system/lstat.go
index 6c1ed2e38..d0e43b370 100644
--- a/pkg/system/lstat.go
+++ b/pkg/system/lstat.go
@@ -6,10 +6,13 @@ import (
"syscall"
)
+// Lstat takes a path to a file and returns
+// a system.Stat_t type pertaining to that file.
+//
+// Throws an error if the file does not exist
func Lstat(path string) (*Stat_t, error) {
s := &syscall.Stat_t{}
- err := syscall.Lstat(path, s)
- if err != nil {
+ if err := syscall.Lstat(path, s); err != nil {
return nil, err
}
return fromStatT(s)
diff --git a/pkg/system/lstat_test.go b/pkg/system/lstat_test.go
index 9bab4d7b0..6bac492eb 100644
--- a/pkg/system/lstat_test.go
+++ b/pkg/system/lstat_test.go
@@ -5,6 +5,7 @@ import (
"testing"
)
+// TestLstat tests Lstat for existing and non existing files
func TestLstat(t *testing.T) {
file, invalid, _, dir := prepareFiles(t)
defer os.RemoveAll(dir)
diff --git a/pkg/system/lstat_windows.go b/pkg/system/lstat_windows.go
index 801e756d8..eee1be26e 100644
--- a/pkg/system/lstat_windows.go
+++ b/pkg/system/lstat_windows.go
@@ -2,7 +2,28 @@
package system
+import (
+ "os"
+)
+
+// Some explanation for my own sanity, and hopefully maintainers in the
+// future.
+//
+// Lstat calls os.Lstat to get a fileinfo interface back.
+// This is then copied into our own locally defined structure.
+// Note the Linux version uses fromStatT to do the copy back,
+// but that not strictly necessary when already in an OS specific module.
+
func Lstat(path string) (*Stat_t, error) {
- // should not be called on cli code path
- return nil, ErrNotSupportedPlatform
+ fi, err := os.Lstat(path)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Stat_t{
+ name: fi.Name(),
+ size: fi.Size(),
+ mode: fi.Mode(),
+ modTime: fi.ModTime(),
+ isDir: fi.IsDir()}, nil
}
diff --git a/pkg/system/meminfo_linux.go b/pkg/system/meminfo_linux.go
index b7de3ff77..e2ca14009 100644
--- a/pkg/system/meminfo_linux.go
+++ b/pkg/system/meminfo_linux.go
@@ -15,8 +15,8 @@ var (
ErrMalformed = errors.New("malformed file")
)
-// Retrieve memory statistics of the host system and parse them into a MemInfo
-// type.
+// ReadMemInfo retrieves memory statistics of the host system and returns a
+// MemInfo type.
func ReadMemInfo() (*MemInfo, error) {
file, err := os.Open("/proc/meminfo")
if err != nil {
@@ -26,6 +26,10 @@ func ReadMemInfo() (*MemInfo, error) {
return parseMemInfo(file)
}
+// parseMemInfo parses the /proc/meminfo file into
+// a MemInfo object given a io.Reader to the file.
+//
+// Throws error if there are problems reading from the file
func parseMemInfo(reader io.Reader) (*MemInfo, error) {
meminfo := &MemInfo{}
scanner := bufio.NewScanner(reader)
diff --git a/pkg/system/meminfo_linux_test.go b/pkg/system/meminfo_linux_test.go
index 377405ea6..10ddf796c 100644
--- a/pkg/system/meminfo_linux_test.go
+++ b/pkg/system/meminfo_linux_test.go
@@ -7,6 +7,7 @@ import (
"github.com/docker/docker/pkg/units"
)
+// TestMemInfo tests parseMemInfo with a static meminfo string
func TestMemInfo(t *testing.T) {
const input = `
MemTotal: 1 kB
diff --git a/pkg/system/mknod.go b/pkg/system/mknod.go
index 06f9c6afb..26617eb08 100644
--- a/pkg/system/mknod.go
+++ b/pkg/system/mknod.go
@@ -6,6 +6,8 @@ import (
"syscall"
)
+// Mknod creates a filesystem node (file, device special file or named pipe) named path
+// with attributes specified by mode and dev
func Mknod(path string, mode uint32, dev int) error {
return syscall.Mknod(path, mode, dev)
}
diff --git a/pkg/system/mknod_windows.go b/pkg/system/mknod_windows.go
index b4020c11b..1811542ab 100644
--- a/pkg/system/mknod_windows.go
+++ b/pkg/system/mknod_windows.go
@@ -3,10 +3,9 @@
package system
func Mknod(path string, mode uint32, dev int) error {
- // should not be called on cli code path
return ErrNotSupportedPlatform
}
func Mkdev(major int64, minor int64) uint32 {
- panic("Mkdev not implemented on windows, should not be called on cli code")
+ panic("Mkdev not implemented on Windows.")
}
diff --git a/pkg/system/stat.go b/pkg/system/stat.go
index 186e85287..e2ecfe52f 100644
--- a/pkg/system/stat.go
+++ b/pkg/system/stat.go
@@ -1,9 +1,13 @@
+// +build !windows
+
package system
import (
"syscall"
)
+// Stat_t type contains status of a file. It contains metadata
+// like permission, owner, group, size, etc about a file
type Stat_t struct {
mode uint32
uid uint32
diff --git a/pkg/system/stat_linux.go b/pkg/system/stat_linux.go
index 072728d0a..3899b3e0e 100644
--- a/pkg/system/stat_linux.go
+++ b/pkg/system/stat_linux.go
@@ -4,6 +4,7 @@ import (
"syscall"
)
+// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*Stat_t, error) {
return &Stat_t{size: s.Size,
mode: s.Mode,
@@ -13,10 +14,13 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) {
mtim: s.Mtim}, nil
}
+// Stat takes a path to a file and returns
+// a system.Stat_t type pertaining to that file.
+//
+// Throws an error if the file does not exist
func Stat(path string) (*Stat_t, error) {
s := &syscall.Stat_t{}
- err := syscall.Stat(path, s)
- if err != nil {
+ if err := syscall.Stat(path, s); err != nil {
return nil, err
}
return fromStatT(s)
diff --git a/pkg/system/stat_test.go b/pkg/system/stat_test.go
index abcc8ea7a..453412920 100644
--- a/pkg/system/stat_test.go
+++ b/pkg/system/stat_test.go
@@ -6,6 +6,7 @@ import (
"testing"
)
+// TestFromStatT tests fromStatT for a tempfile
func TestFromStatT(t *testing.T) {
file, _, _, dir := prepareFiles(t)
defer os.RemoveAll(dir)
diff --git a/pkg/system/stat_unsupported.go b/pkg/system/stat_unsupported.go
index 66323eee2..7e0d0348f 100644
--- a/pkg/system/stat_unsupported.go
+++ b/pkg/system/stat_unsupported.go
@@ -6,6 +6,7 @@ import (
"syscall"
)
+// fromStatT creates a system.Stat_t type from a syscall.Stat_t type
func fromStatT(s *syscall.Stat_t) (*Stat_t, error) {
return &Stat_t{size: s.Size,
mode: uint32(s.Mode),
diff --git a/pkg/system/stat_windows.go b/pkg/system/stat_windows.go
index 42d29d6cc..b1fd39e83 100644
--- a/pkg/system/stat_windows.go
+++ b/pkg/system/stat_windows.go
@@ -3,15 +3,34 @@
package system
import (
- "errors"
- "syscall"
+ "os"
+ "time"
)
-func fromStatT(s *syscall.Win32FileAttributeData) (*Stat_t, error) {
- return nil, errors.New("fromStatT should not be called on windows path")
+type Stat_t struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+ isDir bool
}
-func Stat(path string) (*Stat_t, error) {
- // should not be called on cli code path
- return nil, ErrNotSupportedPlatform
+func (s Stat_t) Name() string {
+ return s.name
+}
+
+func (s Stat_t) Size() int64 {
+ return s.size
+}
+
+func (s Stat_t) Mode() os.FileMode {
+ return s.mode
+}
+
+func (s Stat_t) ModTime() time.Time {
+ return s.modTime
+}
+
+func (s Stat_t) IsDir() bool {
+ return s.isDir
}
diff --git a/pkg/system/utimes_test.go b/pkg/system/utimes_test.go
index 1dea47cc1..350cce1ea 100644
--- a/pkg/system/utimes_test.go
+++ b/pkg/system/utimes_test.go
@@ -8,6 +8,7 @@ import (
"testing"
)
+// prepareFiles creates files for testing in the temp directory
func prepareFiles(t *testing.T) (string, string, string, string) {
dir, err := ioutil.TempDir("", "docker-system-test")
if err != nil {
diff --git a/pkg/tarsum/tarsum.go b/pkg/tarsum/tarsum.go
index 88fcbe4a9..a778bb0b9 100644
--- a/pkg/tarsum/tarsum.go
+++ b/pkg/tarsum/tarsum.go
@@ -1,6 +1,7 @@
package tarsum
import (
+ "archive/tar"
"bytes"
"compress/gzip"
"crypto"
@@ -11,8 +12,6 @@ import (
"hash"
"io"
"strings"
-
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
const (
diff --git a/pkg/tarsum/tarsum_test.go b/pkg/tarsum/tarsum_test.go
index 26f12cc84..968d7c7cf 100644
--- a/pkg/tarsum/tarsum_test.go
+++ b/pkg/tarsum/tarsum_test.go
@@ -1,6 +1,7 @@
package tarsum
import (
+ "archive/tar"
"bytes"
"compress/gzip"
"crypto/md5"
@@ -14,8 +15,6 @@ import (
"io/ioutil"
"os"
"testing"
-
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
type testLayer struct {
diff --git a/pkg/tarsum/versioning.go b/pkg/tarsum/versioning.go
index 0ceb5298a..3cdc6ddaa 100644
--- a/pkg/tarsum/versioning.go
+++ b/pkg/tarsum/versioning.go
@@ -1,12 +1,11 @@
package tarsum
import (
+ "archive/tar"
"errors"
"sort"
"strconv"
"strings"
-
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
// versioning of the TarSum algorithm
diff --git a/pkg/term/tc_linux_cgo.go b/pkg/term/tc_linux_cgo.go
index ae9516c99..d47cf59b8 100644
--- a/pkg/term/tc_linux_cgo.go
+++ b/pkg/term/tc_linux_cgo.go
@@ -24,6 +24,7 @@ func MakeRaw(fd uintptr) (*State, error) {
newState := oldState.termios
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState)))
+ newState.Oflag = newState.Oflag | C.OPOST
if err := tcset(fd, &newState); err != 0 {
return nil, err
}
diff --git a/pkg/term/winconsole/console_windows_test.go b/pkg/term/winconsole/console_windows_test.go
index ee9d96834..edb5d6f66 100644
--- a/pkg/term/winconsole/console_windows_test.go
+++ b/pkg/term/winconsole/console_windows_test.go
@@ -18,7 +18,7 @@ func helpsTestParseInt16OrDefault(t *testing.T, expectedValue int16, shouldFail
t.Errorf(format, args)
}
if expectedValue != value {
- t.Errorf("The value returned does not macth expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value)
+ t.Errorf("The value returned does not match expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value)
t.Errorf(format, args)
}
}
diff --git a/pkg/term/winconsole/term_emulator_test.go b/pkg/term/winconsole/term_emulator_test.go
index 65de5a793..94104ff51 100644
--- a/pkg/term/winconsole/term_emulator_test.go
+++ b/pkg/term/winconsole/term_emulator_test.go
@@ -138,7 +138,7 @@ func TestAssertEqualBytesNegative(t *testing.T) {
AssertBytesEqual(t, []byte{1, 2, 3}, []byte{1, 1, 1}, "content mismatch")
}*/
-// Checks that the calls recieved
+// Checks that the calls received
func assertHandlerOutput(t *testing.T, mock *mockTerminal, plainText string, commands ...string) {
text := make([]byte, 0, 3*len(plainText))
cmdIndex := 0
diff --git a/pkg/testutils/README.md b/pkg/testutils/README.md
deleted file mode 100644
index a208a90e6..000000000
--- a/pkg/testutils/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-`testutils` is a collection of utility functions to facilitate the writing
-of tests. It is used in various places by the Docker test suite.
diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go
deleted file mode 100644
index 9c664ff25..000000000
--- a/pkg/testutils/utils.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package testutils
-
-import (
- "math/rand"
- "testing"
- "time"
-)
-
-const chars = "abcdefghijklmnopqrstuvwxyz" +
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
- "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` "
-
-// Timeout calls f and waits for 100ms for it to complete.
-// If it doesn't, it causes the tests to fail.
-// t must be a valid testing context.
-func Timeout(t *testing.T, f func()) {
- onTimeout := time.After(100 * time.Millisecond)
- onDone := make(chan bool)
- go func() {
- f()
- close(onDone)
- }()
- select {
- case <-onTimeout:
- t.Fatalf("timeout")
- case <-onDone:
- }
-}
-
-// RandomString returns random string of specified length
-func RandomString(length int) string {
- res := make([]byte, length)
- for i := 0; i < length; i++ {
- res[i] = chars[rand.Intn(len(chars))]
- }
- return string(res)
-}
diff --git a/pkg/timeoutconn/timeoutconn.go b/pkg/timeoutconn/timeoutconn.go
index 3a554559a..d9534b5da 100644
--- a/pkg/timeoutconn/timeoutconn.go
+++ b/pkg/timeoutconn/timeoutconn.go
@@ -17,8 +17,7 @@ type conn struct {
func (c *conn) Read(b []byte) (int, error) {
if c.timeout > 0 {
- err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout))
- if err != nil {
+ if err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout)); err != nil {
return 0, err
}
}
diff --git a/pkg/timeutils/utils.go b/pkg/timeutils/utils.go
new file mode 100644
index 000000000..6af16a1d7
--- /dev/null
+++ b/pkg/timeutils/utils.go
@@ -0,0 +1,29 @@
+package timeutils
+
+import (
+ "strconv"
+ "strings"
+ "time"
+)
+
+// GetTimestamp tries to parse given string as RFC3339 time
+// or Unix timestamp (with seconds precision), if successful
+//returns a Unix timestamp as string otherwise returns value back.
+func GetTimestamp(value string) string {
+ var format string
+ if strings.Contains(value, ".") {
+ format = time.RFC3339Nano
+ } else {
+ format = time.RFC3339
+ }
+
+ loc := time.FixedZone(time.Now().Zone())
+ if len(value) < len(format) {
+ format = format[:len(value)]
+ }
+ t, err := time.ParseInLocation(format, value, loc)
+ if err != nil {
+ return value
+ }
+ return strconv.FormatInt(t.Unix(), 10)
+}
diff --git a/pkg/timeutils/utils_test.go b/pkg/timeutils/utils_test.go
new file mode 100644
index 000000000..1d724fb2a
--- /dev/null
+++ b/pkg/timeutils/utils_test.go
@@ -0,0 +1,36 @@
+package timeutils
+
+import (
+ "testing"
+)
+
+func TestGetTimestamp(t *testing.T) {
+ cases := []struct{ in, expected string }{
+ {"0", "-62167305600"}, // 0 gets parsed year 0
+
+ // Partial RFC3339 strings get parsed with second precision
+ {"2006-01-02T15:04:05.999999999+07:00", "1136189045"},
+ {"2006-01-02T15:04:05.999999999Z", "1136214245"},
+ {"2006-01-02T15:04:05.999999999", "1136214245"},
+ {"2006-01-02T15:04:05", "1136214245"},
+ {"2006-01-02T15:04", "1136214240"},
+ {"2006-01-02T15", "1136214000"},
+ {"2006-01-02T", "1136160000"},
+ {"2006-01-02", "1136160000"},
+ {"2006", "1136073600"},
+ {"2015-05-13T20:39:09Z", "1431549549"},
+
+ // unix timestamps returned as is
+ {"1136073600", "1136073600"},
+
+ // String fallback
+ {"invalid", "invalid"},
+ }
+
+ for _, c := range cases {
+ o := GetTimestamp(c.in)
+ if o != c.expected {
+ t.Fatalf("wrong value for '%s'. expected:'%s' got:'%s'", c.in, c.expected, o)
+ }
+ }
+}
diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/LICENSE b/pkg/transport/LICENSE
similarity index 92%
rename from vendor/src/github.com/go-fsnotify/fsnotify/LICENSE
rename to pkg/transport/LICENSE
index f21e54080..d02f24fd5 100644
--- a/vendor/src/github.com/go-fsnotify/fsnotify/LICENSE
+++ b/pkg/transport/LICENSE
@@ -1,5 +1,4 @@
-Copyright (c) 2012 The Go Authors. All rights reserved.
-Copyright (c) 2012 fsnotify Authors. All rights reserved.
+Copyright (c) 2009 The oauth2 Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
diff --git a/pkg/transport/transport.go b/pkg/transport/transport.go
new file mode 100644
index 000000000..510d8b4bc
--- /dev/null
+++ b/pkg/transport/transport.go
@@ -0,0 +1,148 @@
+package transport
+
+import (
+ "io"
+ "net/http"
+ "sync"
+)
+
+type RequestModifier interface {
+ ModifyRequest(*http.Request) error
+}
+
+type headerModifier http.Header
+
+// NewHeaderRequestModifier returns a RequestModifier that merges the HTTP headers
+// passed as an argument, with the HTTP headers of a request.
+//
+// If the same key is present in both, the modifying header values for that key,
+// are appended to the values for that same key in the request header.
+func NewHeaderRequestModifier(header http.Header) RequestModifier {
+ return headerModifier(header)
+}
+
+func (h headerModifier) ModifyRequest(req *http.Request) error {
+ for k, s := range http.Header(h) {
+ req.Header[k] = append(req.Header[k], s...)
+ }
+
+ return nil
+}
+
+// NewTransport returns an http.RoundTripper that modifies requests according to
+// the RequestModifiers passed in the arguments, before sending the requests to
+// the base http.RoundTripper (which, if nil, defaults to http.DefaultTransport).
+func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper {
+ return &transport{
+ Modifiers: modifiers,
+ Base: base,
+ }
+}
+
+// transport is an http.RoundTripper that makes HTTP requests after
+// copying and modifying the request
+type transport struct {
+ Modifiers []RequestModifier
+ Base http.RoundTripper
+
+ mu sync.Mutex // guards modReq
+ modReq map[*http.Request]*http.Request // original -> modified
+}
+
+func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
+ req2 := CloneRequest(req)
+ for _, modifier := range t.Modifiers {
+ if err := modifier.ModifyRequest(req2); err != nil {
+ return nil, err
+ }
+ }
+
+ t.setModReq(req, req2)
+ res, err := t.base().RoundTrip(req2)
+ if err != nil {
+ t.setModReq(req, nil)
+ return nil, err
+ }
+ res.Body = &OnEOFReader{
+ Rc: res.Body,
+ Fn: func() { t.setModReq(req, nil) },
+ }
+ return res, nil
+}
+
+// CancelRequest cancels an in-flight request by closing its connection.
+func (t *transport) CancelRequest(req *http.Request) {
+ type canceler interface {
+ CancelRequest(*http.Request)
+ }
+ if cr, ok := t.base().(canceler); ok {
+ t.mu.Lock()
+ modReq := t.modReq[req]
+ delete(t.modReq, req)
+ t.mu.Unlock()
+ cr.CancelRequest(modReq)
+ }
+}
+
+func (t *transport) base() http.RoundTripper {
+ if t.Base != nil {
+ return t.Base
+ }
+ return http.DefaultTransport
+}
+
+func (t *transport) setModReq(orig, mod *http.Request) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ if t.modReq == nil {
+ t.modReq = make(map[*http.Request]*http.Request)
+ }
+ if mod == nil {
+ delete(t.modReq, orig)
+ } else {
+ t.modReq[orig] = mod
+ }
+}
+
+// CloneRequest returns a clone of the provided *http.Request.
+// The clone is a shallow copy of the struct and its Header map.
+func CloneRequest(r *http.Request) *http.Request {
+ // shallow copy of the struct
+ r2 := new(http.Request)
+ *r2 = *r
+ // deep copy of the Header
+ r2.Header = make(http.Header, len(r.Header))
+ for k, s := range r.Header {
+ r2.Header[k] = append([]string(nil), s...)
+ }
+
+ return r2
+}
+
+// OnEOFReader ensures a callback function is called
+// on Close() and when the underlying Reader returns an io.EOF error
+type OnEOFReader struct {
+ Rc io.ReadCloser
+ Fn func()
+}
+
+func (r *OnEOFReader) Read(p []byte) (n int, err error) {
+ n, err = r.Rc.Read(p)
+ if err == io.EOF {
+ r.runFunc()
+ }
+ return
+}
+
+func (r *OnEOFReader) Close() error {
+ err := r.Rc.Close()
+ r.runFunc()
+ return err
+}
+
+func (r *OnEOFReader) runFunc() {
+ if fn := r.Fn; fn != nil {
+ fn()
+ r.Fn = nil
+ }
+}
diff --git a/pkg/truncindex/truncindex.go b/pkg/truncindex/truncindex.go
index 73c7e24fb..9aae5c0d0 100644
--- a/pkg/truncindex/truncindex.go
+++ b/pkg/truncindex/truncindex.go
@@ -14,12 +14,6 @@ var (
ErrAmbiguousPrefix = errors.New("Multiple IDs found with provided prefix")
)
-func init() {
- // Change patricia max prefix per node length,
- // because our len(ID) always 64
- patricia.MaxPrefixPerNode = 64
-}
-
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
type TruncIndex struct {
@@ -31,8 +25,11 @@ type TruncIndex struct {
// NewTruncIndex creates a new TruncIndex and initializes with a list of IDs
func NewTruncIndex(ids []string) (idx *TruncIndex) {
idx = &TruncIndex{
- ids: make(map[string]struct{}),
- trie: patricia.NewTrie(),
+ ids: make(map[string]struct{}),
+
+ // Change patricia max prefix per node length,
+ // because our len(ID) always 64
+ trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)),
}
for _, id := range ids {
idx.addID(id)
diff --git a/pkg/truncindex/truncindex_test.go b/pkg/truncindex/truncindex_test.go
index 928653428..7253f6a4c 100644
--- a/pkg/truncindex/truncindex_test.go
+++ b/pkg/truncindex/truncindex_test.go
@@ -4,7 +4,7 @@ import (
"math/rand"
"testing"
- "github.com/docker/docker/pkg/common"
+ "github.com/docker/docker/pkg/stringid"
)
// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
@@ -60,7 +60,7 @@ func TestTruncIndex(t *testing.T) {
assertIndexGet(t, index, id[:1], "", true)
// An ambiguous id prefix should return an error
- if _, err := index.Get(id[:4]); err == nil || err == nil {
+ if _, err := index.Get(id[:4]); err == nil {
t.Fatal("An ambiguous id prefix should return an error")
}
@@ -111,7 +111,7 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin
func BenchmarkTruncIndexAdd100(b *testing.B) {
var testSet []string
for i := 0; i < 100; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -127,7 +127,7 @@ func BenchmarkTruncIndexAdd100(b *testing.B) {
func BenchmarkTruncIndexAdd250(b *testing.B) {
var testSet []string
for i := 0; i < 250; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -143,7 +143,7 @@ func BenchmarkTruncIndexAdd250(b *testing.B) {
func BenchmarkTruncIndexAdd500(b *testing.B) {
var testSet []string
for i := 0; i < 500; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -160,7 +160,7 @@ func BenchmarkTruncIndexGet100(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 100; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
index := NewTruncIndex([]string{})
for _, id := range testSet {
@@ -184,7 +184,7 @@ func BenchmarkTruncIndexGet250(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 250; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
index := NewTruncIndex([]string{})
for _, id := range testSet {
@@ -208,7 +208,7 @@ func BenchmarkTruncIndexGet500(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
index := NewTruncIndex([]string{})
for _, id := range testSet {
@@ -231,7 +231,7 @@ func BenchmarkTruncIndexGet500(b *testing.B) {
func BenchmarkTruncIndexDelete100(b *testing.B) {
var testSet []string
for i := 0; i < 100; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -254,7 +254,7 @@ func BenchmarkTruncIndexDelete100(b *testing.B) {
func BenchmarkTruncIndexDelete250(b *testing.B) {
var testSet []string
for i := 0; i < 250; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -277,7 +277,7 @@ func BenchmarkTruncIndexDelete250(b *testing.B) {
func BenchmarkTruncIndexDelete500(b *testing.B) {
var testSet []string
for i := 0; i < 500; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -300,7 +300,7 @@ func BenchmarkTruncIndexDelete500(b *testing.B) {
func BenchmarkTruncIndexNew100(b *testing.B) {
var testSet []string
for i := 0; i < 100; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -311,7 +311,7 @@ func BenchmarkTruncIndexNew100(b *testing.B) {
func BenchmarkTruncIndexNew250(b *testing.B) {
var testSet []string
for i := 0; i < 250; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -322,7 +322,7 @@ func BenchmarkTruncIndexNew250(b *testing.B) {
func BenchmarkTruncIndexNew500(b *testing.B) {
var testSet []string
for i := 0; i < 500; i++ {
- testSet = append(testSet, common.GenerateRandomID())
+ testSet = append(testSet, stringid.GenerateRandomID())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -334,7 +334,7 @@ func BenchmarkTruncIndexAddGet100(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
- id := common.GenerateRandomID()
+ id := stringid.GenerateRandomID()
testSet = append(testSet, id)
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
@@ -359,7 +359,7 @@ func BenchmarkTruncIndexAddGet250(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
- id := common.GenerateRandomID()
+ id := stringid.GenerateRandomID()
testSet = append(testSet, id)
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
@@ -384,7 +384,7 @@ func BenchmarkTruncIndexAddGet500(b *testing.B) {
var testSet []string
var testKeys []string
for i := 0; i < 500; i++ {
- id := common.GenerateRandomID()
+ id := stringid.GenerateRandomID()
testSet = append(testSet, id)
l := rand.Intn(12) + 12
testKeys = append(testKeys, id[:l])
diff --git a/pkg/ulimit/ulimit.go b/pkg/ulimit/ulimit.go
index 2375315e3..eb2ae4e8c 100644
--- a/pkg/ulimit/ulimit.go
+++ b/pkg/ulimit/ulimit.go
@@ -102,5 +102,5 @@ func (u *Ulimit) GetRlimit() (*Rlimit, error) {
}
func (u *Ulimit) String() string {
- return fmt.Sprintf("%s=%s:%s", u.Name, u.Soft, u.Hard)
+ return fmt.Sprintf("%s=%d:%d", u.Name, u.Soft, u.Hard)
}
diff --git a/pkg/ulimit/ulimit_test.go b/pkg/ulimit/ulimit_test.go
index 419b5e040..1e8c881f5 100644
--- a/pkg/ulimit/ulimit_test.go
+++ b/pkg/ulimit/ulimit_test.go
@@ -2,6 +2,13 @@ package ulimit
import "testing"
+func TestParseValid(t *testing.T) {
+ u1 := &Ulimit{"nofile", 1024, 512}
+ if u2, _ := Parse("nofile=512:1024"); *u1 != *u2 {
+ t.Fatalf("expected %q, but got %q", u1, u2)
+ }
+}
+
func TestParseInvalidLimitType(t *testing.T) {
if _, err := Parse("notarealtype=1024:1024"); err == nil {
t.Fatalf("expected error on invalid ulimit type")
@@ -39,3 +46,10 @@ func TestParseInvalidValueType(t *testing.T) {
t.Fatal("expected error on bad value type")
}
}
+
+func TestStringOutput(t *testing.T) {
+ u := &Ulimit{"nofile", 1024, 512}
+ if s := u.String(); s != "nofile=512:1024" {
+ t.Fatal("expected String to return nofile=512:1024, but got", s)
+ }
+}
diff --git a/pkg/units/duration.go b/pkg/units/duration.go
index cd3312149..44012aafb 100644
--- a/pkg/units/duration.go
+++ b/pkg/units/duration.go
@@ -27,5 +27,5 @@ func HumanDuration(d time.Duration) string {
} else if hours < 24*365*2 {
return fmt.Sprintf("%d months", hours/24/30)
}
- return fmt.Sprintf("%f years", d.Hours()/24/365)
+ return fmt.Sprintf("%d years", int(d.Hours())/24/365)
}
diff --git a/pkg/units/duration_test.go b/pkg/units/duration_test.go
index a22947402..fcfb6b7bb 100644
--- a/pkg/units/duration_test.go
+++ b/pkg/units/duration_test.go
@@ -41,6 +41,6 @@ func TestHumanDuration(t *testing.T) {
assertEquals(t, "13 months", HumanDuration(13*month))
assertEquals(t, "23 months", HumanDuration(23*month))
assertEquals(t, "24 months", HumanDuration(24*month))
- assertEquals(t, "2.010959 years", HumanDuration(24*month+2*week))
- assertEquals(t, "3.164384 years", HumanDuration(3*year+2*month))
+ assertEquals(t, "2 years", HumanDuration(24*month+2*week))
+ assertEquals(t, "3 years", HumanDuration(3*year+2*month))
}
diff --git a/pkg/units/size.go b/pkg/units/size.go
index 7cfb57ba5..d7850ad0b 100644
--- a/pkg/units/size.go
+++ b/pkg/units/size.go
@@ -37,23 +37,25 @@ var (
var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
+// CustomSize returns a human-readable approximation of a size
+// using custom format
+func CustomSize(format string, size float64, base float64, _map []string) string {
+ i := 0
+ for size >= base {
+ size = size / base
+ i++
+ }
+ return fmt.Sprintf(format, size, _map[i])
+}
+
// HumanSize returns a human-readable approximation of a size
// using SI standard (eg. "44kB", "17MB")
func HumanSize(size float64) string {
- return intToString(float64(size), 1000.0, decimapAbbrs)
+ return CustomSize("%.4g %s", float64(size), 1000.0, decimapAbbrs)
}
func BytesSize(size float64) string {
- return intToString(size, 1024.0, binaryAbbrs)
-}
-
-func intToString(size, unit float64, _map []string) string {
- i := 0
- for size >= unit {
- size = size / unit
- i++
- }
- return fmt.Sprintf("%.4g %s", size, _map[i])
+ return CustomSize("%.4g %s", size, 1024.0, binaryAbbrs)
}
// FromHumanSize returns an integer from a human-readable specification of a
diff --git a/pkg/urlutil/git.go b/pkg/urlutil/git.go
index ba88ddf6e..dc4d6662e 100644
--- a/pkg/urlutil/git.go
+++ b/pkg/urlutil/git.go
@@ -1,6 +1,9 @@
package urlutil
-import "strings"
+import (
+ "regexp"
+ "strings"
+)
var (
validPrefixes = []string{
@@ -8,11 +11,13 @@ var (
"github.com/",
"git@",
}
+
+ urlPathWithFragmentSuffix = regexp.MustCompile(".git(?:#.+)?$")
)
// IsGitURL returns true if the provided str is a git repository URL.
func IsGitURL(str string) bool {
- if IsURL(str) && strings.HasSuffix(str, ".git") {
+ if IsURL(str) && urlPathWithFragmentSuffix.MatchString(str) {
return true
}
for _, prefix := range validPrefixes {
diff --git a/pkg/urlutil/git_test.go b/pkg/urlutil/git_test.go
index 01dcea7da..bb89d8b5f 100644
--- a/pkg/urlutil/git_test.go
+++ b/pkg/urlutil/git_test.go
@@ -9,10 +9,15 @@ var (
"git@bitbucket.org:atlassianlabs/atlassian-docker.git",
"https://github.com/docker/docker.git",
"http://github.com/docker/docker.git",
+ "http://github.com/docker/docker.git#branch",
+ "http://github.com/docker/docker.git#:dir",
}
incompleteGitUrls = []string{
"github.com/docker/docker",
}
+ invalidGitUrls = []string{
+ "http://github.com/docker/docker.git:#branch",
+ }
)
func TestValidGitTransport(t *testing.T) {
@@ -35,9 +40,16 @@ func TestIsGIT(t *testing.T) {
t.Fatalf("%q should be detected as valid Git url", url)
}
}
+
for _, url := range incompleteGitUrls {
if IsGitURL(url) == false {
t.Fatalf("%q should be detected as valid Git url", url)
}
}
+
+ for _, url := range invalidGitUrls {
+ if IsGitURL(url) == true {
+ t.Fatalf("%q should not be detected as valid Git prefix", url)
+ }
+ }
}
diff --git a/pkg/useragent/README.md b/pkg/useragent/README.md
new file mode 100644
index 000000000..d9cb367d1
--- /dev/null
+++ b/pkg/useragent/README.md
@@ -0,0 +1 @@
+This package provides helper functions to pack version information into a single User-Agent header.
diff --git a/pkg/useragent/useragent.go b/pkg/useragent/useragent.go
new file mode 100644
index 000000000..9e35d1c70
--- /dev/null
+++ b/pkg/useragent/useragent.go
@@ -0,0 +1,60 @@
+// Package useragent provides helper functions to pack
+// version information into a single User-Agent header.
+package useragent
+
+import (
+ "errors"
+ "strings"
+)
+
+var (
+ ErrNilRequest = errors.New("request cannot be nil")
+)
+
+// VersionInfo is used to model UserAgent versions.
+type VersionInfo struct {
+ Name string
+ Version string
+}
+
+func (vi *VersionInfo) isValid() bool {
+ const stopChars = " \t\r\n/"
+ name := vi.Name
+ vers := vi.Version
+ if len(name) == 0 || strings.ContainsAny(name, stopChars) {
+ return false
+ }
+ if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
+ return false
+ }
+ return true
+}
+
+// Convert versions to a string and append the string to the string base.
+//
+// Each VersionInfo will be converted to a string in the format of
+// "product/version", where the "product" is get from the name field, while
+// version is get from the version field. Several pieces of verson information
+// will be concatinated and separated by space.
+//
+// Example:
+// AppendVersions("base", VersionInfo{"foo", "1.0"}, VersionInfo{"bar", "2.0"})
+// results in "base foo/1.0 bar/2.0".
+func AppendVersions(base string, versions ...VersionInfo) string {
+ if len(versions) == 0 {
+ return base
+ }
+
+ verstrs := make([]string, 0, 1+len(versions))
+ if len(base) > 0 {
+ verstrs = append(verstrs, base)
+ }
+
+ for _, v := range versions {
+ if !v.isValid() {
+ continue
+ }
+ verstrs = append(verstrs, v.Name+"/"+v.Version)
+ }
+ return strings.Join(verstrs, " ")
+}
diff --git a/pkg/useragent/useragent_test.go b/pkg/useragent/useragent_test.go
new file mode 100644
index 000000000..0ad7243a6
--- /dev/null
+++ b/pkg/useragent/useragent_test.go
@@ -0,0 +1,31 @@
+package useragent
+
+import "testing"
+
+func TestVersionInfo(t *testing.T) {
+ vi := VersionInfo{"foo", "bar"}
+ if !vi.isValid() {
+ t.Fatalf("VersionInfo should be valid")
+ }
+ vi = VersionInfo{"", "bar"}
+ if vi.isValid() {
+ t.Fatalf("Expected VersionInfo to be invalid")
+ }
+ vi = VersionInfo{"foo", ""}
+ if vi.isValid() {
+ t.Fatalf("Expected VersionInfo to be invalid")
+ }
+}
+
+func TestAppendVersions(t *testing.T) {
+ vis := []VersionInfo{
+ {"foo", "1.0"},
+ {"bar", "0.1"},
+ {"pi", "3.1.4"},
+ }
+ v := AppendVersions("base", vis...)
+ expect := "base foo/1.0 bar/0.1 pi/3.1.4"
+ if v != expect {
+ t.Fatalf("expected %q, got %q", expect, v)
+ }
+}
diff --git a/pkg/version/version.go b/pkg/version/version.go
index cc802a654..bd5ec7a83 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -37,7 +37,7 @@ func (v Version) compareTo(other Version) int {
return 0
}
-// LessThan checks if a version is less than another version
+// LessThan checks if a version is less than another
func (v Version) LessThan(other Version) bool {
return v.compareTo(other) == -1
}
@@ -47,12 +47,12 @@ func (v Version) LessThanOrEqualTo(other Version) bool {
return v.compareTo(other) <= 0
}
-// GreaterThan checks if a version is greater than another one
+// GreaterThan checks if a version is greater than another
func (v Version) GreaterThan(other Version) bool {
return v.compareTo(other) == 1
}
-// GreaterThanOrEqualTo checks ia version is greater than or equal to another
+// GreaterThanOrEqualTo checks if a version is greater than or equal to another
func (v Version) GreaterThanOrEqualTo(other Version) bool {
return v.compareTo(other) >= 0
}
diff --git a/project/GOVERNANCE.md b/project/GOVERNANCE.md
index 52a8bf05d..6ae7baf74 100644
--- a/project/GOVERNANCE.md
+++ b/project/GOVERNANCE.md
@@ -4,7 +4,7 @@ In the spirit of openness, Docker created a Governance Advisory Board, and commi
All output from the meetings should be considered proposals only, and are subject to the review and approval of the community and the project leadership.
The materials from the first Docker Governance Advisory Board meeting, held on October 28, 2014, are available at
-[Google Docs Folder](http://goo.gl/Alfj8r)
+[Google Docs Folder](https://goo.gl/Alfj8r)
These include:
diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md
index 701e552d5..fd2156c58 100644
--- a/project/PACKAGERS.md
+++ b/project/PACKAGERS.md
@@ -45,9 +45,9 @@ need to package Docker your way, without denaturing it in the process.
To build Docker, you will need the following:
* A recent version of Git and Mercurial
-* Go version 1.3 or later
+* Go version 1.4 or later
* A clean checkout of the source added to a valid [Go
- workspace](http://golang.org/doc/code.html#Workspaces) under the path
+ workspace](https://golang.org/doc/code.html#Workspaces) under the path
*src/github.com/docker/docker* (unless you plan to use `AUTO_GOPATH`,
explained in more detail below)
@@ -58,8 +58,7 @@ To build the Docker daemon, you will additionally need:
* libdevmapper version 1.02.68-cvs (2012-01-26) or later from lvm2 version
2.02.89 or later
* btrfs-progs version 3.16.1 or later (unless using an older version is
- absolutely necessary, in which case 3.8 is the minimum and the note below
- regarding `btrfs_noversion` applies)
+ absolutely necessary, in which case 3.8 is the minimum)
Be sure to also check out Docker's Dockerfile for the most up-to-date list of
these build-time dependencies.
@@ -163,12 +162,6 @@ SELinux, you will need to use the `selinux` build tag:
export DOCKER_BUILDTAGS='selinux'
```
-If your version of btrfs-progs (also called btrfs-tools) is < 3.16.1, then you
-will need the following tag to not check for btrfs version headers:
-```bash
-export DOCKER_BUILDTAGS='btrfs_noversion'
-```
-
There are build tags for disabling graphdrivers as well. By default, support
for all graphdrivers are built in.
@@ -244,9 +237,9 @@ are as follows (in order):
installed at "/usr/bin/docker", then "/usr/bin/dockerinit" will be the first
place this file is searched for)
* "/usr/libexec/docker/dockerinit" or "/usr/local/libexec/docker/dockerinit"
- ([FHS 3.0 Draft](http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec))
+ ([FHS 3.0 Draft](https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec))
* "/usr/lib/docker/dockerinit" or "/usr/local/lib/docker/dockerinit" ([FHS
- 2.3](http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA))
+ 2.3](https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA))
If (and please, only if) one of the paths above is insufficient due to distro
policy or similar issues, you may use the `DOCKER_INITPATH` environment variable
@@ -310,6 +303,7 @@ by having support for them in the kernel or userspace. A few examples include:
* AUFS graph driver (requires AUFS patches/support enabled in the kernel, and at
least the "auplink" utility from aufs-tools)
* BTRFS graph driver (requires BTRFS support enabled in the kernel)
+* ZFS graph driver (requires userspace zfs-utils and a corresponding kernel module)
## Daemon Init Script
diff --git a/project/RELEASE-CHECKLIST.md b/project/RELEASE-CHECKLIST.md
index d9382b901..d2b965080 100644
--- a/project/RELEASE-CHECKLIST.md
+++ b/project/RELEASE-CHECKLIST.md
@@ -49,7 +49,17 @@ git cherry-pick
...
```
-### 2. Update CHANGELOG.md
+### 2. Bump the API version on master
+
+We don't want to stop contributions to master just because we are releasing. At
+the same time, now that the release branch exists, we don't want API changes to
+go to the now frozen API version.
+
+Create a new entry in `docs/sources/reference/api/` by copying the latest and
+bumping the version number (in both the file's name and content), and submit
+this in a PR against master.
+
+### 3. Update CHANGELOG.md
You can run this command for reference with git 2.0:
@@ -124,7 +134,7 @@ git log --format='%aN <%aE>' v0.7.0...bump_v0.8.0 | sort -uf
Obviously, you'll need to adjust version numbers as necessary. If you just need
a count, add a simple `| wc -l`.
-### 3. Change the contents of the VERSION file
+### 4. Change the contents of the VERSION file
Before the big thing, you'll want to make successive release candidates and get
people to test. The release candidate number `N` should be part of the version:
@@ -134,7 +144,7 @@ export RC_VERSION=${VERSION}-rcN
echo ${RC_VERSION#v} > VERSION
```
-### 4. Test the docs
+### 5. Test the docs
Make sure that your tree includes documentation for any modified or
new features, syntax or semantic changes.
@@ -145,7 +155,7 @@ To test locally:
make docs
```
-To make a shared test at http://beta-docs.docker.io:
+To make a shared test at https://beta-docs.docker.io:
(You will need the `awsconfig` file added to the `docs/` dir)
@@ -153,7 +163,7 @@ To make a shared test at http://beta-docs.docker.io:
make AWS_S3_BUCKET=beta-docs.docker.io BUILD_ROOT=yes docs-release
```
-### 5. Commit and create a pull request to the "release" branch
+### 6. Commit and create a pull request to the "release" branch
```bash
git add VERSION CHANGELOG.md
@@ -166,7 +176,7 @@ That last command will give you the proper link to visit to ensure that you
open the PR against the "release" branch instead of accidentally against
"master" (like so many brave souls before you already have).
-### 6. Publish release candidate binaries
+### 7. Publish release candidate binaries
To run this you will need access to the release credentials. Get them from the
Core maintainers.
@@ -219,7 +229,7 @@ We recommend announcing the release candidate on:
- The [docker-maintainers](https://groups.google.com/a/dockerproject.org/forum/#!forum/maintainers) group
- Any social media that can bring some attention to the release candidate
-### 7. Iterate on successive release candidates
+### 8. Iterate on successive release candidates
Spend several days along with the community explicitly investing time and
resources to try and break Docker in every possible way, documenting any
@@ -269,7 +279,7 @@ git push -f $GITHUBUSER bump_$VERSION
Repeat step 6 to tag the code, publish new binaries, announce availability, and
get help testing.
-### 8. Finalize the bump branch
+### 9. Finalize the bump branch
When you're happy with the quality of a release candidate, you can move on and
create the real thing.
@@ -285,9 +295,9 @@ git commit --amend
You will then repeat step 6 to publish the binaries to test
-### 9. Get 2 other maintainers to validate the pull request
+### 10. Get 2 other maintainers to validate the pull request
-### 10. Publish final binaries
+### 11. Publish final binaries
Once they're tested and reasonably believed to be working, run against
get.docker.com:
@@ -303,7 +313,7 @@ docker run \
hack/release.sh
```
-### 9. Apply tag
+### 12. Apply tag
It's very important that we don't make the tag until after the official
release is uploaded to get.docker.com!
@@ -313,12 +323,12 @@ git tag -a $VERSION -m $VERSION bump_$VERSION
git push origin $VERSION
```
-### 10. Go to github to merge the `bump_$VERSION` branch into release
+### 13. Go to github to merge the `bump_$VERSION` branch into release
Don't forget to push that pretty blue button to delete the leftover
branch afterwards!
-### 11. Update the docs branch
+### 14. Update the docs branch
If this is a MAJOR.MINOR.0 release, you need to make an branch for the previous release's
documentation:
@@ -341,7 +351,7 @@ git push -f origin docs
make AWS_S3_BUCKET=docs.docker.com BUILD_ROOT=yes DISTRIBUTION_ID=C2K6......FL2F docs-release
```
-The docs will appear on http://docs.docker.com/ (though there may be cached
+The docs will appear on https://docs.docker.com/ (though there may be cached
versions, so its worth checking http://docs.docker.com.s3-website-us-east-1.amazonaws.com/).
For more information about documentation releases, see `docs/README.md`.
@@ -350,7 +360,7 @@ distributed CDN system) is flushed. The `make docs-release` command will do this
_if_ the `DISTRIBUTION_ID` is set correctly - this will take at least 15 minutes to run
and you can check its progress with the CDN Cloudfront Chrome addin.
-### 12. Create a new pull request to merge your bump commit back into master
+### 15. Create a new pull request to merge your bump commit back into master
```bash
git checkout master
@@ -364,7 +374,14 @@ echo "https://github.com/$GITHUBUSER/docker/compare/docker:master...$GITHUBUSER:
Again, get two maintainers to validate, then merge, then push that pretty
blue button to delete your branch.
-### 13. Rejoice and Evangelize!
+### 16. Update the VERSION files
+
+Now that version X.Y.Z is out, time to start working on the next! Update the
+content of the `VERSION` file to be the next minor (incrementing Y) and add the
+`-dev` suffix. For example, after 1.5.0 release, the `VERSION` file gets
+updated to `1.6.0-dev` (as in "1.6.0 in the making").
+
+### 17. Rejoice and Evangelize!
Congratulations! You're done.
diff --git a/project/TOOLS.md b/project/TOOLS.md
index f057ccd2b..79bd28374 100644
--- a/project/TOOLS.md
+++ b/project/TOOLS.md
@@ -14,11 +14,11 @@ we run Docker in Docker to test.
Leeroy is a Go application which integrates Jenkins with
GitHub pull requests. Leeroy uses
-[GitHub hooks](http://developer.github.com/v3/repos/hooks/)
+[GitHub hooks](https://developer.github.com/v3/repos/hooks/)
to listen for pull request notifications and starts jobs on your Jenkins
server. Using the Jenkins [notification plugin][jnp], Leeroy updates the
pull request using GitHub's
-[status API](http://developer.github.com/v3/repos/statuses/)
+[status API](https://developer.github.com/v3/repos/statuses/)
with pending, success, failure, or error statuses.
The leeroy repository is maintained at
diff --git a/registry/auth.go b/registry/auth.go
index 4baf114c6..33f8fa068 100644
--- a/registry/auth.go
+++ b/registry/auth.go
@@ -1,46 +1,20 @@
package registry
import (
- "encoding/base64"
"encoding/json"
- "errors"
"fmt"
"io/ioutil"
"net/http"
- "os"
- "path"
"strings"
"sync"
"time"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/utils"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/cliconfig"
)
-const (
- // Where we store the config file
- CONFIGFILE = ".dockercfg"
-)
-
-var (
- ErrConfigFileMissing = errors.New("The Auth config file is missing")
-)
-
-type AuthConfig struct {
- Username string `json:"username,omitempty"`
- Password string `json:"password,omitempty"`
- Auth string `json:"auth"`
- Email string `json:"email"`
- ServerAddress string `json:"serveraddress,omitempty"`
-}
-
-type ConfigFile struct {
- Configs map[string]AuthConfig `json:"configs,omitempty"`
- rootPath string
-}
-
type RequestAuthorization struct {
- authConfig *AuthConfig
+ authConfig *cliconfig.AuthConfig
registryEndpoint *Endpoint
resource string
scope string
@@ -51,7 +25,7 @@ type RequestAuthorization struct {
tokenExpiration time.Time
}
-func NewRequestAuthorization(authConfig *AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization {
+func NewRequestAuthorization(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization {
return &RequestAuthorization{
authConfig: authConfig,
registryEndpoint: registryEndpoint,
@@ -66,25 +40,22 @@ func (auth *RequestAuthorization) getToken() (string, error) {
defer auth.tokenLock.Unlock()
now := time.Now()
if now.Before(auth.tokenExpiration) {
- log.Debugf("Using cached token for %s", auth.authConfig.Username)
+ logrus.Debugf("Using cached token for %s", auth.authConfig.Username)
return auth.tokenCache, nil
}
- client := auth.registryEndpoint.HTTPClient()
- factory := HTTPRequestFactory(nil)
-
for _, challenge := range auth.registryEndpoint.AuthChallenges {
switch strings.ToLower(challenge.Scheme) {
case "basic":
// no token necessary
case "bearer":
- log.Debugf("Getting bearer token with %s for %s", challenge.Parameters, auth.authConfig.Username)
+ logrus.Debugf("Getting bearer token with %s for %s", challenge.Parameters, auth.authConfig.Username)
params := map[string]string{}
for k, v := range challenge.Parameters {
params[k] = v
}
params["scope"] = fmt.Sprintf("%s:%s:%s", auth.resource, auth.scope, strings.Join(auth.actions, ","))
- token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint, client, factory)
+ token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint)
if err != nil {
return "", err
}
@@ -93,7 +64,7 @@ func (auth *RequestAuthorization) getToken() (string, error) {
return token, nil
default:
- log.Infof("Unsupported auth scheme: %q", challenge.Scheme)
+ logrus.Infof("Unsupported auth scheme: %q", challenge.Scheme)
}
}
@@ -116,136 +87,26 @@ func (auth *RequestAuthorization) Authorize(req *http.Request) error {
return nil
}
-// create a base64 encoded auth string to store in config
-func encodeAuth(authConfig *AuthConfig) string {
- authStr := authConfig.Username + ":" + authConfig.Password
- msg := []byte(authStr)
- encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
- base64.StdEncoding.Encode(encoded, msg)
- return string(encoded)
-}
-
-// decode the auth string
-func decodeAuth(authStr string) (string, string, error) {
- decLen := base64.StdEncoding.DecodedLen(len(authStr))
- decoded := make([]byte, decLen)
- authByte := []byte(authStr)
- n, err := base64.StdEncoding.Decode(decoded, authByte)
- if err != nil {
- return "", "", err
- }
- if n > decLen {
- return "", "", fmt.Errorf("Something went wrong decoding auth config")
- }
- arr := strings.SplitN(string(decoded), ":", 2)
- if len(arr) != 2 {
- return "", "", fmt.Errorf("Invalid auth configuration file")
- }
- password := strings.Trim(arr[1], "\x00")
- return arr[0], password, nil
-}
-
-// load up the auth config information and return values
-// FIXME: use the internal golang config parser
-func LoadConfig(rootPath string) (*ConfigFile, error) {
- configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
- confFile := path.Join(rootPath, CONFIGFILE)
- if _, err := os.Stat(confFile); err != nil {
- return &configFile, nil //missing file is not an error
- }
- b, err := ioutil.ReadFile(confFile)
- if err != nil {
- return &configFile, err
- }
-
- if err := json.Unmarshal(b, &configFile.Configs); err != nil {
- arr := strings.Split(string(b), "\n")
- if len(arr) < 2 {
- return &configFile, fmt.Errorf("The Auth config file is empty")
- }
- authConfig := AuthConfig{}
- origAuth := strings.Split(arr[0], " = ")
- if len(origAuth) != 2 {
- return &configFile, fmt.Errorf("Invalid Auth config file")
- }
- authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
- if err != nil {
- return &configFile, err
- }
- origEmail := strings.Split(arr[1], " = ")
- if len(origEmail) != 2 {
- return &configFile, fmt.Errorf("Invalid Auth config file")
- }
- authConfig.Email = origEmail[1]
- authConfig.ServerAddress = IndexServerAddress()
- // *TODO: Switch to using IndexServerName() instead?
- configFile.Configs[IndexServerAddress()] = authConfig
- } else {
- for k, authConfig := range configFile.Configs {
- authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
- if err != nil {
- return &configFile, err
- }
- authConfig.Auth = ""
- authConfig.ServerAddress = k
- configFile.Configs[k] = authConfig
- }
- }
- return &configFile, nil
-}
-
-// save the auth config
-func SaveConfig(configFile *ConfigFile) error {
- confFile := path.Join(configFile.rootPath, CONFIGFILE)
- if len(configFile.Configs) == 0 {
- os.Remove(confFile)
- return nil
- }
-
- configs := make(map[string]AuthConfig, len(configFile.Configs))
- for k, authConfig := range configFile.Configs {
- authCopy := authConfig
-
- authCopy.Auth = encodeAuth(&authCopy)
- authCopy.Username = ""
- authCopy.Password = ""
- authCopy.ServerAddress = ""
- configs[k] = authCopy
- }
-
- b, err := json.MarshalIndent(configs, "", "\t")
- if err != nil {
- return err
- }
- err = ioutil.WriteFile(confFile, b, 0600)
- if err != nil {
- return err
- }
- return nil
-}
-
// Login tries to register/login to the registry server.
-func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
+func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
// Separates the v2 registry login logic from the v1 logic.
if registryEndpoint.Version == APIVersion2 {
- return loginV2(authConfig, registryEndpoint, factory)
+ return loginV2(authConfig, registryEndpoint)
}
-
- return loginV1(authConfig, registryEndpoint, factory)
+ return loginV1(authConfig, registryEndpoint)
}
// loginV1 tries to register/login to the v1 registry server.
-func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
+func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
var (
status string
reqBody []byte
err error
- client = registryEndpoint.HTTPClient()
reqStatusCode = 0
serverAddress = authConfig.ServerAddress
)
- log.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
+ logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
if serverAddress == "" {
return "", fmt.Errorf("Server Error: Server Address not set.")
@@ -264,7 +125,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
b := strings.NewReader(string(jsonBody))
- req1, err := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
+ req1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
if err != nil {
return "", fmt.Errorf("Server Error: %s", err)
}
@@ -285,9 +146,9 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
}
} else if reqStatusCode == 400 {
if string(reqBody) == "\"Username or email already exists\"" {
- req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
+ req, err := http.NewRequest("GET", serverAddress+"users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
@@ -314,9 +175,9 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
} else if reqStatusCode == 401 {
// This case would happen with private registries where /v1/users is
// protected, so people can use `docker login` as an auth check.
- req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
+ req, err := http.NewRequest("GET", serverAddress+"users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
@@ -348,22 +209,21 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
// now, users should create their account through other means like directly from a web page
// served by the v2 registry service provider. Whether this will be supported in the future
// is to be determined.
-func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
- log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
+func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
+ logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
var (
err error
allErrors []error
- client = registryEndpoint.HTTPClient()
)
for _, challenge := range registryEndpoint.AuthChallenges {
- log.Debugf("trying %q auth challenge with params %s", challenge.Scheme, challenge.Parameters)
+ logrus.Debugf("trying %q auth challenge with params %s", challenge.Scheme, challenge.Parameters)
switch strings.ToLower(challenge.Scheme) {
case "basic":
- err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
+ err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
case "bearer":
- err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
+ err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
default:
// Unsupported challenge types are explicitly skipped.
err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
@@ -373,7 +233,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
return "Login Succeeded", nil
}
- log.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err)
+ logrus.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err)
allErrors = append(allErrors, err)
}
@@ -381,15 +241,15 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
}
-func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) error {
- req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
+func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
+ req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
if err != nil {
return err
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return err
}
@@ -402,20 +262,20 @@ func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, regis
return nil
}
-func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) error {
- token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory)
+func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
+ token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint)
if err != nil {
return err
}
- req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
+ req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
if err != nil {
return err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return err
}
@@ -429,10 +289,10 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis
}
// this method matches a auth configuration to a server address or a url
-func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
+func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig {
configKey := index.GetAuthConfigKey()
// First try the happy case
- if c, found := config.Configs[configKey]; found || index.Official {
+ if c, found := config.AuthConfigs[configKey]; found || index.Official {
return c
}
@@ -451,12 +311,12 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
- for registry, config := range config.Configs {
+ for registry, ac := range config.AuthConfigs {
if configKey == convertToHostname(registry) {
- return config
+ return ac
}
}
// When all else fails, return an empty auth config
- return AuthConfig{}
+ return cliconfig.AuthConfig{}
}
diff --git a/registry/auth_test.go b/registry/auth_test.go
index 9cc299aab..71b963a1f 100644
--- a/registry/auth_test.go
+++ b/registry/auth_test.go
@@ -3,15 +3,18 @@ package registry
import (
"io/ioutil"
"os"
+ "path/filepath"
"testing"
+
+ "github.com/docker/docker/cliconfig"
)
func TestEncodeAuth(t *testing.T) {
- newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
- authStr := encodeAuth(newAuthConfig)
- decAuthConfig := &AuthConfig{}
+ newAuthConfig := &cliconfig.AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
+ authStr := cliconfig.EncodeAuth(newAuthConfig)
+ decAuthConfig := &cliconfig.AuthConfig{}
var err error
- decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
+ decAuthConfig.Username, decAuthConfig.Password, err = cliconfig.DecodeAuth(authStr)
if err != nil {
t.Fatal(err)
}
@@ -26,18 +29,16 @@ func TestEncodeAuth(t *testing.T) {
}
}
-func setupTempConfigFile() (*ConfigFile, error) {
+func setupTempConfigFile() (*cliconfig.ConfigFile, error) {
root, err := ioutil.TempDir("", "docker-test-auth")
if err != nil {
return nil, err
}
- configFile := &ConfigFile{
- rootPath: root,
- Configs: make(map[string]AuthConfig),
- }
+ root = filepath.Join(root, cliconfig.CONFIGFILE)
+ configFile := cliconfig.NewConfigFile(root)
for _, registry := range []string{"testIndex", IndexServerAddress()} {
- configFile.Configs[registry] = AuthConfig{
+ configFile.AuthConfigs[registry] = cliconfig.AuthConfig{
Username: "docker-user",
Password: "docker-pass",
Email: "docker@docker.io",
@@ -52,14 +53,14 @@ func TestSameAuthDataPostSave(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- defer os.RemoveAll(configFile.rootPath)
+ defer os.RemoveAll(configFile.Filename())
- err = SaveConfig(configFile)
+ err = configFile.Save()
if err != nil {
t.Fatal(err)
}
- authConfig := configFile.Configs["testIndex"]
+ authConfig := configFile.AuthConfigs["testIndex"]
if authConfig.Username != "docker-user" {
t.Fail()
}
@@ -79,9 +80,9 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- defer os.RemoveAll(configFile.rootPath)
+ defer os.RemoveAll(configFile.Filename())
- indexConfig := configFile.Configs[IndexServerAddress()]
+ indexConfig := configFile.AuthConfigs[IndexServerAddress()]
officialIndex := &IndexInfo{
Official: true,
@@ -90,10 +91,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
Official: false,
}
- resolved := configFile.ResolveAuthConfig(officialIndex)
+ resolved := ResolveAuthConfig(configFile, officialIndex)
assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()")
- resolved = configFile.ResolveAuthConfig(privateIndex)
+ resolved = ResolveAuthConfig(configFile, privateIndex)
assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()")
}
@@ -102,26 +103,26 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- defer os.RemoveAll(configFile.rootPath)
+ defer os.RemoveAll(configFile.Filename())
- registryAuth := AuthConfig{
+ registryAuth := cliconfig.AuthConfig{
Username: "foo-user",
Password: "foo-pass",
Email: "foo@example.com",
}
- localAuth := AuthConfig{
+ localAuth := cliconfig.AuthConfig{
Username: "bar-user",
Password: "bar-pass",
Email: "bar@example.com",
}
- officialAuth := AuthConfig{
+ officialAuth := cliconfig.AuthConfig{
Username: "baz-user",
Password: "baz-pass",
Email: "baz@example.com",
}
- configFile.Configs[IndexServerAddress()] = officialAuth
+ configFile.AuthConfigs[IndexServerAddress()] = officialAuth
- expectedAuths := map[string]AuthConfig{
+ expectedAuths := map[string]cliconfig.AuthConfig{
"registry.example.com": registryAuth,
"localhost:8000": localAuth,
"registry.com": localAuth,
@@ -157,13 +158,13 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
Name: configKey,
}
for _, registry := range registries {
- configFile.Configs[registry] = configured
- resolved := configFile.ResolveAuthConfig(index)
+ configFile.AuthConfigs[registry] = configured
+ resolved := ResolveAuthConfig(configFile, index)
if resolved.Email != configured.Email {
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
}
- delete(configFile.Configs, registry)
- resolved = configFile.ResolveAuthConfig(index)
+ delete(configFile.AuthConfigs, registry)
+ resolved = ResolveAuthConfig(configFile, index)
if resolved.Email == configured.Email {
t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
}
diff --git a/registry/config.go b/registry/config.go
index a706f17e6..92ef4d997 100644
--- a/registry/config.go
+++ b/registry/config.go
@@ -9,9 +9,9 @@ import (
"regexp"
"strings"
+ "github.com/docker/docker/image"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
- "github.com/docker/docker/utils"
)
// Options holds command line options.
@@ -60,10 +60,10 @@ func (ipnet *netIPNet) MarshalJSON() ([]byte, error) {
}
func (ipnet *netIPNet) UnmarshalJSON(b []byte) (err error) {
- var ipnet_str string
- if err = json.Unmarshal(b, &ipnet_str); err == nil {
+ var ipnetStr string
+ if err = json.Unmarshal(b, &ipnetStr); err == nil {
var cidr *net.IPNet
- if _, cidr, err = net.ParseCIDR(ipnet_str); err == nil {
+ if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil {
*ipnet = netIPNet(*cidr)
}
}
@@ -189,7 +189,7 @@ func ValidateMirror(val string) (string, error) {
return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
}
- return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
+ return fmt.Sprintf("%s://%s/", uri.Scheme, uri.Host), nil
}
// ValidateIndexName validates an index name.
@@ -198,6 +198,9 @@ func ValidateIndexName(val string) (string, error) {
if val == "index."+IndexServerName() {
val = IndexServerName()
}
+ if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
+ return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
+ }
// *TODO: Check if valid hostname[:port]/ip[:port]?
return val, nil
}
@@ -213,7 +216,7 @@ func validateRemoteName(remoteName string) error {
name = nameParts[0]
// the repository name must not be a valid image ID
- if err := utils.ValidateID(name); err == nil {
+ if err := image.ValidateID(name); err == nil {
return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
}
} else {
@@ -235,6 +238,9 @@ func validateRemoteName(remoteName string) error {
if !validRepo.MatchString(name) {
return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
}
+ if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") {
+ return fmt.Errorf("Invalid repository name (%s). Cannot begin or end with a hyphen.", name)
+ }
return nil
}
@@ -352,7 +358,9 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInf
// *TODO: Decouple index name from hostname (via registry configuration?)
repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
repoInfo.CanonicalName = repoInfo.LocalName
+
}
+
return repoInfo, nil
}
diff --git a/registry/endpoint.go b/registry/endpoint.go
index 59ae4dd54..ce92668f4 100644
--- a/registry/endpoint.go
+++ b/registry/endpoint.go
@@ -1,7 +1,6 @@
package registry
import (
- "crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
@@ -10,9 +9,9 @@ import (
"net/url"
"strings"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/registry/v2"
- "github.com/docker/docker/utils"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/distribution/registry/api/v2"
+ "github.com/docker/docker/pkg/transport"
)
// for mocking in unit tests
@@ -43,9 +42,9 @@ func scanForAPIVersion(address string) (string, APIVersion) {
}
// NewEndpoint parses the given address to return a registry endpoint.
-func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
+func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
// *TODO: Allow per-registry configuration of endpoints.
- endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
+ endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure, metaHeaders)
if err != nil {
return nil, err
}
@@ -57,7 +56,7 @@ func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
}
func validateEndpoint(endpoint *Endpoint) error {
- log.Debugf("pinging registry endpoint %s", endpoint)
+ logrus.Debugf("pinging registry endpoint %s", endpoint)
// Try HTTPS ping to registry
endpoint.URL.Scheme = "https"
@@ -69,7 +68,7 @@ func validateEndpoint(endpoint *Endpoint) error {
}
// If registry is insecure and HTTPS failed, fallback to HTTP.
- log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
+ logrus.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
endpoint.URL.Scheme = "http"
var err2 error
@@ -83,7 +82,7 @@ func validateEndpoint(endpoint *Endpoint) error {
return nil
}
-func newEndpoint(address string, secure bool) (*Endpoint, error) {
+func newEndpoint(address string, secure bool, metaHeaders http.Header) (*Endpoint, error) {
var (
endpoint = new(Endpoint)
trimmedAddress string
@@ -100,15 +99,18 @@ func newEndpoint(address string, secure bool) (*Endpoint, error) {
return nil, err
}
endpoint.IsSecure = secure
+ tr := NewTransport(ConnectTimeout, endpoint.IsSecure)
+ endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...))
return endpoint, nil
}
-func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
- return NewEndpoint(repoInfo.Index)
+func (repoInfo *RepositoryInfo) GetEndpoint(metaHeaders http.Header) (*Endpoint, error) {
+ return NewEndpoint(repoInfo.Index, metaHeaders)
}
// Endpoint stores basic information about a registry endpoint.
type Endpoint struct {
+ client *http.Client
URL *url.URL
Version APIVersion
IsSecure bool
@@ -135,25 +137,24 @@ func (e *Endpoint) Path(path string) string {
func (e *Endpoint) Ping() (RegistryInfo, error) {
// The ping logic to use is determined by the registry endpoint version.
- factory := HTTPRequestFactory(nil)
switch e.Version {
case APIVersion1:
- return e.pingV1(factory)
+ return e.pingV1()
case APIVersion2:
- return e.pingV2(factory)
+ return e.pingV2()
}
// APIVersionUnknown
// We should try v2 first...
e.Version = APIVersion2
- regInfo, errV2 := e.pingV2(factory)
+ regInfo, errV2 := e.pingV2()
if errV2 == nil {
return regInfo, nil
}
// ... then fallback to v1.
e.Version = APIVersion1
- regInfo, errV1 := e.pingV1(factory)
+ regInfo, errV1 := e.pingV1()
if errV1 == nil {
return regInfo, nil
}
@@ -162,8 +163,8 @@ func (e *Endpoint) Ping() (RegistryInfo, error) {
return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
}
-func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, error) {
- log.Debugf("attempting v1 ping for registry endpoint %s", e)
+func (e *Endpoint) pingV1() (RegistryInfo, error) {
+ logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
if e.String() == IndexServerAddress() {
// Skip the check, we know this one is valid
@@ -171,12 +172,12 @@ func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, erro
return RegistryInfo{Standalone: false}, nil
}
- req, err := factory.NewRequest("GET", e.Path("_ping"), nil)
+ req, err := http.NewRequest("GET", e.Path("_ping"), nil)
if err != nil {
return RegistryInfo{Standalone: false}, err
}
- resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
+ resp, err := e.client.Do(req)
if err != nil {
return RegistryInfo{Standalone: false}, err
}
@@ -194,17 +195,17 @@ func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, erro
Standalone: true,
}
if err := json.Unmarshal(jsonString, &info); err != nil {
- log.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
+ logrus.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
// don't stop here. Just assume sane defaults
}
if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
- log.Debugf("Registry version header: '%s'", hdr)
+ logrus.Debugf("Registry version header: '%s'", hdr)
info.Version = hdr
}
- log.Debugf("RegistryInfo.Version: %q", info.Version)
+ logrus.Debugf("RegistryInfo.Version: %q", info.Version)
standalone := resp.Header.Get("X-Docker-Registry-Standalone")
- log.Debugf("Registry standalone header: '%s'", standalone)
+ logrus.Debugf("Registry standalone header: '%s'", standalone)
// Accepted values are "true" (case-insensitive) and "1".
if strings.EqualFold(standalone, "true") || standalone == "1" {
info.Standalone = true
@@ -212,19 +213,19 @@ func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, erro
// there is a header set, and it is not "true" or "1", so assume fails
info.Standalone = false
}
- log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
+ logrus.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
return info, nil
}
-func (e *Endpoint) pingV2(factory *utils.HTTPRequestFactory) (RegistryInfo, error) {
- log.Debugf("attempting v2 ping for registry endpoint %s", e)
+func (e *Endpoint) pingV2() (RegistryInfo, error) {
+ logrus.Debugf("attempting v2 ping for registry endpoint %s", e)
- req, err := factory.NewRequest("GET", e.Path(""), nil)
+ req, err := http.NewRequest("GET", e.Path(""), nil)
if err != nil {
return RegistryInfo{}, err
}
- resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
+ resp, err := e.client.Do(req)
if err != nil {
return RegistryInfo{}, err
}
@@ -263,20 +264,3 @@ HeaderLoop:
return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
}
-
-func (e *Endpoint) HTTPClient() *http.Client {
- tlsConfig := tls.Config{
- MinVersion: tls.VersionTLS10,
- }
- if !e.IsSecure {
- tlsConfig.InsecureSkipVerify = true
- }
- return &http.Client{
- Transport: &http.Transport{
- DisableKeepAlives: true,
- Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tlsConfig,
- },
- CheckRedirect: AddRequiredHeadersToRedirectedRequests,
- }
-}
diff --git a/registry/endpoint_test.go b/registry/endpoint_test.go
index 9567ba235..6f67867bb 100644
--- a/registry/endpoint_test.go
+++ b/registry/endpoint_test.go
@@ -19,7 +19,7 @@ func TestEndpointParse(t *testing.T) {
{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
}
for _, td := range testData {
- e, err := newEndpoint(td.str, false)
+ e, err := newEndpoint(td.str, false, nil)
if err != nil {
t.Errorf("%q: %s", td.str, err)
}
@@ -60,6 +60,7 @@ func TestValidateEndpointAmbiguousAPIVersion(t *testing.T) {
testEndpoint := Endpoint{
URL: testServerURL,
Version: APIVersionUnknown,
+ client: HTTPClient(NewTransport(ConnectTimeout, false)),
}
if err = validateEndpoint(&testEndpoint); err != nil {
diff --git a/registry/httpfactory.go b/registry/httpfactory.go
deleted file mode 100644
index a4fea3822..000000000
--- a/registry/httpfactory.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package registry
-
-import (
- "runtime"
-
- "github.com/docker/docker/autogen/dockerversion"
- "github.com/docker/docker/pkg/parsers/kernel"
- "github.com/docker/docker/utils"
-)
-
-func HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
- // FIXME: this replicates the 'info' job.
- httpVersion := make([]utils.VersionInfo, 0, 4)
- httpVersion = append(httpVersion, &simpleVersionInfo{"docker", dockerversion.VERSION})
- httpVersion = append(httpVersion, &simpleVersionInfo{"go", runtime.Version()})
- httpVersion = append(httpVersion, &simpleVersionInfo{"git-commit", dockerversion.GITCOMMIT})
- if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
- httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", kernelVersion.String()})
- }
- httpVersion = append(httpVersion, &simpleVersionInfo{"os", runtime.GOOS})
- httpVersion = append(httpVersion, &simpleVersionInfo{"arch", runtime.GOARCH})
- ud := utils.NewHTTPUserAgentDecorator(httpVersion...)
- md := &utils.HTTPMetaHeadersDecorator{
- Headers: metaHeaders,
- }
- factory := utils.NewHTTPRequestFactory(ud, md)
- return factory
-}
-
-// simpleVersionInfo is a simple implementation of
-// the interface VersionInfo, which is used
-// to provide version information for some product,
-// component, etc. It stores the product name and the version
-// in string and returns them on calls to Name() and Version().
-type simpleVersionInfo struct {
- name string
- version string
-}
-
-func (v *simpleVersionInfo) Name() string {
- return v.name
-}
-
-func (v *simpleVersionInfo) Version() string {
- return v.version
-}
diff --git a/registry/registry.go b/registry/registry.go
index a8bb83318..4436f135b 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -8,13 +8,20 @@ import (
"io/ioutil"
"net"
"net/http"
+ "net/http/httputil"
"os"
"path"
+ "path/filepath"
+ "runtime"
"strings"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/autogen/dockerversion"
+ "github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/timeoutconn"
+ "github.com/docker/docker/pkg/transport"
+ "github.com/docker/docker/pkg/useragent"
)
var (
@@ -31,29 +38,118 @@ const (
ConnectTimeout
)
-func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
- tlsConfig := tls.Config{
- RootCAs: roots,
+// dockerUserAgent is the User-Agent the Docker client uses to identify itself.
+// It is populated on init(), comprising version information of different components.
+var dockerUserAgent string
+
+func init() {
+ httpVersion := make([]useragent.VersionInfo, 0, 6)
+ httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
+ httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
+ httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
+ if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
+ httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
+ }
+ httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
+ httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
+
+ dockerUserAgent = useragent.AppendVersions("", httpVersion...)
+}
+
+type httpsRequestModifier struct{ tlsConfig *tls.Config }
+
+// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
+// it's because it's so as to match the current behavior in master: we generate the
+// certpool on every-goddam-request. It's not great, but it allows people to just put
+// the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would
+// prefer an fsnotify implementation, but that was out of scope of my refactoring.
+func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error {
+ var (
+ roots *x509.CertPool
+ certs []tls.Certificate
+ hostDir string
+ )
+
+ if req.URL.Scheme == "https" {
+ hasFile := func(files []os.FileInfo, name string) bool {
+ for _, f := range files {
+ if f.Name() == name {
+ return true
+ }
+ }
+ return false
+ }
+
+ if runtime.GOOS == "windows" {
+ hostDir = path.Join(os.TempDir(), "/docker/certs.d", req.URL.Host)
+ } else {
+ hostDir = path.Join("/etc/docker/certs.d", req.URL.Host)
+ }
+ logrus.Debugf("hostDir: %s", hostDir)
+ fs, err := ioutil.ReadDir(hostDir)
+ if err != nil && !os.IsNotExist(err) {
+ return nil
+ }
+
+ for _, f := range fs {
+ if strings.HasSuffix(f.Name(), ".crt") {
+ if roots == nil {
+ roots = x509.NewCertPool()
+ }
+ logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
+ data, err := ioutil.ReadFile(filepath.Join(hostDir, f.Name()))
+ if err != nil {
+ return err
+ }
+ roots.AppendCertsFromPEM(data)
+ }
+ if strings.HasSuffix(f.Name(), ".cert") {
+ certName := f.Name()
+ keyName := certName[:len(certName)-5] + ".key"
+ logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
+ if !hasFile(fs, keyName) {
+ return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
+ }
+ cert, err := tls.LoadX509KeyPair(filepath.Join(hostDir, certName), path.Join(hostDir, keyName))
+ if err != nil {
+ return err
+ }
+ certs = append(certs, cert)
+ }
+ if strings.HasSuffix(f.Name(), ".key") {
+ keyName := f.Name()
+ certName := keyName[:len(keyName)-4] + ".cert"
+ logrus.Debugf("key: %s", hostDir+"/"+f.Name())
+ if !hasFile(fs, certName) {
+ return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
+ }
+ }
+ }
+ m.tlsConfig.RootCAs = roots
+ m.tlsConfig.Certificates = certs
+ }
+ return nil
+}
+
+func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
+ tlsConfig := &tls.Config{
// Avoid fallback to SSL protocols < TLS1.0
- MinVersion: tls.VersionTLS10,
- Certificates: certs,
+ MinVersion: tls.VersionTLS10,
+ InsecureSkipVerify: !secure,
}
- if !secure {
- tlsConfig.InsecureSkipVerify = true
- }
-
- httpTransport := &http.Transport{
+ tr := &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tlsConfig,
+ TLSClientConfig: tlsConfig,
}
switch timeout {
case ConnectTimeout:
- httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
- // Set the connect timeout to 5 seconds
- d := net.Dialer{Timeout: 5 * time.Second, DualStack: true}
+ tr.Dial = func(proto string, addr string) (net.Conn, error) {
+ // Set the connect timeout to 30 seconds to allow for slower connection
+ // times...
+ d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
conn, err := d.Dial(proto, addr)
if err != nil {
@@ -64,7 +160,7 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate
return conn, nil
}
case ReceiveTimeout:
- httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
+ tr.Dial = func(proto string, addr string) (net.Conn, error) {
d := net.Dialer{DualStack: true}
conn, err := d.Dial(proto, addr)
@@ -76,84 +172,57 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate
}
}
- return &http.Client{
- Transport: httpTransport,
- CheckRedirect: AddRequiredHeadersToRedirectedRequests,
- Jar: jar,
+ if secure {
+ // note: httpsTransport also handles http transport
+ // but for HTTPS, it sets up the certs
+ return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig})
}
+
+ return tr
}
-func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
- var (
- pool *x509.CertPool
- certs []tls.Certificate
- )
+// DockerHeaders returns request modifiers that ensure requests have
+// the User-Agent header set to dockerUserAgent and that metaHeaders
+// are added.
+func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
+ modifiers := []transport.RequestModifier{
+ transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}),
+ }
+ if metaHeaders != nil {
+ modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
+ }
+ return modifiers
+}
- if secure && req.URL.Scheme == "https" {
- hasFile := func(files []os.FileInfo, name string) bool {
- for _, f := range files {
- if f.Name() == name {
- return true
- }
- }
- return false
- }
+type debugTransport struct{ http.RoundTripper }
- hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
- log.Debugf("hostDir: %s", hostDir)
- fs, err := ioutil.ReadDir(hostDir)
- if err != nil && !os.IsNotExist(err) {
- return nil, nil, err
- }
+func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ dump, err := httputil.DumpRequestOut(req, false)
+ if err != nil {
+ fmt.Println("could not dump request")
+ }
+ fmt.Println(string(dump))
+ resp, err := tr.RoundTripper.RoundTrip(req)
+ if err != nil {
+ return nil, err
+ }
+ dump, err = httputil.DumpResponse(resp, false)
+ if err != nil {
+ fmt.Println("could not dump response")
+ }
+ fmt.Println(string(dump))
+ return resp, err
+}
- for _, f := range fs {
- if strings.HasSuffix(f.Name(), ".crt") {
- if pool == nil {
- pool = x509.NewCertPool()
- }
- log.Debugf("crt: %s", hostDir+"/"+f.Name())
- data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
- if err != nil {
- return nil, nil, err
- }
- pool.AppendCertsFromPEM(data)
- }
- if strings.HasSuffix(f.Name(), ".cert") {
- certName := f.Name()
- keyName := certName[:len(certName)-5] + ".key"
- log.Debugf("cert: %s", hostDir+"/"+f.Name())
- if !hasFile(fs, keyName) {
- return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
- }
- cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
- if err != nil {
- return nil, nil, err
- }
- certs = append(certs, cert)
- }
- if strings.HasSuffix(f.Name(), ".key") {
- keyName := f.Name()
- certName := keyName[:len(keyName)-4] + ".cert"
- log.Debugf("key: %s", hostDir+"/"+f.Name())
- if !hasFile(fs, certName) {
- return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
- }
- }
- }
+func HTTPClient(transport http.RoundTripper) *http.Client {
+ if transport == nil {
+ transport = NewTransport(ConnectTimeout, true)
}
- if len(certs) == 0 {
- client := newClient(jar, pool, nil, timeout, secure)
- res, err := client.Do(req)
- if err != nil {
- return nil, nil, err
- }
- return res, client, nil
+ return &http.Client{
+ Transport: transport,
+ CheckRedirect: AddRequiredHeadersToRedirectedRequests,
}
-
- client := newClient(jar, pool, certs, timeout, secure)
- res, err := client.Do(req)
- return res, client, err
}
func trustedLocation(req *http.Request) bool {
diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go
index 57233d7c7..d58904d03 100644
--- a/registry/registry_mock_test.go
+++ b/registry/registry_mock_test.go
@@ -18,7 +18,7 @@ import (
"github.com/docker/docker/opts"
"github.com/gorilla/mux"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
var (
@@ -134,7 +134,7 @@ func init() {
func handlerAccessLog(handler http.Handler) http.Handler {
logHandler := func(w http.ResponseWriter, r *http.Request) {
- log.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL)
+ logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL)
handler.ServeHTTP(w, r)
}
return http.HandlerFunc(logHandler)
@@ -171,7 +171,7 @@ func makePublicIndex() *IndexInfo {
return index
}
-func makeServiceConfig(mirrors []string, insecure_registries []string) *ServiceConfig {
+func makeServiceConfig(mirrors []string, insecureRegistries []string) *ServiceConfig {
options := &Options{
Mirrors: opts.NewListOpts(nil),
InsecureRegistries: opts.NewListOpts(nil),
@@ -181,9 +181,9 @@ func makeServiceConfig(mirrors []string, insecure_registries []string) *ServiceC
options.Mirrors.Set(mirror)
}
}
- if insecure_registries != nil {
- for _, insecure_registries := range insecure_registries {
- options.InsecureRegistries.Set(insecure_registries)
+ if insecureRegistries != nil {
+ for _, insecureRegistries := range insecureRegistries {
+ options.InsecureRegistries.Set(insecureRegistries)
}
}
@@ -467,7 +467,7 @@ func TestPing(t *testing.T) {
* WARNING: Don't push on the repos uncommented, it'll block the tests
*
func TestWait(t *testing.T) {
- log.Println("Test HTTP server ready and waiting:", testHttpServer.URL)
+ logrus.Println("Test HTTP server ready and waiting:", testHttpServer.URL)
c := make(chan int)
<-c
}
diff --git a/registry/registry_test.go b/registry/registry_test.go
index d96630d90..33e86ff43 100644
--- a/registry/registry_test.go
+++ b/registry/registry_test.go
@@ -7,7 +7,8 @@ import (
"strings"
"testing"
- "github.com/docker/docker/utils"
+ "github.com/docker/docker/cliconfig"
+ "github.com/docker/docker/pkg/transport"
)
var (
@@ -20,46 +21,35 @@ const (
)
func spawnTestRegistrySession(t *testing.T) *Session {
- authConfig := &AuthConfig{}
- endpoint, err := NewEndpoint(makeIndex("/v1/"))
+ authConfig := &cliconfig.AuthConfig{}
+ endpoint, err := NewEndpoint(makeIndex("/v1/"), nil)
if err != nil {
t.Fatal(err)
}
- r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true)
+ var tr http.RoundTripper = debugTransport{NewTransport(ReceiveTimeout, endpoint.IsSecure)}
+ tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...)
+ client := HTTPClient(tr)
+ r, err := NewSession(client, authConfig, endpoint)
if err != nil {
t.Fatal(err)
}
+ // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
+ // header while authenticating, in order to retrieve a token that can be later used to
+ // perform authenticated actions.
+ //
+ // The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
+ // it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
+ //
+ // Because we know that the client's transport is an `*authTransport` we simply cast it,
+ // in order to set the internal cached token to the fake token, and thus send that fake token
+ // upon every subsequent requests.
+ r.client.Transport.(*authTransport).token = token
return r
}
-func TestPublicSession(t *testing.T) {
- authConfig := &AuthConfig{}
-
- getSessionDecorators := func(index *IndexInfo) int {
- endpoint, err := NewEndpoint(index)
- if err != nil {
- t.Fatal(err)
- }
- r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true)
- if err != nil {
- t.Fatal(err)
- }
- return len(r.reqFactory.GetDecorators())
- }
-
- decorators := getSessionDecorators(makeIndex("/v1/"))
- assertEqual(t, decorators, 0, "Expected no decorator on http session")
-
- decorators = getSessionDecorators(makeHttpsIndex("/v1/"))
- assertNotEqual(t, decorators, 0, "Expected decorator on https session")
-
- decorators = getSessionDecorators(makePublicIndex())
- assertEqual(t, decorators, 0, "Expected no decorator on public session")
-}
-
func TestPingRegistryEndpoint(t *testing.T) {
testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
- ep, err := NewEndpoint(index)
+ ep, err := NewEndpoint(index, nil)
if err != nil {
t.Fatal(err)
}
@@ -79,7 +69,7 @@ func TestPingRegistryEndpoint(t *testing.T) {
func TestEndpoint(t *testing.T) {
// Simple wrapper to fail test if err != nil
expandEndpoint := func(index *IndexInfo) *Endpoint {
- endpoint, err := NewEndpoint(index)
+ endpoint, err := NewEndpoint(index, nil)
if err != nil {
t.Fatal(err)
}
@@ -88,7 +78,7 @@ func TestEndpoint(t *testing.T) {
assertInsecureIndex := func(index *IndexInfo) {
index.Secure = true
- _, err := NewEndpoint(index)
+ _, err := NewEndpoint(index, nil)
assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index")
index.Secure = false
@@ -96,7 +86,7 @@ func TestEndpoint(t *testing.T) {
assertSecureIndex := func(index *IndexInfo) {
index.Secure = true
- _, err := NewEndpoint(index)
+ _, err := NewEndpoint(index, nil)
assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
index.Secure = false
@@ -162,14 +152,14 @@ func TestEndpoint(t *testing.T) {
}
for _, address := range badEndpoints {
index.Name = address
- _, err := NewEndpoint(index)
+ _, err := NewEndpoint(index, nil)
checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
}
}
func TestGetRemoteHistory(t *testing.T) {
r := spawnTestRegistrySession(t)
- hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"), token)
+ hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -181,16 +171,16 @@ func TestGetRemoteHistory(t *testing.T) {
func TestLookupRemoteImage(t *testing.T) {
r := spawnTestRegistrySession(t)
- err := r.LookupRemoteImage(imageID, makeURL("/v1/"), token)
+ err := r.LookupRemoteImage(imageID, makeURL("/v1/"))
assertEqual(t, err, nil, "Expected error of remote lookup to nil")
- if err := r.LookupRemoteImage("abcdef", makeURL("/v1/"), token); err == nil {
+ if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil {
t.Fatal("Expected error of remote lookup to not nil")
}
}
func TestGetRemoteImageJSON(t *testing.T) {
r := spawnTestRegistrySession(t)
- json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"), token)
+ json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -199,7 +189,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
t.Fatal("Expected non-empty json")
}
- _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"), token)
+ _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"))
if err == nil {
t.Fatal("Expected image not found error")
}
@@ -207,7 +197,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
func TestGetRemoteImageLayer(t *testing.T) {
r := spawnTestRegistrySession(t)
- data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), token, 0)
+ data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0)
if err != nil {
t.Fatal(err)
}
@@ -215,7 +205,7 @@ func TestGetRemoteImageLayer(t *testing.T) {
t.Fatal("Expected non-nil data result")
}
- _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), token, 0)
+ _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0)
if err == nil {
t.Fatal("Expected image not found error")
}
@@ -223,14 +213,14 @@ func TestGetRemoteImageLayer(t *testing.T) {
func TestGetRemoteTags(t *testing.T) {
r := spawnTestRegistrySession(t)
- tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO, token)
+ tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO)
if err != nil {
t.Fatal(err)
}
assertEqual(t, len(tags), 1, "Expected one tag")
assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
- _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz", token)
+ _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz")
if err == nil {
t.Fatal("Expected error when fetching tags for bogus repo")
}
@@ -264,7 +254,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
}
- err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"), token)
+ err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -273,7 +263,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
func TestPushImageLayerRegistry(t *testing.T) {
r := spawnTestRegistrySession(t)
layer := strings.NewReader("")
- _, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), token, []byte{})
+ _, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{})
if err != nil {
t.Fatal(err)
}
@@ -298,6 +288,9 @@ func TestValidateRepositoryName(t *testing.T) {
invalidRepoNames := []string{
"https://github.com/docker/docker",
"docker/Docker",
+ "-docker",
+ "-docker/docker",
+ "-docker.io/docker/docker",
"docker///docker",
"docker.io/docker/Docker",
"docker.io/docker///docker",
@@ -690,7 +683,7 @@ func TestNewIndexInfo(t *testing.T) {
func TestPushRegistryTag(t *testing.T) {
r := spawnTestRegistrySession(t)
- err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"), token)
+ err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -735,7 +728,7 @@ func TestSearchRepositories(t *testing.T) {
}
assertEqual(t, results.NumResults, 1, "Expected 1 search results")
assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query")
- assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars")
+ assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
}
func TestValidRemoteName(t *testing.T) {
diff --git a/registry/service.go b/registry/service.go
index 048340224..681174927 100644
--- a/registry/service.go
+++ b/registry/service.go
@@ -1,18 +1,11 @@
package registry
import (
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "net/http"
+
+ "github.com/docker/docker/cliconfig"
)
-// Service exposes registry capabilities in the standard Engine
-// interface. Once installed, it extends the engine with the
-// following calls:
-//
-// 'auth': Authenticate against the public registry
-// 'search': Search for images on the public registry
-// 'pull': Download images from any registry (TODO)
-// 'push': Upload images to any registry (TODO)
type Service struct {
Config *ServiceConfig
}
@@ -25,201 +18,54 @@ func NewService(options *Options) *Service {
}
}
-// Install installs registry capabilities to eng.
-func (s *Service) Install(eng *engine.Engine) error {
- eng.Register("auth", s.Auth)
- eng.Register("search", s.Search)
- eng.Register("resolve_repository", s.ResolveRepository)
- eng.Register("resolve_index", s.ResolveIndex)
- eng.Register("registry_config", s.GetRegistryConfig)
- return nil
-}
-
// Auth contacts the public registry with the provided credentials,
// and returns OK if authentication was sucessful.
// It can be used to verify the validity of a client's credentials.
-func (s *Service) Auth(job *engine.Job) engine.Status {
- var (
- authConfig = new(AuthConfig)
- endpoint *Endpoint
- index *IndexInfo
- status string
- err error
- )
-
- job.GetenvJson("authConfig", authConfig)
-
+func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
addr := authConfig.ServerAddress
if addr == "" {
// Use the official registry address if not specified.
addr = IndexServerAddress()
}
-
- if index, err = ResolveIndexInfo(job, addr); err != nil {
- return job.Error(err)
+ index, err := s.ResolveIndex(addr)
+ if err != nil {
+ return "", err
}
-
- if endpoint, err = NewEndpoint(index); err != nil {
- log.Errorf("unable to get new registry endpoint: %s", err)
- return job.Error(err)
+ endpoint, err := NewEndpoint(index, nil)
+ if err != nil {
+ return "", err
}
-
authConfig.ServerAddress = endpoint.String()
-
- if status, err = Login(authConfig, endpoint, HTTPRequestFactory(nil)); err != nil {
- log.Errorf("unable to login against registry endpoint %s: %s", endpoint, err)
- return job.Error(err)
- }
-
- log.Infof("successful registry login for endpoint %s: %s", endpoint, status)
- job.Printf("%s\n", status)
-
- return engine.StatusOK
+ return Login(authConfig, endpoint)
}
// Search queries the public registry for images matching the specified
// search terms, and returns the results.
-//
-// Argument syntax: search TERM
-//
-// Option environment:
-// 'authConfig': json-encoded credentials to authenticate against the registry.
-// The search extends to images only accessible via the credentials.
-//
-// 'metaHeaders': extra HTTP headers to include in the request to the registry.
-// The headers should be passed as a json-encoded dictionary.
-//
-// Output:
-// Results are sent as a collection of structured messages (using engine.Table).
-// Each result is sent as a separate message.
-// Results are ordered by number of stars on the public registry.
-func (s *Service) Search(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.Errorf("Usage: %s TERM", job.Name)
+func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) {
+ repoInfo, err := s.ResolveRepository(term)
+ if err != nil {
+ return nil, err
}
- var (
- term = job.Args[0]
- metaHeaders = map[string][]string{}
- authConfig = &AuthConfig{}
- )
- job.GetenvJson("authConfig", authConfig)
- job.GetenvJson("metaHeaders", metaHeaders)
- repoInfo, err := ResolveRepositoryInfo(job, term)
- if err != nil {
- return job.Error(err)
- }
// *TODO: Search multiple indexes.
- endpoint, err := repoInfo.GetEndpoint()
+ endpoint, err := repoInfo.GetEndpoint(http.Header(headers))
if err != nil {
- return job.Error(err)
+ return nil, err
}
- r, err := NewSession(authConfig, HTTPRequestFactory(metaHeaders), endpoint, true)
+ r, err := NewSession(endpoint.client, authConfig, endpoint)
if err != nil {
- return job.Error(err)
+ return nil, err
}
- results, err := r.SearchRepositories(repoInfo.GetSearchTerm())
- if err != nil {
- return job.Error(err)
- }
- outs := engine.NewTable("star_count", 0)
- for _, result := range results.Results {
- out := &engine.Env{}
- out.Import(result)
- outs.Add(out)
- }
- outs.ReverseSort()
- if _, err := outs.WriteListTo(job.Stdout); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
+ return r.SearchRepositories(repoInfo.GetSearchTerm())
}
// ResolveRepository splits a repository name into its components
// and configuration of the associated registry.
-func (s *Service) ResolveRepository(job *engine.Job) engine.Status {
- var (
- reposName = job.Args[0]
- )
-
- repoInfo, err := s.Config.NewRepositoryInfo(reposName)
- if err != nil {
- return job.Error(err)
- }
-
- out := engine.Env{}
- err = out.SetJson("repository", repoInfo)
- if err != nil {
- return job.Error(err)
- }
- out.WriteTo(job.Stdout)
-
- return engine.StatusOK
-}
-
-// Convenience wrapper for calling resolve_repository Job from a running job.
-func ResolveRepositoryInfo(jobContext *engine.Job, reposName string) (*RepositoryInfo, error) {
- job := jobContext.Eng.Job("resolve_repository", reposName)
- env, err := job.Stdout.AddEnv()
- if err != nil {
- return nil, err
- }
- if err := job.Run(); err != nil {
- return nil, err
- }
- info := RepositoryInfo{}
- if err := env.GetJson("repository", &info); err != nil {
- return nil, err
- }
- return &info, nil
+func (s *Service) ResolveRepository(name string) (*RepositoryInfo, error) {
+ return s.Config.NewRepositoryInfo(name)
}
// ResolveIndex takes indexName and returns index info
-func (s *Service) ResolveIndex(job *engine.Job) engine.Status {
- var (
- indexName = job.Args[0]
- )
-
- index, err := s.Config.NewIndexInfo(indexName)
- if err != nil {
- return job.Error(err)
- }
-
- out := engine.Env{}
- err = out.SetJson("index", index)
- if err != nil {
- return job.Error(err)
- }
- out.WriteTo(job.Stdout)
-
- return engine.StatusOK
-}
-
-// Convenience wrapper for calling resolve_index Job from a running job.
-func ResolveIndexInfo(jobContext *engine.Job, indexName string) (*IndexInfo, error) {
- job := jobContext.Eng.Job("resolve_index", indexName)
- env, err := job.Stdout.AddEnv()
- if err != nil {
- return nil, err
- }
- if err := job.Run(); err != nil {
- return nil, err
- }
- info := IndexInfo{}
- if err := env.GetJson("index", &info); err != nil {
- return nil, err
- }
- return &info, nil
-}
-
-// GetRegistryConfig returns current registry configuration.
-func (s *Service) GetRegistryConfig(job *engine.Job) engine.Status {
- out := engine.Env{}
- err := out.SetJson("config", s.Config)
- if err != nil {
- return job.Error(err)
- }
- out.WriteTo(job.Stdout)
-
- return engine.StatusOK
+func (s *Service) ResolveIndex(name string) (*IndexInfo, error) {
+ return s.Config.NewIndexInfo(name)
}
diff --git a/registry/session.go b/registry/session.go
index bf04b586d..71b27bef9 100644
--- a/registry/session.go
+++ b/registry/session.go
@@ -3,6 +3,8 @@ package registry
import (
"bytes"
"crypto/sha256"
+ "errors"
+ "sync"
// this is required for some certificates
_ "crypto/sha512"
"encoding/hex"
@@ -17,66 +19,143 @@ import (
"strings"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/tarsum"
- "github.com/docker/docker/utils"
+ "github.com/docker/docker/pkg/transport"
)
type Session struct {
- authConfig *AuthConfig
- reqFactory *utils.HTTPRequestFactory
indexEndpoint *Endpoint
- jar *cookiejar.Jar
- timeout TimeoutType
+ client *http.Client
+ // TODO(tiborvass): remove authConfig
+ authConfig *cliconfig.AuthConfig
}
-func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) {
+type authTransport struct {
+ http.RoundTripper
+ *cliconfig.AuthConfig
+
+ alwaysSetBasicAuth bool
+ token []string
+
+ mu sync.Mutex // guards modReq
+ modReq map[*http.Request]*http.Request // original -> modified
+}
+
+// AuthTransport handles the auth layer when communicating with a v1 registry (private or official)
+//
+// For private v1 registries, set alwaysSetBasicAuth to true.
+//
+// For the official v1 registry, if there isn't already an Authorization header in the request,
+// but there is an X-Docker-Token header set to true, then Basic Auth will be used to set the Authorization header.
+// After sending the request with the provided base http.RoundTripper, if an X-Docker-Token header, representing
+// a token, is present in the response, then it gets cached and sent in the Authorization header of all subsequent
+// requests.
+//
+// If the server sends a token without the client having requested it, it is ignored.
+//
+// This RoundTripper also has a CancelRequest method important for correct timeout handling.
+func AuthTransport(base http.RoundTripper, authConfig *cliconfig.AuthConfig, alwaysSetBasicAuth bool) http.RoundTripper {
+ if base == nil {
+ base = http.DefaultTransport
+ }
+ return &authTransport{
+ RoundTripper: base,
+ AuthConfig: authConfig,
+ alwaysSetBasicAuth: alwaysSetBasicAuth,
+ modReq: make(map[*http.Request]*http.Request),
+ }
+}
+
+func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
+ req := transport.CloneRequest(orig)
+ tr.mu.Lock()
+ tr.modReq[orig] = req
+ tr.mu.Unlock()
+
+ if tr.alwaysSetBasicAuth {
+ req.SetBasicAuth(tr.Username, tr.Password)
+ return tr.RoundTripper.RoundTrip(req)
+ }
+
+ // Don't override
+ if req.Header.Get("Authorization") == "" {
+ if req.Header.Get("X-Docker-Token") == "true" && len(tr.Username) > 0 {
+ req.SetBasicAuth(tr.Username, tr.Password)
+ } else if len(tr.token) > 0 {
+ req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ","))
+ }
+ }
+ resp, err := tr.RoundTripper.RoundTrip(req)
+ if err != nil {
+ delete(tr.modReq, orig)
+ return nil, err
+ }
+ if len(resp.Header["X-Docker-Token"]) > 0 {
+ tr.token = resp.Header["X-Docker-Token"]
+ }
+ resp.Body = &transport.OnEOFReader{
+ Rc: resp.Body,
+ Fn: func() { delete(tr.modReq, orig) },
+ }
+ return resp, nil
+}
+
+// CancelRequest cancels an in-flight request by closing its connection.
+func (tr *authTransport) CancelRequest(req *http.Request) {
+ type canceler interface {
+ CancelRequest(*http.Request)
+ }
+ if cr, ok := tr.RoundTripper.(canceler); ok {
+ tr.mu.Lock()
+ modReq := tr.modReq[req]
+ delete(tr.modReq, req)
+ tr.mu.Unlock()
+ cr.CancelRequest(modReq)
+ }
+}
+
+// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
+func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint *Endpoint) (r *Session, err error) {
r = &Session{
authConfig: authConfig,
+ client: client,
indexEndpoint: endpoint,
}
- if timeout {
- r.timeout = ReceiveTimeout
- }
-
- r.jar, err = cookiejar.New(nil)
- if err != nil {
- return nil, err
- }
+ var alwaysSetBasicAuth bool
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
- // alongside our requests.
- if r.indexEndpoint.VersionString(1) != IndexServerAddress() && r.indexEndpoint.URL.Scheme == "https" {
- info, err := r.indexEndpoint.Ping()
+ // alongside all our requests.
+ if endpoint.VersionString(1) != IndexServerAddress() && endpoint.URL.Scheme == "https" {
+ info, err := endpoint.Ping()
if err != nil {
return nil, err
}
- if info.Standalone {
- log.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String())
- dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password)
- factory.AddDecorator(dec)
+
+ if info.Standalone && authConfig != nil {
+ logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
+ alwaysSetBasicAuth = true
}
}
- r.reqFactory = factory
- return r, nil
-}
+ client.Transport = AuthTransport(client.Transport, authConfig, alwaysSetBasicAuth)
-func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
- return doRequest(req, r.jar, r.timeout, r.indexEndpoint.IsSecure)
+ jar, err := cookiejar.New(nil)
+ if err != nil {
+ return nil, errors.New("cookiejar.New is not supposed to return an error")
+ }
+ client.Jar = jar
+
+ return r, nil
}
// Retrieve the history of a given image from the Registry.
// Return a list of the parent's json (requested image included)
-func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
- req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
- if err != nil {
- return nil, err
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
+ res, err := r.client.Get(registry + "images/" + imgID + "/ancestry")
if err != nil {
return nil, err
}
@@ -85,55 +164,40 @@ func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]st
if res.StatusCode == 401 {
return nil, errLoginRequired
}
- return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
}
- jsonString, err := ioutil.ReadAll(res.Body)
- if err != nil {
- return nil, fmt.Errorf("Error while reading the http response: %s", err)
+ var history []string
+ if err := json.NewDecoder(res.Body).Decode(&history); err != nil {
+ return nil, fmt.Errorf("Error while reading the http response: %v", err)
}
- log.Debugf("Ancestry: %s", jsonString)
- history := new([]string)
- if err := json.Unmarshal(jsonString, history); err != nil {
- return nil, err
- }
- return *history, nil
+ logrus.Debugf("Ancestry: %v", history)
+ return history, nil
}
// Check if an image exists in the Registry
-func (r *Session) LookupRemoteImage(imgID, registry string, token []string) error {
- req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
- if err != nil {
- return err
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+func (r *Session) LookupRemoteImage(imgID, registry string) error {
+ res, err := r.client.Get(registry + "images/" + imgID + "/json")
if err != nil {
return err
}
res.Body.Close()
if res.StatusCode != 200 {
- return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
+ return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
}
return nil
}
// Retrieve an image from the Registry.
-func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
- // Get the JSON
- req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
- if err != nil {
- return nil, -1, fmt.Errorf("Failed to download json: %s", err)
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error) {
+ res, err := r.client.Get(registry + "images/" + imgID + "/json")
if err != nil {
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
- return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
+ return nil, -1, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
}
// if the size header is not present, then set it to '-1'
imageSize := -1
@@ -146,44 +210,44 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]
jsonString, err := ioutil.ReadAll(res.Body)
if err != nil {
- return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
+ return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString)
}
return jsonString, imageSize, nil
}
-func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) {
+func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) {
var (
retries = 5
statusCode = 0
- client *http.Client
res *http.Response
+ err error
imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
)
- req, err := r.reqFactory.NewRequest("GET", imageURL, nil)
+ req, err := http.NewRequest("GET", imageURL, nil)
if err != nil {
- return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
+ return nil, fmt.Errorf("Error while getting from the server: %v", err)
}
- setTokenAuth(req, token)
+ // TODO: why are we doing retries at this level?
+ // These retries should be generic to both v1 and v2
for i := 1; i <= retries; i++ {
statusCode = 0
- res, client, err = r.doRequest(req)
- if err != nil {
- log.Debugf("Error contacting registry: %s", err)
- if res != nil {
- if res.Body != nil {
- res.Body.Close()
- }
- statusCode = res.StatusCode
- }
- if i == retries {
- return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
- statusCode, imgID)
- }
- time.Sleep(time.Duration(i) * 5 * time.Second)
- continue
+ res, err = r.client.Do(req)
+ if err == nil {
+ break
}
- break
+ logrus.Debugf("Error contacting registry %s: %v", registry, err)
+ if res != nil {
+ if res.Body != nil {
+ res.Body.Close()
+ }
+ statusCode = res.StatusCode
+ }
+ if i == retries {
+ return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
+ statusCode, imgID)
+ }
+ time.Sleep(time.Duration(i) * 5 * time.Second)
}
if res.StatusCode != 200 {
@@ -193,14 +257,14 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, im
}
if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
- log.Debugf("server supports resume")
- return httputils.ResumableRequestReaderWithInitialResponse(client, req, 5, imgSize, res), nil
+ logrus.Debugf("server supports resume")
+ return httputils.ResumableRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil
}
- log.Debugf("server doesn't support resume")
+ logrus.Debugf("server doesn't support resume")
return res.Body, nil
}
-func (r *Session) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
+func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) {
if strings.Count(repository, "/") == 0 {
// This will be removed once the Registry supports auto-resolution on
// the "library" namespace
@@ -208,25 +272,20 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token []
}
for _, host := range registries {
endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
- req, err := r.reqFactory.NewRequest("GET", endpoint, nil)
-
- if err != nil {
- return nil, err
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Get(endpoint)
if err != nil {
return nil, err
}
- log.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
+ logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
defer res.Body.Close()
- if res.StatusCode != 200 && res.StatusCode != 404 {
- continue
- } else if res.StatusCode == 404 {
+ if res.StatusCode == 404 {
return nil, fmt.Errorf("Repository not found")
}
+ if res.StatusCode != 200 {
+ continue
+ }
result := make(map[string]string)
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
@@ -259,18 +318,15 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote)
- log.Debugf("[registry] Calling GET %s", repositoryTarget)
+ logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
- req, err := r.reqFactory.NewRequest("GET", repositoryTarget, nil)
+ req, err := http.NewRequest("GET", repositoryTarget, nil)
if err != nil {
return nil, err
}
- if r.authConfig != nil && len(r.authConfig.Username) > 0 {
- req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
- }
+ // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
req.Header.Set("X-Docker-Token", "true")
-
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return nil, err
}
@@ -281,18 +337,13 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
// TODO: Right now we're ignoring checksums in the response body.
// In the future, we need to use them to check image validity.
if res.StatusCode == 404 {
- return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
} else if res.StatusCode != 200 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
- log.Debugf("Error reading response body: %s", err)
+ logrus.Debugf("Error reading response body: %s", err)
}
- return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
- }
-
- var tokens []string
- if res.Header.Get("X-Docker-Token") != "" {
- tokens = res.Header["X-Docker-Token"]
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
}
var endpoints []string
@@ -320,29 +371,29 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
return &RepositoryData{
ImgList: imgsData,
Endpoints: endpoints,
- Tokens: tokens,
}, nil
}
-func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
+func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error {
- log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
+ u := registry + "images/" + imgData.ID + "/checksum"
- req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
+ logrus.Debugf("[registry] Calling PUT %s", u)
+
+ req, err := http.NewRequest("PUT", u, nil)
if err != nil {
return err
}
- setTokenAuth(req, token)
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
- return fmt.Errorf("Failed to upload metadata: %s", err)
+ return fmt.Errorf("Failed to upload metadata: %v", err)
}
defer res.Body.Close()
if len(res.Cookies()) > 0 {
- r.jar.SetCookies(req.URL, res.Cookies())
+ r.client.Jar.SetCookies(req.URL, res.Cookies())
}
if res.StatusCode != 200 {
errBody, err := ioutil.ReadAll(res.Body)
@@ -361,29 +412,30 @@ func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, t
}
// Push a local image to the registry
-func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
+func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error {
- log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
+ u := registry + "images/" + imgData.ID + "/json"
- req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
+ logrus.Debugf("[registry] Calling PUT %s", u)
+
+ req, err := http.NewRequest("PUT", u, bytes.NewReader(jsonRaw))
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
}
defer res.Body.Close()
if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") {
- return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
+ return httputils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
}
if res.StatusCode != 200 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
- return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
+ return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
}
var jsonBody map[string]string
if err := json.Unmarshal(errBody, &jsonBody); err != nil {
@@ -391,14 +443,16 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist
} else if jsonBody["error"] == "Image already exists" {
return ErrAlreadyExists
}
- return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res)
+ return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res)
}
return nil
}
-func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
+func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
- log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
+ u := registry + "images/" + imgID + "/layer"
+
+ logrus.Debugf("[registry] Calling PUT %s", u)
tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0)
if err != nil {
@@ -409,17 +463,16 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
h.Write([]byte{'\n'})
checksumLayer := io.TeeReader(tarsumLayer, h)
- req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer)
+ req, err := http.NewRequest("PUT", u, checksumLayer)
if err != nil {
return "", "", err
}
req.Header.Add("Content-Type", "application/octet-stream")
req.ContentLength = -1
req.TransferEncoding = []string{"chunked"}
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
- return "", "", fmt.Errorf("Failed to upload layer: %s", err)
+ return "", "", fmt.Errorf("Failed to upload layer: %v", err)
}
if rc, ok := layer.(io.Closer); ok {
if err := rc.Close(); err != nil {
@@ -431,9 +484,9 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
if res.StatusCode != 200 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
- return "", "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
+ return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
}
- return "", "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res)
+ return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res)
}
checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil))
@@ -442,25 +495,24 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
// push a tag on the registry.
// Remote has the format '/
-func (r *Session) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
+func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error {
// "jsonify" the string
revision = "\"" + revision + "\""
path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
- req, err := r.reqFactory.NewRequest("PUT", registry+path, strings.NewReader(revision))
+ req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision))
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
- setTokenAuth(req, token)
req.ContentLength = int64(len(revision))
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return err
}
res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 201 {
- return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
+ return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
}
return nil
}
@@ -486,10 +538,11 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
suffix = "images"
}
u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote, suffix)
- log.Debugf("[registry] PUT %s", u)
- log.Debugf("Image list pushed to index:\n%s", imgListJSON)
+ logrus.Debugf("[registry] PUT %s", u)
+ logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
headers := map[string][]string{
- "Content-type": {"application/json"},
+ "Content-type": {"application/json"},
+ // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
"X-Docker-Token": {"true"},
}
if validate {
@@ -507,7 +560,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
}
res.Body.Close()
u = res.Header.Get("Location")
- log.Debugf("Redirected to %s", u)
+ logrus.Debugf("Redirected to %s", u)
}
defer res.Body.Close()
@@ -520,53 +573,45 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
if res.StatusCode != 200 && res.StatusCode != 201 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
- log.Debugf("Error reading response body: %s", err)
+ logrus.Debugf("Error reading response body: %s", err)
}
- return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
- }
- if res.Header.Get("X-Docker-Token") != "" {
- tokens = res.Header["X-Docker-Token"]
- log.Debugf("Auth token: %v", tokens)
- } else {
- return nil, fmt.Errorf("Index response didn't contain an access token")
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
}
+ tokens = res.Header["X-Docker-Token"]
+ logrus.Debugf("Auth token: %v", tokens)
- if res.Header.Get("X-Docker-Endpoints") != "" {
- endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1))
- if err != nil {
- return nil, err
- }
- } else {
+ if res.Header.Get("X-Docker-Endpoints") == "" {
return nil, fmt.Errorf("Index response didn't contain any endpoints")
}
- }
- if validate {
+ endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1))
+ if err != nil {
+ return nil, err
+ }
+ } else {
if res.StatusCode != 204 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
- log.Debugf("Error reading response body: %s", err)
+ logrus.Debugf("Error reading response body: %s", err)
}
- return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res)
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res)
}
}
return &RepositoryData{
- Tokens: tokens,
Endpoints: endpoints,
}, nil
}
func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
- req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(body))
+ req, err := http.NewRequest("PUT", u, bytes.NewReader(body))
if err != nil {
return nil, err
}
- req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(body))
for k, v := range headers {
req.Header[k] = v
}
- response, _, err := r.doRequest(req)
+ response, err := r.client.Do(req)
if err != nil {
return nil, err
}
@@ -578,43 +623,29 @@ func shouldRedirect(response *http.Response) bool {
}
func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
- log.Debugf("Index server: %s", r.indexEndpoint)
+ logrus.Debugf("Index server: %s", r.indexEndpoint)
u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term)
- req, err := r.reqFactory.NewRequest("GET", u, nil)
- if err != nil {
- return nil, err
- }
- if r.authConfig != nil && len(r.authConfig.Username) > 0 {
- req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
- }
- req.Header.Set("X-Docker-Token", "true")
- res, _, err := r.doRequest(req)
+ res, err := r.client.Get(u)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
- return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res)
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res)
}
result := new(SearchResults)
- err = json.NewDecoder(res.Body).Decode(result)
- return result, err
+ return result, json.NewDecoder(res.Body).Decode(result)
}
-func (r *Session) GetAuthConfig(withPasswd bool) *AuthConfig {
+// TODO(tiborvass): remove this once registry client v2 is vendored
+func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
password := ""
if withPasswd {
password = r.authConfig.Password
}
- return &AuthConfig{
+ return &cliconfig.AuthConfig{
Username: r.authConfig.Username,
Password: password,
Email: r.authConfig.Email,
}
}
-
-func setTokenAuth(req *http.Request, token []string) {
- if req.Header.Get("Authorization") == "" { // Don't override
- req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
- }
-}
diff --git a/registry/session_v2.go b/registry/session_v2.go
index 833abeed6..43d638c79 100644
--- a/registry/session_v2.go
+++ b/registry/session_v2.go
@@ -9,10 +9,10 @@ import (
"net/http"
"strconv"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
- "github.com/docker/docker/registry/v2"
- "github.com/docker/docker/utils"
+ "github.com/docker/distribution/registry/api/v2"
+ "github.com/docker/docker/pkg/httputils"
)
const DockerDigestHeader = "Docker-Content-Digest"
@@ -27,7 +27,7 @@ func getV2Builder(e *Endpoint) *v2.URLBuilder {
func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
// TODO check if should use Mirror
if index.Official {
- ep, err = newEndpoint(REGISTRYSERVER, true)
+ ep, err = newEndpoint(REGISTRYSERVER, true, nil)
if err != nil {
return
}
@@ -38,7 +38,7 @@ func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error)
} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
ep = r.indexEndpoint
} else {
- ep, err = NewEndpoint(index)
+ ep, err = NewEndpoint(index, nil)
if err != nil {
return
}
@@ -49,7 +49,7 @@ func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error)
}
// GetV2Authorization gets the authorization needed to the given image
-// If readonly access is requested, then only the authorization may
+// If readonly access is requested, then the authorization may
// only be used for Get operations.
func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bool) (auth *RequestAuthorization, err error) {
scopes := []string{"pull"}
@@ -57,7 +57,7 @@ func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bo
scopes = append(scopes, "push")
}
- log.Debugf("Getting authorization for %s %s", imageName, scopes)
+ logrus.Debugf("Getting authorization for %s %s", imageName, scopes)
return NewRequestAuthorization(r.GetAuthConfig(true), ep, "repository", imageName, scopes), nil
}
@@ -75,16 +75,16 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
}
method := "GET"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return nil, "", err
}
if err := auth.Authorize(req); err != nil {
return nil, "", err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return nil, "", err
}
@@ -95,7 +95,7 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
} else if res.StatusCode == 404 {
return nil, "", ErrDoesNotExist
}
- return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
+ return nil, "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
}
manifestBytes, err := ioutil.ReadAll(res.Body)
@@ -109,23 +109,23 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
// - Succeeded to head image blob (already exists)
// - Failed with no error (continue to Push the Blob)
// - Failed with error
-func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (bool, error) {
- routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum)
+func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (bool, error) {
+ routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
if err != nil {
return false, err
}
method := "HEAD"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return false, err
}
if err := auth.Authorize(req); err != nil {
return false, err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return false, err
}
@@ -141,25 +141,25 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string,
return false, nil
}
- return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s:%s", res.StatusCode, imageName, sumType, sum), res)
+ return false, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res)
}
-func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, blobWrtr io.Writer, auth *RequestAuthorization) error {
- routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum)
+func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error {
+ routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
if err != nil {
return err
}
method := "GET"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return err
}
if err := auth.Authorize(req); err != nil {
return err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return err
}
@@ -168,29 +168,29 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, b
if res.StatusCode == 401 {
return errLoginRequired
}
- return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res)
+ return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res)
}
_, err = io.Copy(blobWrtr, res.Body)
return err
}
-func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (io.ReadCloser, int64, error) {
- routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum)
+func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (io.ReadCloser, int64, error) {
+ routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
if err != nil {
return nil, 0, err
}
method := "GET"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return nil, 0, err
}
if err := auth.Authorize(req); err != nil {
return nil, 0, err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return nil, 0, err
}
@@ -198,7 +198,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str
if res.StatusCode == 401 {
return nil, 0, errLoginRequired
}
- return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s:%s", res.StatusCode, imageName, sumType, sum), res)
+ return nil, 0, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res)
}
lenStr := res.Header.Get("Content-Length")
l, err := strconv.ParseInt(lenStr, 10, 64)
@@ -212,25 +212,25 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str
// Push the image to the server for storage.
// 'layer' is an uncompressed reader of the blob to be pushed.
// The server will generate it's own checksum calculation.
-func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string, blobRdr io.Reader, auth *RequestAuthorization) error {
+func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobRdr io.Reader, auth *RequestAuthorization) error {
location, err := r.initiateBlobUpload(ep, imageName, auth)
if err != nil {
return err
}
method := "PUT"
- log.Debugf("[registry] Calling %q %s", method, location)
- req, err := r.reqFactory.NewRequest(method, location, ioutil.NopCloser(blobRdr))
+ logrus.Debugf("[registry] Calling %q %s", method, location)
+ req, err := http.NewRequest(method, location, ioutil.NopCloser(blobRdr))
if err != nil {
return err
}
queryParams := req.URL.Query()
- queryParams.Add("digest", sumType+":"+sumStr)
+ queryParams.Add("digest", dgst.String())
req.URL.RawQuery = queryParams.Encode()
if err := auth.Authorize(req); err != nil {
return err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return err
}
@@ -244,8 +244,8 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string
if err != nil {
return err
}
- log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
- return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s:%s", res.StatusCode, imageName, sumType, sumStr), res)
+ logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
+ return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res)
}
return nil
@@ -258,8 +258,8 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
return "", err
}
- log.Debugf("[registry] Calling %q %s", "POST", routeURL)
- req, err := r.reqFactory.NewRequest("POST", routeURL, nil)
+ logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
+ req, err := http.NewRequest("POST", routeURL, nil)
if err != nil {
return "", err
}
@@ -267,7 +267,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
if err := auth.Authorize(req); err != nil {
return "", err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return "", err
}
@@ -285,8 +285,8 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
return "", err
}
- log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
- return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res)
+ logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
+ return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res)
}
if location = res.Header.Get("Location"); location == "" {
@@ -304,15 +304,15 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si
}
method := "PUT"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
+ req, err := http.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
if err != nil {
return "", err
}
if err := auth.Authorize(req); err != nil {
return "", err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return "", err
}
@@ -327,8 +327,8 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si
if err != nil {
return "", err
}
- log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
- return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
+ logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
+ return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
}
hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader))
@@ -352,8 +352,8 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si
}
type remoteTags struct {
- name string
- tags []string
+ Name string `json:"name"`
+ Tags []string `json:"tags"`
}
// Given a repository name, returns a json array of string tags
@@ -364,16 +364,16 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA
}
method := "GET"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return nil, err
}
if err := auth.Authorize(req); err != nil {
return nil, err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return nil, err
}
@@ -384,14 +384,12 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA
} else if res.StatusCode == 404 {
return nil, ErrDoesNotExist
}
- return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res)
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res)
}
- decoder := json.NewDecoder(res.Body)
var remote remoteTags
- err = decoder.Decode(&remote)
- if err != nil {
+ if err := json.NewDecoder(res.Body).Decode(&remote); err != nil {
return nil, fmt.Errorf("Error while decoding the http response: %s", err)
}
- return remote.tags, nil
+ return remote.Tags, nil
}
diff --git a/registry/token.go b/registry/token.go
index c79a8ca6c..e27cb6f52 100644
--- a/registry/token.go
+++ b/registry/token.go
@@ -7,15 +7,13 @@ import (
"net/http"
"net/url"
"strings"
-
- "github.com/docker/docker/utils"
)
type tokenResponse struct {
Token string `json:"token"`
}
-func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) (token string, err error) {
+func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint) (token string, err error) {
realm, ok := params["realm"]
if !ok {
return "", errors.New("no realm specified for token auth challenge")
@@ -34,7 +32,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
}
}
- req, err := factory.NewRequest("GET", realmURL.String(), nil)
+ req, err := http.NewRequest("GET", realmURL.String(), nil)
if err != nil {
return "", err
}
@@ -58,7 +56,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
req.URL.RawQuery = reqParams.Encode()
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
diff --git a/registry/types.go b/registry/types.go
index bd0bf8b75..2c8369bd8 100644
--- a/registry/types.go
+++ b/registry/types.go
@@ -5,6 +5,7 @@ type SearchResult struct {
IsOfficial bool `json:"is_official"`
Name string `json:"name"`
IsTrusted bool `json:"is_trusted"`
+ IsAutomated bool `json:"is_automated"`
Description string `json:"description"`
}
diff --git a/registry/v2/descriptors.go b/registry/v2/descriptors.go
deleted file mode 100644
index 68d182411..000000000
--- a/registry/v2/descriptors.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package v2
-
-import "net/http"
-
-// TODO(stevvooe): Add route descriptors for each named route, along with
-// accepted methods, parameters, returned status codes and error codes.
-
-// ErrorDescriptor provides relevant information about a given error code.
-type ErrorDescriptor struct {
- // Code is the error code that this descriptor describes.
- Code ErrorCode
-
- // Value provides a unique, string key, often captilized with
- // underscores, to identify the error code. This value is used as the
- // keyed value when serializing api errors.
- Value string
-
- // Message is a short, human readable decription of the error condition
- // included in API responses.
- Message string
-
- // Description provides a complete account of the errors purpose, suitable
- // for use in documentation.
- Description string
-
- // HTTPStatusCodes provides a list of status under which this error
- // condition may arise. If it is empty, the error condition may be seen
- // for any status code.
- HTTPStatusCodes []int
-}
-
-// ErrorDescriptors provides a list of HTTP API Error codes that may be
-// encountered when interacting with the registry API.
-var ErrorDescriptors = []ErrorDescriptor{
- {
- Code: ErrorCodeUnknown,
- Value: "UNKNOWN",
- Message: "unknown error",
- Description: `Generic error returned when the error does not have an
- API classification.`,
- },
- {
- Code: ErrorCodeDigestInvalid,
- Value: "DIGEST_INVALID",
- Message: "provided digest did not match uploaded content",
- Description: `When a blob is uploaded, the registry will check that
- the content matches the digest provided by the client. The error may
- include a detail structure with the key "digest", including the
- invalid digest string. This error may also be returned when a manifest
- includes an invalid layer digest.`,
- HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
- },
- {
- Code: ErrorCodeSizeInvalid,
- Value: "SIZE_INVALID",
- Message: "provided length did not match content length",
- Description: `When a layer is uploaded, the provided size will be
- checked against the uploaded content. If they do not match, this error
- will be returned.`,
- HTTPStatusCodes: []int{http.StatusBadRequest},
- },
- {
- Code: ErrorCodeNameInvalid,
- Value: "NAME_INVALID",
- Message: "manifest name did not match URI",
- Description: `During a manifest upload, if the name in the manifest
- does not match the uri name, this error will be returned.`,
- HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
- },
- {
- Code: ErrorCodeTagInvalid,
- Value: "TAG_INVALID",
- Message: "manifest tag did not match URI",
- Description: `During a manifest upload, if the tag in the manifest
- does not match the uri tag, this error will be returned.`,
- HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
- },
- {
- Code: ErrorCodeNameUnknown,
- Value: "NAME_UNKNOWN",
- Message: "repository name not known to registry",
- Description: `This is returned if the name used during an operation is
- unknown to the registry.`,
- HTTPStatusCodes: []int{http.StatusNotFound},
- },
- {
- Code: ErrorCodeManifestUnknown,
- Value: "MANIFEST_UNKNOWN",
- Message: "manifest unknown",
- Description: `This error is returned when the manifest, identified by
- name and tag is unknown to the repository.`,
- HTTPStatusCodes: []int{http.StatusNotFound},
- },
- {
- Code: ErrorCodeManifestInvalid,
- Value: "MANIFEST_INVALID",
- Message: "manifest invalid",
- Description: `During upload, manifests undergo several checks ensuring
- validity. If those checks fail, this error may be returned, unless a
- more specific error is included. The detail will contain information
- the failed validation.`,
- HTTPStatusCodes: []int{http.StatusBadRequest},
- },
- {
- Code: ErrorCodeManifestUnverified,
- Value: "MANIFEST_UNVERIFIED",
- Message: "manifest failed signature verification",
- Description: `During manifest upload, if the manifest fails signature
- verification, this error will be returned.`,
- HTTPStatusCodes: []int{http.StatusBadRequest},
- },
- {
- Code: ErrorCodeBlobUnknown,
- Value: "BLOB_UNKNOWN",
- Message: "blob unknown to registry",
- Description: `This error may be returned when a blob is unknown to the
- registry in a specified repository. This can be returned with a
- standard get or if a manifest references an unknown layer during
- upload.`,
- HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
- },
-
- {
- Code: ErrorCodeBlobUploadUnknown,
- Value: "BLOB_UPLOAD_UNKNOWN",
- Message: "blob upload unknown to registry",
- Description: `If a blob upload has been cancelled or was never
- started, this error code may be returned.`,
- HTTPStatusCodes: []int{http.StatusNotFound},
- },
-}
-
-var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor
-var idToDescriptors map[string]ErrorDescriptor
-
-func init() {
- errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(ErrorDescriptors))
- idToDescriptors = make(map[string]ErrorDescriptor, len(ErrorDescriptors))
-
- for _, descriptor := range ErrorDescriptors {
- errorCodeToDescriptors[descriptor.Code] = descriptor
- idToDescriptors[descriptor.Value] = descriptor
- }
-}
diff --git a/registry/v2/regexp.go b/registry/v2/regexp.go
deleted file mode 100644
index 07484dcd6..000000000
--- a/registry/v2/regexp.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package v2
-
-import "regexp"
-
-// This file defines regular expressions for use in route definition. These
-// are also defined in the registry code base. Until they are in a common,
-// shared location, and exported, they must be repeated here.
-
-// RepositoryNameComponentRegexp restricts registtry path components names to
-// start with at least two letters or numbers, with following parts able to
-// separated by one period, dash or underscore.
-var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9]+)*`)
-
-// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to
-// 5 path components, separated by a forward slash.
-var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String())
-
-// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
-var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
-
-// DigestRegexp matches valid digest types.
-var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+`)
diff --git a/registry/v2/routes.go b/registry/v2/routes.go
deleted file mode 100644
index de0a38fb8..000000000
--- a/registry/v2/routes.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package v2
-
-import "github.com/gorilla/mux"
-
-// The following are definitions of the name under which all V2 routes are
-// registered. These symbols can be used to look up a route based on the name.
-const (
- RouteNameBase = "base"
- RouteNameManifest = "manifest"
- RouteNameTags = "tags"
- RouteNameBlob = "blob"
- RouteNameBlobUpload = "blob-upload"
- RouteNameBlobUploadChunk = "blob-upload-chunk"
-)
-
-var allEndpoints = []string{
- RouteNameManifest,
- RouteNameTags,
- RouteNameBlob,
- RouteNameBlobUpload,
- RouteNameBlobUploadChunk,
-}
-
-// Router builds a gorilla router with named routes for the various API
-// methods. This can be used directly by both server implementations and
-// clients.
-func Router() *mux.Router {
- router := mux.NewRouter().
- StrictSlash(true)
-
- // GET /v2/ Check Check that the registry implements API version 2(.1)
- router.
- Path("/v2/").
- Name(RouteNameBase)
-
- // GET /v2//manifest/ Image Manifest Fetch the image manifest identified by name and reference where reference can be a tag or digest.
- // PUT /v2//manifest/ Image Manifest Upload the image manifest identified by name and reference where reference can be a tag or digest.
- // DELETE /v2//manifest/ Image Manifest Delete the image identified by name and reference where reference can be a tag or digest.
- router.
- Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + DigestRegexp.String() + "}").
- Name(RouteNameManifest)
-
- // GET /v2//tags/list Tags Fetch the tags under the repository identified by name.
- router.
- Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list").
- Name(RouteNameTags)
-
- // GET /v2//blob/ Layer Fetch the blob identified by digest.
- router.
- Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}").
- Name(RouteNameBlob)
-
- // POST /v2//blob/upload/ Layer Upload Initiate an upload of the layer identified by tarsum.
- router.
- Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/").
- Name(RouteNameBlobUpload)
-
- // GET /v2//blob/upload/ Layer Upload Get the status of the upload identified by tarsum and uuid.
- // PUT /v2//blob/upload/ Layer Upload Upload all or a chunk of the upload identified by tarsum and uuid.
- // DELETE /v2//blob/upload/ Layer Upload Cancel the upload identified by layer and uuid
- router.
- Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}").
- Name(RouteNameBlobUploadChunk)
-
- return router
-}
diff --git a/registry/v2/urls_test.go b/registry/v2/urls_test.go
deleted file mode 100644
index f30c96c0a..000000000
--- a/registry/v2/urls_test.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package v2
-
-import (
- "net/url"
- "testing"
-)
-
-type urlBuilderTestCase struct {
- description string
- expectedPath string
- build func() (string, error)
-}
-
-// TestURLBuilder tests the various url building functions, ensuring they are
-// returning the expected values.
-func TestURLBuilder(t *testing.T) {
- var (
- urlBuilder *URLBuilder
- err error
- )
-
- testCases := []urlBuilderTestCase{
- {
- description: "test base url",
- expectedPath: "/v2/",
- build: func() (string, error) {
- return urlBuilder.BuildBaseURL()
- },
- },
- {
- description: "test tags url",
- expectedPath: "/v2/foo/bar/tags/list",
- build: func() (string, error) {
- return urlBuilder.BuildTagsURL("foo/bar")
- },
- },
- {
- description: "test manifest url",
- expectedPath: "/v2/foo/bar/manifests/tag",
- build: func() (string, error) {
- return urlBuilder.BuildManifestURL("foo/bar", "tag")
- },
- },
- {
- description: "build blob url",
- expectedPath: "/v2/foo/bar/blobs/tarsum.v1+sha256:abcdef0123456789",
- build: func() (string, error) {
- return urlBuilder.BuildBlobURL("foo/bar", "tarsum.v1+sha256:abcdef0123456789")
- },
- },
- {
- description: "build blob upload url",
- expectedPath: "/v2/foo/bar/blobs/uploads/",
- build: func() (string, error) {
- return urlBuilder.BuildBlobUploadURL("foo/bar")
- },
- },
- {
- description: "build blob upload url with digest and size",
- expectedPath: "/v2/foo/bar/blobs/uploads/?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000",
- build: func() (string, error) {
- return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{
- "size": []string{"10000"},
- "digest": []string{"tarsum.v1+sha256:abcdef0123456789"},
- })
- },
- },
- {
- description: "build blob upload chunk url",
- expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part",
- build: func() (string, error) {
- return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part")
- },
- },
- {
- description: "build blob upload chunk url with digest and size",
- expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000",
- build: func() (string, error) {
- return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{
- "size": []string{"10000"},
- "digest": []string{"tarsum.v1+sha256:abcdef0123456789"},
- })
- },
- },
- }
-
- roots := []string{
- "http://example.com",
- "https://example.com",
- "http://localhost:5000",
- "https://localhost:5443",
- }
-
- for _, root := range roots {
- urlBuilder, err = NewURLBuilderFromString(root)
- if err != nil {
- t.Fatalf("unexpected error creating urlbuilder: %v", err)
- }
-
- for _, testCase := range testCases {
- url, err := testCase.build()
- if err != nil {
- t.Fatalf("%s: error building url: %v", testCase.description, err)
- }
-
- expectedURL := root + testCase.expectedPath
-
- if url != expectedURL {
- t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
- }
- }
- }
-}
diff --git a/runconfig/compare.go b/runconfig/compare.go
index 60a21a79c..1d969e9be 100644
--- a/runconfig/compare.go
+++ b/runconfig/compare.go
@@ -10,25 +10,25 @@ func Compare(a, b *Config) bool {
if a.AttachStdout != b.AttachStdout ||
a.AttachStderr != b.AttachStderr ||
a.User != b.User ||
- a.Memory != b.Memory ||
- a.MemorySwap != b.MemorySwap ||
- a.CpuShares != b.CpuShares ||
a.OpenStdin != b.OpenStdin ||
a.Tty != b.Tty {
return false
}
- if len(a.Cmd) != len(b.Cmd) ||
+
+ if a.Cmd.Len() != b.Cmd.Len() ||
len(a.Env) != len(b.Env) ||
len(a.Labels) != len(b.Labels) ||
len(a.PortSpecs) != len(b.PortSpecs) ||
len(a.ExposedPorts) != len(b.ExposedPorts) ||
- len(a.Entrypoint) != len(b.Entrypoint) ||
+ a.Entrypoint.Len() != b.Entrypoint.Len() ||
len(a.Volumes) != len(b.Volumes) {
return false
}
- for i := 0; i < len(a.Cmd); i++ {
- if a.Cmd[i] != b.Cmd[i] {
+ aCmd := a.Cmd.Slice()
+ bCmd := b.Cmd.Slice()
+ for i := 0; i < len(aCmd); i++ {
+ if aCmd[i] != bCmd[i] {
return false
}
}
@@ -52,8 +52,11 @@ func Compare(a, b *Config) bool {
return false
}
}
- for i := 0; i < len(a.Entrypoint); i++ {
- if a.Entrypoint[i] != b.Entrypoint[i] {
+
+ aEntrypoint := a.Entrypoint.Slice()
+ bEntrypoint := b.Entrypoint.Slice()
+ for i := 0; i < len(aEntrypoint); i++ {
+ if aEntrypoint[i] != bEntrypoint[i] {
return false
}
}
diff --git a/runconfig/config.go b/runconfig/config.go
index 45255e9b0..13d718956 100644
--- a/runconfig/config.go
+++ b/runconfig/config.go
@@ -1,10 +1,108 @@
package runconfig
import (
- "github.com/docker/docker/engine"
+ "encoding/json"
+ "io"
+ "strings"
+
"github.com/docker/docker/nat"
)
+// Entrypoint encapsulates the container entrypoint.
+// It might be represented as a string or an array of strings.
+// We need to override the json decoder to accept both options.
+// The JSON decoder will fail if the api sends an string and
+// we try to decode it into an array of string.
+type Entrypoint struct {
+ parts []string
+}
+
+func (e *Entrypoint) MarshalJSON() ([]byte, error) {
+ if e == nil {
+ return []byte{}, nil
+ }
+ return json.Marshal(e.Slice())
+}
+
+// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings.
+func (e *Entrypoint) UnmarshalJSON(b []byte) error {
+ if len(b) == 0 {
+ return nil
+ }
+
+ p := make([]string, 0, 1)
+ if err := json.Unmarshal(b, &p); err != nil {
+ p = append(p, string(b))
+ }
+ e.parts = p
+ return nil
+}
+
+func (e *Entrypoint) Len() int {
+ if e == nil {
+ return 0
+ }
+ return len(e.parts)
+}
+
+func (e *Entrypoint) Slice() []string {
+ if e == nil {
+ return nil
+ }
+ return e.parts
+}
+
+func NewEntrypoint(parts ...string) *Entrypoint {
+ return &Entrypoint{parts}
+}
+
+type Command struct {
+ parts []string
+}
+
+func (e *Command) ToString() string {
+ return strings.Join(e.parts, " ")
+}
+
+func (e *Command) MarshalJSON() ([]byte, error) {
+ if e == nil {
+ return []byte{}, nil
+ }
+ return json.Marshal(e.Slice())
+}
+
+// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings.
+func (e *Command) UnmarshalJSON(b []byte) error {
+ if len(b) == 0 {
+ return nil
+ }
+
+ p := make([]string, 0, 1)
+ if err := json.Unmarshal(b, &p); err != nil {
+ p = append(p, string(b))
+ }
+ e.parts = p
+ return nil
+}
+
+func (e *Command) Len() int {
+ if e == nil {
+ return 0
+ }
+ return len(e.parts)
+}
+
+func (e *Command) Slice() []string {
+ if e == nil {
+ return nil
+ }
+ return e.parts
+}
+
+func NewCommand(parts ...string) *Command {
+ return &Command{parts}
+}
+
// Note: the Config structure should hold only portable information about the container.
// Here, "portable" means "independent from the host we are running on".
// Non-portable information *should* appear in HostConfig.
@@ -12,10 +110,6 @@ type Config struct {
Hostname string
Domainname string
User string
- Memory int64 // FIXME: we keep it for backward compatibility, it has been moved to hostConfig.
- MemorySwap int64 // FIXME: it has been moved to hostConfig.
- CpuShares int64 // FIXME: it has been moved to hostConfig.
- Cpuset string // FIXME: it has been moved to hostConfig and renamed to CpusetCpus.
AttachStdin bool
AttachStdout bool
AttachStderr bool
@@ -25,53 +119,38 @@ type Config struct {
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string
- Cmd []string
+ Cmd *Command
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{}
+ VolumeDriver string
WorkingDir string
- Entrypoint []string
+ Entrypoint *Entrypoint
NetworkDisabled bool
MacAddress string
OnBuild []string
Labels map[string]string
}
-func ContainerConfigFromJob(job *engine.Job) *Config {
- config := &Config{
- Hostname: job.Getenv("Hostname"),
- Domainname: job.Getenv("Domainname"),
- User: job.Getenv("User"),
- Memory: job.GetenvInt64("Memory"),
- MemorySwap: job.GetenvInt64("MemorySwap"),
- CpuShares: job.GetenvInt64("CpuShares"),
- Cpuset: job.Getenv("Cpuset"),
- AttachStdin: job.GetenvBool("AttachStdin"),
- AttachStdout: job.GetenvBool("AttachStdout"),
- AttachStderr: job.GetenvBool("AttachStderr"),
- Tty: job.GetenvBool("Tty"),
- OpenStdin: job.GetenvBool("OpenStdin"),
- StdinOnce: job.GetenvBool("StdinOnce"),
- Image: job.Getenv("Image"),
- WorkingDir: job.Getenv("WorkingDir"),
- NetworkDisabled: job.GetenvBool("NetworkDisabled"),
- MacAddress: job.Getenv("MacAddress"),
+type ContainerConfigWrapper struct {
+ *Config
+ *hostConfigWrapper
+}
+
+func (c ContainerConfigWrapper) HostConfig() *HostConfig {
+ if c.hostConfigWrapper == nil {
+ return new(HostConfig)
}
- job.GetenvJson("ExposedPorts", &config.ExposedPorts)
- job.GetenvJson("Volumes", &config.Volumes)
- if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
- config.PortSpecs = PortSpecs
- }
- if Env := job.GetenvList("Env"); Env != nil {
- config.Env = Env
- }
- if Cmd := job.GetenvList("Cmd"); Cmd != nil {
- config.Cmd = Cmd
- }
+
+ return c.hostConfigWrapper.GetHostConfig()
+}
- job.GetenvJson("Labels", &config.Labels)
+func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
+ decoder := json.NewDecoder(src)
- if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
- config.Entrypoint = Entrypoint
+ var w ContainerConfigWrapper
+ if err := decoder.Decode(&w); err != nil {
+ return nil, nil, err
}
- return config
+
+ return w.Config, w.HostConfig(), nil
}
diff --git a/runconfig/config_test.go b/runconfig/config_test.go
index accbd9107..27727a495 100644
--- a/runconfig/config_test.go
+++ b/runconfig/config_test.go
@@ -1,7 +1,9 @@
package runconfig
import (
+ "bytes"
"fmt"
+ "io/ioutil"
"strings"
"testing"
@@ -43,13 +45,6 @@ func TestParseRunLinks(t *testing.T) {
if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
}
-
- if _, _, err := parse(t, "--link a"); err == nil {
- t.Fatalf("Error parsing links. `--link a` should be an error but is not")
- }
- if _, _, err := parse(t, "--link"); err == nil {
- t.Fatalf("Error parsing links. `--link` should be an error but is not")
- }
}
func TestParseRunAttach(t *testing.T) {
@@ -102,7 +97,7 @@ func TestParseRunVolumes(t *testing.T) {
if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
} else if _, exists := config.Volumes["/tmp"]; !exists {
- t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Recevied %v", config.Volumes)
+ t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
} else if _, exists := config.Volumes["/var"]; !exists {
t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
}
@@ -119,6 +114,14 @@ func TestParseRunVolumes(t *testing.T) {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
}
+ if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:roZ", "/hostVar:/containerVar:rwZ") != nil {
+ t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+ }
+
+ if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil {
+ t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+ }
+
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
} else if _, exists := config.Volumes["/containerVar"]; !exists {
@@ -260,5 +263,39 @@ func TestMerge(t *testing.T) {
t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
}
}
+}
+func TestDecodeContainerConfig(t *testing.T) {
+ fixtures := []struct {
+ file string
+ entrypoint *Entrypoint
+ }{
+ {"fixtures/container_config_1_14.json", NewEntrypoint()},
+ {"fixtures/container_config_1_17.json", NewEntrypoint("bash")},
+ {"fixtures/container_config_1_19.json", NewEntrypoint("bash")},
+ }
+
+ for _, f := range fixtures {
+ b, err := ioutil.ReadFile(f.file)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ c, h, err := DecodeContainerConfig(bytes.NewReader(b))
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
+ }
+
+ if c.Image != "ubuntu" {
+ t.Fatalf("Expected ubuntu image, found %s\n", c.Image)
+ }
+
+ if c.Entrypoint.Len() != f.entrypoint.Len() {
+ t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint)
+ }
+
+ if h.Memory != 1000 {
+ t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory)
+ }
+ }
}
diff --git a/runconfig/exec.go b/runconfig/exec.go
index 9390781a4..781cb3550 100644
--- a/runconfig/exec.go
+++ b/runconfig/exec.go
@@ -1,11 +1,7 @@
package runconfig
import (
- "fmt"
-
- "github.com/docker/docker/engine"
flag "github.com/docker/docker/pkg/mflag"
- "github.com/docker/docker/utils"
)
type ExecConfig struct {
@@ -20,37 +16,17 @@ type ExecConfig struct {
Cmd []string
}
-func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) {
- execConfig := &ExecConfig{
- // TODO(vishh): Expose 'User' once it is supported.
- //User: job.Getenv("User"),
- // TODO(vishh): Expose 'Privileged' once it is supported.
- //Privileged: job.GetenvBool("Privileged"),
- Tty: job.GetenvBool("Tty"),
- AttachStdin: job.GetenvBool("AttachStdin"),
- AttachStderr: job.GetenvBool("AttachStderr"),
- AttachStdout: job.GetenvBool("AttachStdout"),
- }
- cmd := job.GetenvList("Cmd")
- if len(cmd) == 0 {
- return nil, fmt.Errorf("No exec command specified")
- }
-
- execConfig.Cmd = cmd
-
- return execConfig, nil
-}
-
func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) {
var (
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")
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
+ flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])")
execCmd []string
container string
)
cmd.Require(flag.Min, 2)
- if err := utils.ParseFlags(cmd, args, true); err != nil {
+ if err := cmd.ParseFlags(args, true); err != nil {
return nil, err
}
container = cmd.Arg(0)
@@ -58,14 +34,13 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) {
execCmd = parsedArgs[1:]
execConfig := &ExecConfig{
- // TODO(vishh): Expose '-u' flag once it is supported.
- User: "",
- // TODO(vishh): Expose '-p' flag once it is supported.
- Privileged: false,
- Tty: *flTty,
- Cmd: execCmd,
- Container: container,
- Detach: *flDetach,
+ User: *flUser,
+ // TODO(vishh): Expose 'Privileged' once it is supported.
+ // + //Privileged: job.GetenvBool("Privileged"),
+ Tty: *flTty,
+ Cmd: execCmd,
+ Container: container,
+ Detach: *flDetach,
}
// If -d is not set, attach to everything by default
diff --git a/runconfig/fixtures/container_config_1_14.json b/runconfig/fixtures/container_config_1_14.json
new file mode 100644
index 000000000..b08334c09
--- /dev/null
+++ b/runconfig/fixtures/container_config_1_14.json
@@ -0,0 +1,30 @@
+{
+ "Hostname":"",
+ "Domainname": "",
+ "User":"",
+ "Memory": 1000,
+ "MemorySwap":0,
+ "CpuShares": 512,
+ "Cpuset": "0,1",
+ "AttachStdin":false,
+ "AttachStdout":true,
+ "AttachStderr":true,
+ "PortSpecs":null,
+ "Tty":false,
+ "OpenStdin":false,
+ "StdinOnce":false,
+ "Env":null,
+ "Cmd":[
+ "bash"
+ ],
+ "Image":"ubuntu",
+ "Volumes":{
+ "/tmp": {}
+ },
+ "WorkingDir":"",
+ "NetworkDisabled": false,
+ "ExposedPorts":{
+ "22/tcp": {}
+ },
+ "RestartPolicy": { "Name": "always" }
+}
diff --git a/runconfig/fixtures/container_config_1_17.json b/runconfig/fixtures/container_config_1_17.json
new file mode 100644
index 000000000..60fc6e25e
--- /dev/null
+++ b/runconfig/fixtures/container_config_1_17.json
@@ -0,0 +1,49 @@
+{
+ "Hostname": "",
+ "Domainname": "",
+ "User": "",
+ "Memory": 1000,
+ "MemorySwap": 0,
+ "CpuShares": 512,
+ "Cpuset": "0,1",
+ "AttachStdin": false,
+ "AttachStdout": true,
+ "AttachStderr": true,
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": null,
+ "Cmd": [
+ "date"
+ ],
+ "Entrypoint": "bash",
+ "Image": "ubuntu",
+ "Volumes": {
+ "/tmp": {}
+ },
+ "WorkingDir": "",
+ "NetworkDisabled": false,
+ "MacAddress": "12:34:56:78:9a:bc",
+ "ExposedPorts": {
+ "22/tcp": {}
+ },
+ "SecurityOpt": [""],
+ "HostConfig": {
+ "Binds": ["/tmp:/tmp"],
+ "Links": ["redis3:redis"],
+ "LxcConf": {"lxc.utsname":"docker"},
+ "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+ "PublishAllPorts": false,
+ "Privileged": false,
+ "ReadonlyRootfs": false,
+ "Dns": ["8.8.8.8"],
+ "DnsSearch": [""],
+ "ExtraHosts": null,
+ "VolumesFrom": ["parent", "other:ro"],
+ "CapAdd": ["NET_ADMIN"],
+ "CapDrop": ["MKNOD"],
+ "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+ "NetworkMode": "bridge",
+ "Devices": []
+ }
+}
diff --git a/runconfig/fixtures/container_config_1_19.json b/runconfig/fixtures/container_config_1_19.json
new file mode 100644
index 000000000..9a3ce205b
--- /dev/null
+++ b/runconfig/fixtures/container_config_1_19.json
@@ -0,0 +1,57 @@
+{
+ "Hostname": "",
+ "Domainname": "",
+ "User": "",
+ "AttachStdin": false,
+ "AttachStdout": true,
+ "AttachStderr": true,
+ "Tty": false,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "Env": null,
+ "Cmd": [
+ "date"
+ ],
+ "Entrypoint": "bash",
+ "Image": "ubuntu",
+ "Labels": {
+ "com.example.vendor": "Acme",
+ "com.example.license": "GPL",
+ "com.example.version": "1.0"
+ },
+ "Volumes": {
+ "/tmp": {}
+ },
+ "WorkingDir": "",
+ "NetworkDisabled": false,
+ "MacAddress": "12:34:56:78:9a:bc",
+ "ExposedPorts": {
+ "22/tcp": {}
+ },
+ "HostConfig": {
+ "Binds": ["/tmp:/tmp"],
+ "Links": ["redis3:redis"],
+ "LxcConf": {"lxc.utsname":"docker"},
+ "Memory": 1000,
+ "MemorySwap": 0,
+ "CpuShares": 512,
+ "CpusetCpus": "0,1",
+ "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+ "PublishAllPorts": false,
+ "Privileged": false,
+ "ReadonlyRootfs": false,
+ "Dns": ["8.8.8.8"],
+ "DnsSearch": [""],
+ "ExtraHosts": null,
+ "VolumesFrom": ["parent", "other:ro"],
+ "CapAdd": ["NET_ADMIN"],
+ "CapDrop": ["MKNOD"],
+ "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+ "NetworkMode": "bridge",
+ "Devices": [],
+ "Ulimits": [{}],
+ "LogConfig": { "Type": "json-file", "Config": {} },
+ "SecurityOpt": [""],
+ "CgroupParent": ""
+ }
+}
diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go
index 84d636b5c..1418dea4a 100644
--- a/runconfig/hostconfig.go
+++ b/runconfig/hostconfig.go
@@ -1,19 +1,28 @@
package runconfig
import (
+ "encoding/json"
+ "io"
"strings"
- "github.com/docker/docker/engine"
"github.com/docker/docker/nat"
"github.com/docker/docker/pkg/ulimit"
- "github.com/docker/docker/utils"
)
+type KeyValuePair struct {
+ Key string
+ Value string
+}
+
type NetworkMode string
// IsPrivate indicates whether container use it's private network stack
func (n NetworkMode) IsPrivate() bool {
- return !(n.IsHost() || n.IsContainer() || n.IsNone())
+ return !(n.IsHost() || n.IsContainer())
+}
+
+func (n NetworkMode) IsBridge() bool {
+ return n == "bridge"
}
func (n NetworkMode) IsHost() bool {
@@ -67,6 +76,27 @@ func (n IpcMode) Container() string {
return ""
}
+type UTSMode string
+
+// IsPrivate indicates whether container use it's private UTS namespace
+func (n UTSMode) IsPrivate() bool {
+ return !(n.IsHost())
+}
+
+func (n UTSMode) IsHost() bool {
+ return n == "host"
+}
+
+func (n UTSMode) Valid() bool {
+ parts := strings.Split(string(n), ":")
+ switch mode := parts[0]; mode {
+ case "", "host":
+ default:
+ return false
+ }
+ return true
+}
+
type PidMode string
// IsPrivate indicates whether container use it's private pid stack
@@ -99,19 +129,85 @@ type RestartPolicy struct {
MaximumRetryCount int
}
+func (rp *RestartPolicy) IsNone() bool {
+ return rp.Name == "no"
+}
+
+func (rp *RestartPolicy) IsAlways() bool {
+ return rp.Name == "always"
+}
+
+func (rp *RestartPolicy) IsOnFailure() bool {
+ return rp.Name == "on-failure"
+}
+
type LogConfig struct {
Type string
Config map[string]string
}
+type LxcConfig struct {
+ values []KeyValuePair
+}
+
+func (c *LxcConfig) MarshalJSON() ([]byte, error) {
+ if c == nil {
+ return []byte{}, nil
+ }
+ return json.Marshal(c.Slice())
+}
+
+func (c *LxcConfig) UnmarshalJSON(b []byte) error {
+ if len(b) == 0 {
+ return nil
+ }
+
+ var kv []KeyValuePair
+ if err := json.Unmarshal(b, &kv); err != nil {
+ var h map[string]string
+ if err := json.Unmarshal(b, &h); err != nil {
+ return err
+ }
+ for k, v := range h {
+ kv = append(kv, KeyValuePair{k, v})
+ }
+ }
+ c.values = kv
+
+ return nil
+}
+
+func (c *LxcConfig) Len() int {
+ if c == nil {
+ return 0
+ }
+ return len(c.values)
+}
+
+func (c *LxcConfig) Slice() []KeyValuePair {
+ if c == nil {
+ return nil
+ }
+ return c.values
+}
+
+func NewLxcConfig(values []KeyValuePair) *LxcConfig {
+ return &LxcConfig{values}
+}
+
type HostConfig struct {
Binds []string
ContainerIDFile string
- LxcConf []utils.KeyValuePair
- Memory int64 // Memory limit (in bytes)
- MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap
- CpuShares int64 // CPU shares (relative weight vs. other containers)
+ LxcConf *LxcConfig
+ Memory int64 // Memory limit (in bytes)
+ MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap
+ CpuShares int64 // CPU shares (relative weight vs. other containers)
+ CpuPeriod int64
CpusetCpus string // CpusetCpus 0-2, 0,1
+ CpusetMems string // CpusetMems 0-2, 0,1
+ CpuQuota int64
+ BlkioWeight int64 // Block IO weight (relative weight vs. other containers)
+ OomKillDisable bool // Whether to disable OOM Killer or not
Privileged bool
PortBindings nat.PortMap
Links []string
@@ -124,6 +220,7 @@ type HostConfig struct {
NetworkMode NetworkMode
IpcMode IpcMode
PidMode PidMode
+ UTSMode UTSMode
CapAdd []string
CapDrop []string
RestartPolicy RestartPolicy
@@ -134,96 +231,55 @@ type HostConfig struct {
CgroupParent string // Parent cgroup.
}
-// 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 MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper {
+ return &ContainerConfigWrapper{
+ config,
+ &hostConfigWrapper{InnerHostConfig: hostConfig},
}
}
-func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
- if job.EnvExists("HostConfig") {
- hostConfig := HostConfig{}
- job.GetenvJson("HostConfig", &hostConfig)
+type hostConfigWrapper struct {
+ InnerHostConfig *HostConfig `json:"HostConfig,omitempty"`
+ Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
- // FIXME: These are for backward compatibility, if people use these
- // options with `HostConfig`, we should still make them workable.
- if job.EnvExists("Memory") && hostConfig.Memory == 0 {
- hostConfig.Memory = job.GetenvInt64("Memory")
+ *HostConfig // Deprecated. Exported to read attrubutes from json that are not in the inner host config structure.
+}
+
+func (w hostConfigWrapper) GetHostConfig() *HostConfig {
+ hc := w.HostConfig
+
+ if hc == nil && w.InnerHostConfig != nil {
+ hc = w.InnerHostConfig
+ } else if w.InnerHostConfig != nil {
+ if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 {
+ w.InnerHostConfig.Memory = hc.Memory
}
- if job.EnvExists("MemorySwap") && hostConfig.MemorySwap == 0 {
- hostConfig.MemorySwap = job.GetenvInt64("MemorySwap")
+ if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 {
+ w.InnerHostConfig.MemorySwap = hc.MemorySwap
}
- if job.EnvExists("CpuShares") && hostConfig.CpuShares == 0 {
- hostConfig.CpuShares = job.GetenvInt64("CpuShares")
+ if hc.CpuShares != 0 && w.InnerHostConfig.CpuShares == 0 {
+ w.InnerHostConfig.CpuShares = hc.CpuShares
}
- if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" {
- hostConfig.CpusetCpus = job.Getenv("Cpuset")
- }
- return &hostConfig
+ hc = w.InnerHostConfig
}
- hostConfig := &HostConfig{
- ContainerIDFile: job.Getenv("ContainerIDFile"),
- Memory: job.GetenvInt64("Memory"),
- MemorySwap: job.GetenvInt64("MemorySwap"),
- CpuShares: job.GetenvInt64("CpuShares"),
- CpusetCpus: job.Getenv("CpusetCpus"),
- Privileged: job.GetenvBool("Privileged"),
- PublishAllPorts: job.GetenvBool("PublishAllPorts"),
- NetworkMode: NetworkMode(job.Getenv("NetworkMode")),
- IpcMode: IpcMode(job.Getenv("IpcMode")),
- PidMode: PidMode(job.Getenv("PidMode")),
- ReadonlyRootfs: job.GetenvBool("ReadonlyRootfs"),
- CgroupParent: job.Getenv("CgroupParent"),
+ if hc != nil && w.Cpuset != "" && hc.CpusetCpus == "" {
+ hc.CpusetCpus = w.Cpuset
}
+
+ return hc
+}
- // FIXME: This is for backward compatibility, if people use `Cpuset`
- // in json, make it workable, we will only pass hostConfig.CpusetCpus
- // to execDriver.
- if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" {
- hostConfig.CpusetCpus = job.Getenv("Cpuset")
+func DecodeHostConfig(src io.Reader) (*HostConfig, error) {
+ decoder := json.NewDecoder(src)
+
+ var w hostConfigWrapper
+ if err := decoder.Decode(&w); err != nil {
+ return nil, err
}
- job.GetenvJson("LxcConf", &hostConfig.LxcConf)
- job.GetenvJson("PortBindings", &hostConfig.PortBindings)
- job.GetenvJson("Devices", &hostConfig.Devices)
- job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
- job.GetenvJson("Ulimits", &hostConfig.Ulimits)
- job.GetenvJson("LogConfig", &hostConfig.LogConfig)
- hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
- if Binds := job.GetenvList("Binds"); Binds != nil {
- hostConfig.Binds = Binds
- }
- if Links := job.GetenvList("Links"); Links != nil {
- hostConfig.Links = Links
- }
- if Dns := job.GetenvList("Dns"); Dns != nil {
- hostConfig.Dns = Dns
- }
- if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil {
- hostConfig.DnsSearch = DnsSearch
- }
- if ExtraHosts := job.GetenvList("ExtraHosts"); ExtraHosts != nil {
- hostConfig.ExtraHosts = ExtraHosts
- }
- if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil {
- hostConfig.VolumesFrom = VolumesFrom
- }
- if CapAdd := job.GetenvList("CapAdd"); CapAdd != nil {
- hostConfig.CapAdd = CapAdd
- }
- if CapDrop := job.GetenvList("CapDrop"); CapDrop != nil {
- hostConfig.CapDrop = CapDrop
- }
+ hc := w.GetHostConfig()
- return hostConfig
+ return hc, nil
}
diff --git a/runconfig/merge.go b/runconfig/merge.go
index 9bbdc6ad2..9c9a3b436 100644
--- a/runconfig/merge.go
+++ b/runconfig/merge.go
@@ -3,7 +3,7 @@ package runconfig
import (
"strings"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/nat"
)
@@ -11,15 +11,6 @@ func Merge(userConf, imageConf *Config) error {
if userConf.User == "" {
userConf.User = imageConf.User
}
- if userConf.Memory == 0 {
- userConf.Memory = imageConf.Memory
- }
- if userConf.MemorySwap == 0 {
- userConf.MemorySwap = imageConf.MemorySwap
- }
- if userConf.CpuShares == 0 {
- userConf.CpuShares = imageConf.CpuShares
- }
if len(userConf.ExposedPorts) == 0 {
userConf.ExposedPorts = imageConf.ExposedPorts
} else if imageConf.ExposedPorts != nil {
@@ -50,7 +41,7 @@ func Merge(userConf, imageConf *Config) error {
}
if len(imageConf.PortSpecs) > 0 {
// FIXME: I think we can safely remove this. Leaving it for now for the sake of reverse-compat paranoia.
- log.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", "))
+ logrus.Debugf("Migrating image port specs to container: %s", strings.Join(imageConf.PortSpecs, ", "))
if userConf.ExposedPorts == nil {
userConf.ExposedPorts = make(nat.PortSet)
}
@@ -94,8 +85,8 @@ func Merge(userConf, imageConf *Config) error {
userConf.Labels = imageConf.Labels
}
- if len(userConf.Entrypoint) == 0 {
- if len(userConf.Cmd) == 0 {
+ if userConf.Entrypoint.Len() == 0 {
+ if userConf.Cmd.Len() == 0 {
userConf.Cmd = imageConf.Cmd
}
diff --git a/runconfig/parse.go b/runconfig/parse.go
index ccd8056cf..46ec26785 100644
--- a/runconfig/parse.go
+++ b/runconfig/parse.go
@@ -2,7 +2,6 @@ package runconfig
import (
"fmt"
- "path"
"strconv"
"strings"
@@ -12,16 +11,15 @@ import (
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/pkg/units"
- "github.com/docker/docker/utils"
)
var (
- 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.")
+ ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior")
+ ErrConflictNetworkAndDns = fmt.Errorf("Conflicting options: --dns and the network mode (--net)")
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.")
+ ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: --net=host can't be used with links. This would result in undefined behavior")
+ ErrConflictContainerNetworkAndMac = fmt.Errorf("Conflicting options: --mac-address and the network mode (--net)")
+ ErrConflictNetworkHosts = fmt.Errorf("Conflicting options: --add-host and the network mode (--net)")
)
func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSet, error) {
@@ -49,13 +47,16 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
flCapDrop = opts.NewListOpts(nil)
flSecurityOpt = opts.NewListOpts(nil)
flLabelsFile = opts.NewListOpts(nil)
+ flLoggingOpts = opts.NewListOpts(nil)
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")
flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use")
+ flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
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")
+ flOomKillDisable = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
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")
@@ -64,7 +65,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
+ flCpuPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
+ flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
+ flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS quota")
+ flBlkioWeight = cmd.Int64([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container")
flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
@@ -93,16 +98,49 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
+ cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
+
+ expFlags := attachExperimentalFlags(cmd)
cmd.Require(flag.Min, 1)
- if err := utils.ParseFlags(cmd, args, true); err != nil {
+ if err := cmd.ParseFlags(args, true); err != nil {
return nil, nil, cmd, err
}
- // Validate input params
- if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
- return nil, nil, cmd, ErrInvalidWorkingDirectory
+ var (
+ attachStdin = flAttach.Get("stdin")
+ attachStdout = flAttach.Get("stdout")
+ attachStderr = flAttach.Get("stderr")
+ )
+
+ netMode, err := parseNetMode(*flNetMode)
+ if err != nil {
+ return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err)
+ }
+
+ if (netMode.IsHost() || netMode.IsContainer()) && *flHostname != "" {
+ return nil, nil, cmd, ErrConflictNetworkHostname
+ }
+
+ if netMode.IsHost() && flLinks.Len() > 0 {
+ return nil, nil, cmd, ErrConflictHostNetworkAndLinks
+ }
+
+ if netMode.IsContainer() && flLinks.Len() > 0 {
+ return nil, nil, cmd, ErrConflictContainerNetworkAndLinks
+ }
+
+ if (netMode.IsHost() || netMode.IsContainer()) && flDns.Len() > 0 {
+ return nil, nil, cmd, ErrConflictNetworkAndDns
+ }
+
+ if (netMode.IsContainer() || netMode.IsHost()) && flExtraHosts.Len() > 0 {
+ return nil, nil, cmd, ErrConflictNetworkHosts
+ }
+
+ if (netMode.IsContainer() || netMode.IsHost()) && *flMacAddress != "" {
+ return nil, nil, cmd, ErrConflictContainerNetworkAndMac
}
// Validate the input mac address
@@ -111,31 +149,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
}
}
- var (
- attachStdin = flAttach.Get("stdin")
- attachStdout = flAttach.Get("stdout")
- attachStderr = flAttach.Get("stderr")
- )
-
- if *flNetMode != "bridge" && *flNetMode != "none" && *flHostname != "" {
- return nil, nil, cmd, ErrConflictNetworkHostname
- }
-
- if *flNetMode == "host" && flLinks.Len() > 0 {
- return nil, nil, cmd, ErrConflictHostNetworkAndLinks
- }
-
- 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
- }
// If neither -d or -a are set, attach to everything by default
if flAttach.Len() == 0 {
@@ -186,21 +199,22 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
var (
parsedArgs = cmd.Args()
- runCmd []string
- entrypoint []string
+ runCmd *Command
+ entrypoint *Entrypoint
image = cmd.Arg(0)
)
if len(parsedArgs) > 1 {
- runCmd = parsedArgs[1:]
+ runCmd = NewCommand(parsedArgs[1:]...)
}
if *flEntrypoint != "" {
- entrypoint = []string{*flEntrypoint}
+ entrypoint = NewEntrypoint(*flEntrypoint)
}
- lxcConf, err := parseKeyValueOpts(flLxcOpts)
+ lc, err := parseKeyValueOpts(flLxcOpts)
if err != nil {
return nil, nil, cmd, err
}
+ lxcConf := NewLxcConfig(lc)
var (
domainname string
@@ -270,12 +284,17 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
}
- netMode, err := parseNetMode(*flNetMode)
- if err != nil {
- return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err)
+ utsMode := UTSMode(*flUTSMode)
+ if !utsMode.Valid() {
+ return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
}
- restartPolicy, err := parseRestartPolicy(*flRestartPolicy)
+ restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
+ if err != nil {
+ return nil, nil, cmd, err
+ }
+
+ loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
if err != nil {
return nil, nil, cmd, err
}
@@ -289,10 +308,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
Tty: *flTty,
NetworkDisabled: !*flNetwork,
OpenStdin: *flStdin,
- Memory: flMemory, // FIXME: for backward compatibility
- MemorySwap: MemorySwap, // FIXME: for backward compatibility
- CpuShares: *flCpuShares, // FIXME: for backward compatibility
- Cpuset: *flCpusetCpus, // FIXME: for backward compatibility
AttachStdin: attachStdin,
AttachStdout: attachStdout,
AttachStderr: attachStderr,
@@ -313,7 +328,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
Memory: flMemory,
MemorySwap: MemorySwap,
CpuShares: *flCpuShares,
+ CpuPeriod: *flCpuPeriod,
CpusetCpus: *flCpusetCpus,
+ CpusetMems: *flCpusetMems,
+ CpuQuota: *flCpuQuota,
+ BlkioWeight: *flBlkioWeight,
+ OomKillDisable: *flOomKillDisable,
Privileged: *flPrivileged,
PortBindings: portBindings,
Links: flLinks.GetAll(),
@@ -325,6 +345,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
NetworkMode: netMode,
IpcMode: ipcMode,
PidMode: pidMode,
+ UTSMode: utsMode,
Devices: deviceMappings,
CapAdd: flCapAdd.GetAll(),
CapDrop: flCapDrop.GetAll(),
@@ -332,10 +353,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
SecurityOpt: flSecurityOpt.GetAll(),
ReadonlyRootfs: *flReadonlyRootfs,
Ulimits: flUlimits.GetList(),
- LogConfig: LogConfig{Type: *flLoggingDriver},
+ LogConfig: LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
CgroupParent: *flCgroupParent,
}
+ applyExperimentalFlags(expFlags, config, hostConfig)
+
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
@@ -374,8 +397,17 @@ func convertKVStringsToMap(values []string) map[string]string {
return result
}
-// parseRestartPolicy returns the parsed policy or an error indicating what is incorrect
-func parseRestartPolicy(policy string) (RestartPolicy, error) {
+func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
+ loggingOptsMap := convertKVStringsToMap(loggingOpts)
+ if loggingDriver == "none" && len(loggingOpts) > 0 {
+ return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
+ }
+ //TODO - validation step
+ return loggingOptsMap, nil
+}
+
+// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
+func ParseRestartPolicy(policy string) (RestartPolicy, error) {
p := RestartPolicy{}
if policy == "" {
@@ -430,14 +462,14 @@ func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) {
return out, nil
}
-func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) {
- out := make([]utils.KeyValuePair, opts.Len())
+func parseKeyValueOpts(opts opts.ListOpts) ([]KeyValuePair, error) {
+ out := make([]KeyValuePair, opts.Len())
for i, o := range opts.GetAll() {
k, v, err := parsers.ParseKeyValueOpt(o)
if err != nil {
return nil, err
}
- out[i] = utils.KeyValuePair{Key: k, Value: v}
+ out[i] = KeyValuePair{Key: k, Value: v}
}
return out, nil
}
diff --git a/runconfig/parse_experimental.go b/runconfig/parse_experimental.go
new file mode 100644
index 000000000..886b377fa
--- /dev/null
+++ b/runconfig/parse_experimental.go
@@ -0,0 +1,19 @@
+// +build experimental
+
+package runconfig
+
+import flag "github.com/docker/docker/pkg/mflag"
+
+type experimentalFlags struct {
+ flags map[string]interface{}
+}
+
+func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
+ flags := make(map[string]interface{})
+ flags["volume-driver"] = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
+ return &experimentalFlags{flags: flags}
+}
+
+func applyExperimentalFlags(exp *experimentalFlags, config *Config, hostConfig *HostConfig) {
+ config.VolumeDriver = *(exp.flags["volume-driver"]).(*string)
+}
diff --git a/runconfig/parse_stub.go b/runconfig/parse_stub.go
new file mode 100644
index 000000000..391b6ed43
--- /dev/null
+++ b/runconfig/parse_stub.go
@@ -0,0 +1,14 @@
+// +build !experimental
+
+package runconfig
+
+import flag "github.com/docker/docker/pkg/mflag"
+
+type experimentalFlags struct{}
+
+func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
+ return nil
+}
+
+func applyExperimentalFlags(flags *experimentalFlags, config *Config, hostConfig *HostConfig) {
+}
diff --git a/runconfig/parse_test.go b/runconfig/parse_test.go
index cd90dc3a9..6c0a1cfc6 100644
--- a/runconfig/parse_test.go
+++ b/runconfig/parse_test.go
@@ -57,3 +57,9 @@ func TestNetHostname(t *testing.T) {
t.Fatalf("Expected error ErrConflictNetworkHostname, got: %s", err)
}
}
+
+func TestConflictContainerNetworkAndLinks(t *testing.T) {
+ if _, _, _, err := parseRun([]string{"--net=container:other", "--link=zip:zap", "img", "cmd"}); err != ErrConflictContainerNetworkAndLinks {
+ t.Fatalf("Expected error ErrConflictContainerNetworkAndLinks, got: %s", err)
+ }
+}
diff --git a/trust/service.go b/trust/service.go
index 324a478f1..6a804faf5 100644
--- a/trust/service.go
+++ b/trust/service.go
@@ -4,71 +4,50 @@ import (
"fmt"
"time"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
"github.com/docker/libtrust"
)
-func (t *TrustStore) Install(eng *engine.Engine) error {
- for name, handler := range map[string]engine.Handler{
- "trust_key_check": t.CmdCheckKey,
- "trust_update_base": t.CmdUpdateBase,
- } {
- if err := eng.Register(name, handler); err != nil {
- return fmt.Errorf("Could not register %q: %v", name, err)
- }
- }
- return nil
+type NotVerifiedError string
+
+func (e NotVerifiedError) Error() string {
+ return string(e)
}
-func (t *TrustStore) CmdCheckKey(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.Errorf("Usage: %s NAMESPACE", job.Name)
+func (t *TrustStore) CheckKey(ns string, key []byte, perm uint16) (bool, error) {
+ if len(key) == 0 {
+ return false, fmt.Errorf("Missing PublicKey")
}
- var (
- namespace = job.Args[0]
- keyBytes = job.Getenv("PublicKey")
- )
-
- if keyBytes == "" {
- return job.Errorf("Missing PublicKey")
- }
- pk, err := libtrust.UnmarshalPublicKeyJWK([]byte(keyBytes))
+ pk, err := libtrust.UnmarshalPublicKeyJWK(key)
if err != nil {
- return job.Errorf("Error unmarshalling public key: %s", err)
+ return false, fmt.Errorf("Error unmarshalling public key: %v", err)
}
- permission := uint16(job.GetenvInt("Permission"))
- if permission == 0 {
- permission = 0x03
+ if perm == 0 {
+ perm = 0x03
}
t.RLock()
defer t.RUnlock()
if t.graph == nil {
- job.Stdout.Write([]byte("no graph"))
- return engine.StatusOK
+ return false, NotVerifiedError("no graph")
}
// Check if any expired grants
- verified, err := t.graph.Verify(pk, namespace, permission)
+ verified, err := t.graph.Verify(pk, ns, perm)
if err != nil {
- return job.Errorf("Error verifying key to namespace: %s", namespace)
+ return false, fmt.Errorf("Error verifying key to namespace: %s", ns)
}
if !verified {
- log.Debugf("Verification failed for %s using key %s", namespace, pk.KeyID())
- job.Stdout.Write([]byte("not verified"))
- } else if t.expiration.Before(time.Now()) {
- job.Stdout.Write([]byte("expired"))
- } else {
- job.Stdout.Write([]byte("verified"))
+ logrus.Debugf("Verification failed for %s using key %s", ns, pk.KeyID())
+ return false, NotVerifiedError("not verified")
}
-
- return engine.StatusOK
+ if t.expiration.Before(time.Now()) {
+ return false, NotVerifiedError("expired")
+ }
+ return true, nil
}
-func (t *TrustStore) CmdUpdateBase(job *engine.Job) engine.Status {
+func (t *TrustStore) UpdateBase() {
t.fetch()
-
- return engine.StatusOK
}
diff --git a/trust/trusts.go b/trust/trusts.go
index f5e317e9e..885127ee5 100644
--- a/trust/trusts.go
+++ b/trust/trusts.go
@@ -12,7 +12,7 @@ import (
"sync"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/libtrust/trustgraph"
)
@@ -62,8 +62,7 @@ func NewTrustStore(path string) (*TrustStore, error) {
baseEndpoints: endpoints,
}
- err = t.reload()
- if err != nil {
+ if err := t.reload(); err != nil {
return nil, err
}
@@ -93,7 +92,7 @@ func (t *TrustStore) reload() error {
}
if len(statements) == 0 {
if t.autofetch {
- log.Debugf("No grants, fetching")
+ logrus.Debugf("No grants, fetching")
t.fetcher = time.AfterFunc(t.fetchTime, t.fetch)
}
return nil
@@ -106,7 +105,7 @@ func (t *TrustStore) reload() error {
t.expiration = expiration
t.graph = trustgraph.NewMemoryGraph(grants)
- log.Debugf("Reloaded graph with %d grants expiring at %s", len(grants), expiration)
+ logrus.Debugf("Reloaded graph with %d grants expiring at %s", len(grants), expiration)
if t.autofetch {
nextFetch := expiration.Sub(time.Now())
@@ -161,28 +160,26 @@ func (t *TrustStore) fetch() {
for bg, ep := range t.baseEndpoints {
statement, err := t.fetchBaseGraph(ep)
if err != nil {
- log.Infof("Trust graph fetch failed: %s", err)
+ logrus.Infof("Trust graph fetch failed: %s", err)
continue
}
b, err := statement.Bytes()
if err != nil {
- log.Infof("Bad trust graph statement: %s", err)
+ logrus.Infof("Bad trust graph statement: %s", err)
continue
}
// TODO check if value differs
- err = ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600)
- if err != nil {
- log.Infof("Error writing trust graph statement: %s", err)
+ if err := ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600); err != nil {
+ logrus.Infof("Error writing trust graph statement: %s", err)
}
fetchCount++
}
- log.Debugf("Fetched %d base graphs at %s", fetchCount, time.Now())
+ logrus.Debugf("Fetched %d base graphs at %s", fetchCount, time.Now())
if fetchCount > 0 {
go func() {
- err := t.reload()
- if err != nil {
- log.Infof("Reload of trust graph failed: %s", err)
+ if err := t.reload(); err != nil {
+ logrus.Infof("Reload of trust graph failed: %s", err)
}
}()
t.fetchTime = defaultFetchtime
diff --git a/utils/daemon.go b/utils/daemon.go
deleted file mode 100644
index 871122ed5..000000000
--- a/utils/daemon.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package utils
-
-import (
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "strconv"
-)
-
-func CreatePidFile(pidfile string) error {
- if pidString, err := ioutil.ReadFile(pidfile); err == nil {
- pid, err := strconv.Atoi(string(pidString))
- if err == nil {
- if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil {
- return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
- }
- }
- }
-
- file, err := os.Create(pidfile)
- if err != nil {
- return err
- }
-
- defer file.Close()
-
- _, err = fmt.Fprintf(file, "%d", os.Getpid())
- return err
-}
-
-func RemovePidFile(pidfile string) {
- if err := os.Remove(pidfile); err != nil {
- log.Printf("Error removing %s: %s", pidfile, err)
- }
-}
diff --git a/utils/experimental.go b/utils/experimental.go
new file mode 100644
index 000000000..b308a59fa
--- /dev/null
+++ b/utils/experimental.go
@@ -0,0 +1,7 @@
+// +build experimental
+
+package utils
+
+func ExperimentalBuild() bool {
+ return true
+}
diff --git a/utils/flags.go b/utils/flags.go
deleted file mode 100644
index 33c72279b..000000000
--- a/utils/flags.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package utils
-
-import (
- "fmt"
- "os"
-
- flag "github.com/docker/docker/pkg/mflag"
-)
-
-// ParseFlags is a utility function that adds a help flag if withHelp is true,
-// calls cmd.Parse(args) and prints a relevant error message if there are
-// incorrect number of arguments. It returns error only if error handling is
-// set to ContinueOnError and parsing fails. If error handling is set to
-// ExitOnError, it's safe to ignore the return value.
-// TODO: move this to a better package than utils
-func ParseFlags(cmd *flag.FlagSet, args []string, withHelp bool) error {
- var help *bool
- if withHelp {
- help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage")
- }
- if err := cmd.Parse(args); err != nil {
- return err
- }
- if help != nil && *help {
- cmd.Usage()
- // just in case Usage does not exit
- os.Exit(0)
- }
- if str := cmd.CheckArgs(); str != "" {
- ReportError(cmd, str, withHelp)
- }
- return nil
-}
-
-func ReportError(cmd *flag.FlagSet, str string, withHelp bool) {
- if withHelp {
- if os.Args[0] == cmd.Name() {
- str += ". See '" + os.Args[0] + " --help'"
- } else {
- str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'"
- }
- }
- fmt.Fprintf(cmd.Out(), "docker: %s.\n", str)
- os.Exit(1)
-}
diff --git a/utils/git.go b/utils/git.go
new file mode 100644
index 000000000..ce8924d8a
--- /dev/null
+++ b/utils/git.go
@@ -0,0 +1,93 @@
+package utils
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/docker/docker/pkg/urlutil"
+)
+
+func GitClone(remoteURL string) (string, error) {
+ if !urlutil.IsGitTransport(remoteURL) {
+ remoteURL = "https://" + remoteURL
+ }
+ root, err := ioutil.TempDir("", "docker-build-git")
+ if err != nil {
+ return "", err
+ }
+
+ u, err := url.Parse(remoteURL)
+ if err != nil {
+ return "", err
+ }
+
+ fragment := u.Fragment
+ clone := cloneArgs(u, root)
+
+ if output, err := git(clone...); err != nil {
+ return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output)
+ }
+
+ return checkoutGit(fragment, root)
+}
+
+func cloneArgs(remoteURL *url.URL, root string) []string {
+ args := []string{"clone", "--recursive"}
+ shallow := len(remoteURL.Fragment) == 0
+
+ if shallow && strings.HasPrefix(remoteURL.Scheme, "http") {
+ res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL))
+ if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" {
+ shallow = false
+ }
+ }
+
+ if shallow {
+ args = append(args, "--depth", "1")
+ }
+
+ if remoteURL.Fragment != "" {
+ remoteURL.Fragment = ""
+ }
+
+ return append(args, remoteURL.String(), root)
+}
+
+func checkoutGit(fragment, root string) (string, error) {
+ refAndDir := strings.SplitN(fragment, ":", 2)
+
+ if len(refAndDir[0]) != 0 {
+ if output, err := gitWithinDir(root, "checkout", refAndDir[0]); err != nil {
+ return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output)
+ }
+ }
+
+ if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
+ newCtx := filepath.Join(root, refAndDir[1])
+ fi, err := os.Stat(newCtx)
+ if err != nil {
+ return "", err
+ }
+ if !fi.IsDir() {
+ return "", fmt.Errorf("Error setting git context, not a directory: %s", newCtx)
+ }
+ root = newCtx
+ }
+
+ return root, nil
+}
+
+func gitWithinDir(dir string, args ...string) ([]byte, error) {
+ a := []string{"--work-tree", dir, "--git-dir", filepath.Join(dir, ".git")}
+ return git(append(a, args...)...)
+}
+
+func git(args ...string) ([]byte, error) {
+ return exec.Command("git", args...).CombinedOutput()
+}
diff --git a/utils/git_test.go b/utils/git_test.go
new file mode 100644
index 000000000..10b13e962
--- /dev/null
+++ b/utils/git_test.go
@@ -0,0 +1,175 @@
+package utils
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+func TestCloneArgsSmartHttp(t *testing.T) {
+ mux := http.NewServeMux()
+ server := httptest.NewServer(mux)
+ serverURL, _ := url.Parse(server.URL)
+
+ serverURL.Path = "/repo.git"
+ gitURL := serverURL.String()
+
+ mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) {
+ q := r.URL.Query().Get("service")
+ w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q))
+ })
+
+ args := cloneArgs(serverURL, "/tmp")
+ exp := []string{"clone", "--recursive", "--depth", "1", gitURL, "/tmp"}
+ if !reflect.DeepEqual(args, exp) {
+ t.Fatalf("Expected %v, got %v", exp, args)
+ }
+}
+
+func TestCloneArgsDumbHttp(t *testing.T) {
+ mux := http.NewServeMux()
+ server := httptest.NewServer(mux)
+ serverURL, _ := url.Parse(server.URL)
+
+ serverURL.Path = "/repo.git"
+ gitURL := serverURL.String()
+
+ mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ })
+
+ args := cloneArgs(serverURL, "/tmp")
+ exp := []string{"clone", "--recursive", gitURL, "/tmp"}
+ if !reflect.DeepEqual(args, exp) {
+ t.Fatalf("Expected %v, got %v", exp, args)
+ }
+}
+
+func TestCloneArgsGit(t *testing.T) {
+ u, _ := url.Parse("git://github.com/docker/docker")
+ args := cloneArgs(u, "/tmp")
+ exp := []string{"clone", "--recursive", "--depth", "1", "git://github.com/docker/docker", "/tmp"}
+ if !reflect.DeepEqual(args, exp) {
+ t.Fatalf("Expected %v, got %v", exp, args)
+ }
+}
+
+func TestCloneArgsStripFragment(t *testing.T) {
+ u, _ := url.Parse("git://github.com/docker/docker#test")
+ args := cloneArgs(u, "/tmp")
+ exp := []string{"clone", "--recursive", "git://github.com/docker/docker", "/tmp"}
+ if !reflect.DeepEqual(args, exp) {
+ t.Fatalf("Expected %v, got %v", exp, args)
+ }
+}
+
+func TestCheckoutGit(t *testing.T) {
+ root, err := ioutil.TempDir("", "docker-build-git-checkout")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(root)
+
+ gitDir := filepath.Join(root, "repo")
+ _, err = git("init", gitDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "config", "user.email", "test@docker.com"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "config", "user.name", "Docker test"); err != nil {
+ t.Fatal(err)
+ }
+
+ if err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ subDir := filepath.Join(gitDir, "subdir")
+ if err = os.Mkdir(subDir, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ if err = ioutil.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 5000"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "add", "-A"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "commit", "-am", "First commit"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "checkout", "-b", "test"); err != nil {
+ t.Fatal(err)
+ }
+
+ if err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 3000"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ if err = ioutil.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM busybox\nEXPOSE 5000"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "add", "-A"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "commit", "-am", "Branch commit"); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = gitWithinDir(gitDir, "checkout", "master"); err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ frag string
+ exp string
+ fail bool
+ }{
+ {"", "FROM scratch", false},
+ {"master", "FROM scratch", false},
+ {":subdir", "FROM scratch\nEXPOSE 5000", false},
+ {":nosubdir", "", true}, // missing directory error
+ {":Dockerfile", "", true}, // not a directory error
+ {"master:nosubdir", "", true},
+ {"master:subdir", "FROM scratch\nEXPOSE 5000", false},
+ {"test", "FROM scratch\nEXPOSE 3000", false},
+ {"test:", "FROM scratch\nEXPOSE 3000", false},
+ {"test:subdir", "FROM busybox\nEXPOSE 5000", false},
+ }
+
+ for _, c := range cases {
+ r, err := checkoutGit(c.frag, gitDir)
+
+ fail := err != nil
+ if fail != c.fail {
+ t.Fatalf("Expected %v failure, error was %v\n", c.fail, err)
+ }
+ if c.fail {
+ continue
+ }
+
+ b, err := ioutil.ReadFile(filepath.Join(r, "Dockerfile"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if string(b) != c.exp {
+ t.Fatalf("Expected %v, was %v\n", c.exp, string(b))
+ }
+ }
+}
diff --git a/utils/http.go b/utils/http.go
deleted file mode 100644
index 24eaea56b..000000000
--- a/utils/http.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package utils
-
-import (
- "io"
- "net/http"
- "strings"
-
- log "github.com/Sirupsen/logrus"
-)
-
-// VersionInfo is used to model entities which has a version.
-// It is basically a tupple with name and version.
-type VersionInfo interface {
- Name() string
- Version() string
-}
-
-func validVersion(version VersionInfo) bool {
- const stopChars = " \t\r\n/"
- name := version.Name()
- vers := version.Version()
- if len(name) == 0 || strings.ContainsAny(name, stopChars) {
- return false
- }
- if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
- return false
- }
- return true
-}
-
-// Convert versions to a string and append the string to the string base.
-//
-// Each VersionInfo will be converted to a string in the format of
-// "product/version", where the "product" is get from the Name() method, while
-// version is get from the Version() method. Several pieces of verson information
-// will be concatinated and separated by space.
-func appendVersions(base string, versions ...VersionInfo) string {
- if len(versions) == 0 {
- return base
- }
-
- verstrs := make([]string, 0, 1+len(versions))
- if len(base) > 0 {
- verstrs = append(verstrs, base)
- }
-
- for _, v := range versions {
- if !validVersion(v) {
- continue
- }
- verstrs = append(verstrs, v.Name()+"/"+v.Version())
- }
- return strings.Join(verstrs, " ")
-}
-
-// HTTPRequestDecorator is used to change an instance of
-// http.Request. It could be used to add more header fields,
-// change body, etc.
-type HTTPRequestDecorator interface {
- // ChangeRequest() changes the request accordingly.
- // The changed request will be returned or err will be non-nil
- // if an error occur.
- ChangeRequest(req *http.Request) (newReq *http.Request, err error)
-}
-
-// HTTPUserAgentDecorator appends the product/version to the user agent field
-// of a request.
-type HTTPUserAgentDecorator struct {
- versions []VersionInfo
-}
-
-func NewHTTPUserAgentDecorator(versions ...VersionInfo) HTTPRequestDecorator {
- return &HTTPUserAgentDecorator{
- versions: versions,
- }
-}
-
-func (h *HTTPUserAgentDecorator) ChangeRequest(req *http.Request) (newReq *http.Request, err error) {
- if req == nil {
- return req, nil
- }
-
- userAgent := appendVersions(req.UserAgent(), h.versions...)
- if len(userAgent) > 0 {
- req.Header.Set("User-Agent", userAgent)
- }
- return req, nil
-}
-
-type HTTPMetaHeadersDecorator struct {
- Headers map[string][]string
-}
-
-func (h *HTTPMetaHeadersDecorator) ChangeRequest(req *http.Request) (newReq *http.Request, err error) {
- if h.Headers == nil {
- return req, nil
- }
- for k, v := range h.Headers {
- req.Header[k] = v
- }
- return req, nil
-}
-
-type HTTPAuthDecorator struct {
- login string
- password string
-}
-
-func NewHTTPAuthDecorator(login, password string) HTTPRequestDecorator {
- return &HTTPAuthDecorator{
- login: login,
- password: password,
- }
-}
-
-func (self *HTTPAuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
- req.SetBasicAuth(self.login, self.password)
- return req, nil
-}
-
-// HTTPRequestFactory creates an HTTP request
-// and applies a list of decorators on the request.
-type HTTPRequestFactory struct {
- decorators []HTTPRequestDecorator
-}
-
-func NewHTTPRequestFactory(d ...HTTPRequestDecorator) *HTTPRequestFactory {
- return &HTTPRequestFactory{
- decorators: d,
- }
-}
-
-func (self *HTTPRequestFactory) AddDecorator(d ...HTTPRequestDecorator) {
- self.decorators = append(self.decorators, d...)
-}
-
-func (self *HTTPRequestFactory) GetDecorators() []HTTPRequestDecorator {
- return self.decorators
-}
-
-// NewRequest() creates a new *http.Request,
-// applies all decorators in the HTTPRequestFactory on the request,
-// then applies decorators provided by d on the request.
-func (h *HTTPRequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...HTTPRequestDecorator) (*http.Request, error) {
- req, err := http.NewRequest(method, urlStr, body)
- if err != nil {
- return nil, err
- }
-
- // By default, a nil factory should work.
- if h == nil {
- return req, nil
- }
- for _, dec := range h.decorators {
- req, err = dec.ChangeRequest(req)
- if err != nil {
- return nil, err
- }
- }
- for _, dec := range d {
- req, err = dec.ChangeRequest(req)
- if err != nil {
- return nil, err
- }
- }
- log.Debugf("%v -- HEADERS: %v", req.URL, req.Header)
- return req, err
-}
diff --git a/utils/stubs.go b/utils/stubs.go
new file mode 100644
index 000000000..b376f0cfb
--- /dev/null
+++ b/utils/stubs.go
@@ -0,0 +1,7 @@
+// +build !experimental
+
+package utils
+
+func ExperimentalBuild() bool {
+ return false
+}
diff --git a/utils/tcp.go b/utils/tcp.go
new file mode 100644
index 000000000..75980ff69
--- /dev/null
+++ b/utils/tcp.go
@@ -0,0 +1,22 @@
+package utils
+
+import (
+ "net"
+ "net/http"
+ "time"
+)
+
+func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
+ // Why 32? See https://github.com/docker/docker/pull/8035.
+ timeout := 32 * time.Second
+ if proto == "unix" {
+ // No need for compression in local communications.
+ tr.DisableCompression = true
+ tr.Dial = func(_, _ string) (net.Conn, error) {
+ return net.DialTimeout(proto, addr, timeout)
+ }
+ } else {
+ tr.Proxy = http.ProxyFromEnvironment
+ tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
+ }
+}
diff --git a/utils/tmpdir.go b/utils/tmpdir.go
deleted file mode 100644
index e200f340d..000000000
--- a/utils/tmpdir.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package utils
-
-import (
- "os"
- "path/filepath"
-)
-
-// TempDir returns the default directory to use for temporary files.
-func TempDir(rootDir string) (string, error) {
- var tmpDir string
- if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" {
- tmpDir = filepath.Join(rootDir, "tmp")
- }
- err := os.MkdirAll(tmpDir, 0700)
- return tmpDir, err
-}
diff --git a/utils/utils.go b/utils/utils.go
index 540ae6f57..cb1b7b34c 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -2,57 +2,23 @@ package utils
import (
"bufio"
- "bytes"
"crypto/sha1"
- "crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
- "net/http"
"os"
"os/exec"
"path/filepath"
- "regexp"
"runtime"
"strings"
- "sync"
- log "github.com/Sirupsen/logrus"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/pkg/archive"
- "github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/fileutils"
- "github.com/docker/docker/pkg/ioutils"
+ "github.com/docker/docker/pkg/stringid"
)
-type KeyValuePair struct {
- Key string
- Value string
-}
-
-var (
- validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
-)
-
-// Request a given URL and return an io.Reader
-func Download(url string) (resp *http.Response, err error) {
- if resp, err = http.Get(url); err != nil {
- return nil, err
- }
- if resp.StatusCode >= 400 {
- return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status)
- }
- return resp, nil
-}
-
-func Trunc(s string, maxlen int) string {
- if len(s) <= maxlen {
- return s
- }
- return s[:maxlen]
-}
-
// Figure out the absolute path of our own binary (if it's still around).
func SelfPath() string {
path, err := exec.LookPath(os.Args[0])
@@ -126,12 +92,12 @@ func DockerInitPath(localCopy string) string {
filepath.Join(filepath.Dir(selfPath), "dockerinit"),
// FHS 3.0 Draft: "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec."
- // http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec
+ // https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec
"/usr/libexec/docker/dockerinit",
"/usr/local/libexec/docker/dockerinit",
// FHS 2.3: "/usr/lib includes object files, libraries, and internal binaries that are not intended to be executed directly by users or shell scripts."
- // http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA
+ // https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA
"/usr/lib/docker/dockerinit",
"/usr/local/lib/docker/dockerinit",
}
@@ -154,157 +120,6 @@ func DockerInitPath(localCopy string) string {
return ""
}
-func GetTotalUsedFds() int {
- if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
- log.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
- } else {
- return len(fds)
- }
- return -1
-}
-
-func ValidateID(id string) error {
- if ok := validHex.MatchString(id); !ok {
- err := fmt.Errorf("image ID '%s' is invalid", id)
- return err
- }
- return nil
-}
-
-// Code c/c from io.Copy() modified to handle escape sequence
-func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
- buf := make([]byte, 32*1024)
- for {
- nr, er := src.Read(buf)
- if nr > 0 {
- // ---- Docker addition
- // char 16 is C-p
- if nr == 1 && buf[0] == 16 {
- nr, er = src.Read(buf)
- // char 17 is C-q
- if nr == 1 && buf[0] == 17 {
- if err := src.Close(); err != nil {
- return 0, err
- }
- return 0, nil
- }
- }
- // ---- End of docker
- nw, ew := dst.Write(buf[0:nr])
- if nw > 0 {
- written += int64(nw)
- }
- if ew != nil {
- err = ew
- break
- }
- if nr != nw {
- err = io.ErrShortWrite
- break
- }
- }
- if er == io.EOF {
- break
- }
- if er != nil {
- err = er
- break
- }
- }
- return written, err
-}
-
-func HashData(src io.Reader) (string, error) {
- h := sha256.New()
- if _, err := io.Copy(h, src); err != nil {
- return "", err
- }
- return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
-}
-
-type WriteFlusher struct {
- sync.Mutex
- w io.Writer
- flusher http.Flusher
-}
-
-func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
- wf.Lock()
- defer wf.Unlock()
- n, err = wf.w.Write(b)
- wf.flusher.Flush()
- return n, err
-}
-
-// Flush the stream immediately.
-func (wf *WriteFlusher) Flush() {
- wf.Lock()
- defer wf.Unlock()
- wf.flusher.Flush()
-}
-
-func NewWriteFlusher(w io.Writer) *WriteFlusher {
- var flusher http.Flusher
- if f, ok := w.(http.Flusher); ok {
- flusher = f
- } else {
- flusher = &ioutils.NopFlusher{}
- }
- return &WriteFlusher{w: w, flusher: flusher}
-}
-
-func NewHTTPRequestError(msg string, res *http.Response) error {
- return &JSONError{
- Message: msg,
- Code: res.StatusCode,
- }
-}
-
-// An StatusError reports an unsuccessful exit by a command.
-type StatusError struct {
- Status string
- StatusCode int
-}
-
-func (e *StatusError) Error() string {
- return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
-}
-
-func quote(word string, buf *bytes.Buffer) {
- // Bail out early for "simple" strings
- if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") {
- buf.WriteString(word)
- return
- }
-
- buf.WriteString("'")
-
- for i := 0; i < len(word); i++ {
- b := word[i]
- if b == '\'' {
- // Replace literal ' with a close ', a \', and a open '
- buf.WriteString("'\\''")
- } else {
- buf.WriteByte(b)
- }
- }
-
- buf.WriteString("'")
-}
-
-// Take a list of strings and escape them so they will be handled right
-// when passed as arguments to an program via a shell
-func ShellQuoteArguments(args []string) string {
- var buf bytes.Buffer
- for i, arg := range args {
- if i != 0 {
- buf.WriteByte(' ')
- }
- quote(arg, &buf)
- }
- return buf.String()
-}
-
var globalTestID string
// TestDirectory creates a new temporary directory and returns its path.
@@ -312,7 +127,7 @@ var globalTestID string
// new directory.
func TestDirectory(templateDir string) (dir string, err error) {
if globalTestID == "" {
- globalTestID = common.RandomString()[:4]
+ globalTestID = stringid.GenerateRandomID()[:4]
}
prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2))
if prefix == "" {
@@ -342,26 +157,6 @@ func GetCallerName(depth int) string {
return callerShortName
}
-func CopyFile(src, dst string) (int64, error) {
- if src == dst {
- return 0, nil
- }
- sf, err := os.Open(src)
- if err != nil {
- return 0, err
- }
- defer sf.Close()
- if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
- return 0, err
- }
- df, err := os.Create(dst)
- if err != nil {
- return 0, err
- }
- defer df.Close()
- return io.Copy(df, sf)
-}
-
// ReplaceOrAppendValues returns the defaults with the overrides either
// replaced by env key or appended to the list
func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
@@ -400,37 +195,6 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
return defaults
}
-func DoesEnvExist(name string) bool {
- for _, entry := range os.Environ() {
- parts := strings.SplitN(entry, "=", 2)
- if parts[0] == name {
- return true
- }
- }
- return false
-}
-
-// ReadSymlinkedDirectory returns the target directory of a symlink.
-// The target of the symbolic link may not be a file.
-func ReadSymlinkedDirectory(path string) (string, error) {
- var realPath string
- var err error
- if realPath, err = filepath.Abs(path); err != nil {
- return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
- }
- if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
- return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
- }
- realPathInfo, err := os.Stat(realPath)
- if err != nil {
- return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
- }
- if !realPathInfo.Mode().IsDir() {
- return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
- }
- return realPath, nil
-}
-
// ValidateContextDirectory checks if all the contents of the directory
// can be read and returns an error if some files can't be read
// symlinks which point to non-existing files don't trigger an error
@@ -475,15 +239,6 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
})
}
-func StringsContainsNoCase(slice []string, s string) bool {
- for _, ss := range slice {
- if strings.ToLower(s) == strings.ToLower(ss) {
- return true
- }
- }
- return false
-}
-
// Reads a .dockerignore file and returns the list of file patterns
// to ignore. Note this will trim whitespace from each line as well
// as use GO's "clean" func to get the shortest/cleanest path for each.
@@ -515,27 +270,6 @@ func ReadDockerIgnore(path string) ([]string, error) {
return excludes, nil
}
-// Wrap a concrete io.Writer and hold a count of the number
-// of bytes written to the writer during a "session".
-// This can be convenient when write return is masked
-// (e.g., json.Encoder.Encode())
-type WriteCounter struct {
- Count int64
- Writer io.Writer
-}
-
-func NewWriteCounter(w io.Writer) *WriteCounter {
- return &WriteCounter{
- Writer: w,
- }
-}
-
-func (wc *WriteCounter) Write(p []byte) (count int, err error) {
- count, err = wc.Writer.Write(p)
- wc.Count += int64(count)
- return
-}
-
// ImageReference combines `repo` and `ref` and returns a string representing
// the combination. If `ref` is a digest (meaning it's of the form
// :, the returned string is @[. Otherwise,
diff --git a/utils/utils_daemon.go b/utils/utils_daemon.go
deleted file mode 100644
index 3f8f4d569..000000000
--- a/utils/utils_daemon.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// +build daemon
-
-package utils
-
-import (
- "github.com/docker/docker/pkg/system"
- "os"
-)
-
-// IsFileOwner checks whether the current user is the owner of the given file.
-func IsFileOwner(f string) bool {
- if fileInfo, err := system.Stat(f); err == nil && fileInfo != nil {
- if int(fileInfo.Uid()) == os.Getuid() {
- return true
- }
- }
- return false
-}
diff --git a/utils/utils_daemon_test.go b/utils/utils_daemon_test.go
deleted file mode 100644
index e8361489b..000000000
--- a/utils/utils_daemon_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package utils
-
-import (
- "os"
- "path"
- "testing"
-)
-
-func TestIsFileOwner(t *testing.T) {
- var err error
- var file *os.File
-
- if file, err = os.Create(path.Join(os.TempDir(), "testIsFileOwner")); err != nil {
- t.Fatalf("failed to create file: %s", err)
- }
- file.Close()
-
- if ok := IsFileOwner(path.Join(os.TempDir(), "testIsFileOwner")); !ok {
- t.Fatalf("User should be owner of file")
- }
-
- if err = os.Remove(path.Join(os.TempDir(), "testIsFileOwner")); err != nil {
- t.Fatalf("failed to remove file: %s", err)
- }
-
-}
diff --git a/utils/utils_test.go b/utils/utils_test.go
index 94303a0e9..286300942 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -1,9 +1,10 @@
package utils
import (
- "bytes"
+ "fmt"
+ "io/ioutil"
"os"
- "strings"
+ "path/filepath"
"testing"
)
@@ -25,104 +26,6 @@ func TestReplaceAndAppendEnvVars(t *testing.T) {
}
}
-// Reading a symlink to a directory must return the directory
-func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) {
- var err error
- if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil {
- t.Errorf("failed to create directory: %s", err)
- }
-
- if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil {
- t.Errorf("failed to create symlink: %s", err)
- }
-
- var path string
- if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil {
- t.Fatalf("failed to read symlink to directory: %s", err)
- }
-
- if path != "/tmp/testReadSymlinkToExistingDirectory" {
- t.Fatalf("symlink returned unexpected directory: %s", path)
- }
-
- if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil {
- t.Errorf("failed to remove temporary directory: %s", err)
- }
-
- if err = os.Remove("/tmp/dirLinkTest"); err != nil {
- t.Errorf("failed to remove symlink: %s", err)
- }
-}
-
-// Reading a non-existing symlink must fail
-func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) {
- var path string
- var err error
- if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil {
- t.Fatalf("error expected for non-existing symlink")
- }
-
- if path != "" {
- t.Fatalf("expected empty path, but '%s' was returned", path)
- }
-}
-
-// Reading a symlink to a file must fail
-func TestReadSymlinkedDirectoryToFile(t *testing.T) {
- var err error
- var file *os.File
-
- if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil {
- t.Fatalf("failed to create file: %s", err)
- }
-
- file.Close()
-
- if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil {
- t.Errorf("failed to create symlink: %s", err)
- }
-
- var path string
- if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil {
- t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed")
- }
-
- if path != "" {
- t.Fatalf("path should've been empty: %s", path)
- }
-
- if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil {
- t.Errorf("failed to remove file: %s", err)
- }
-
- if err = os.Remove("/tmp/fileLinkTest"); err != nil {
- t.Errorf("failed to remove symlink: %s", err)
- }
-}
-
-func TestWriteCounter(t *testing.T) {
- dummy1 := "This is a dummy string."
- dummy2 := "This is another dummy string."
- totalLength := int64(len(dummy1) + len(dummy2))
-
- reader1 := strings.NewReader(dummy1)
- reader2 := strings.NewReader(dummy2)
-
- var buffer bytes.Buffer
- wc := NewWriteCounter(&buffer)
-
- reader1.WriteTo(wc)
- reader2.WriteTo(wc)
-
- if wc.Count != totalLength {
- t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength)
- }
-
- if buffer.String() != dummy1+dummy2 {
- t.Error("Wrong message written")
- }
-}
-
func TestImageReference(t *testing.T) {
tests := []struct {
repo string
@@ -152,3 +55,46 @@ func TestDigestReference(t *testing.T) {
t.Errorf("Unexpected DigestReference=true for input %q", input)
}
}
+
+func TestReadDockerIgnore(t *testing.T) {
+ tmpDir, err := ioutil.TempDir("", "dockerignore-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ diName := filepath.Join(tmpDir, ".dockerignore")
+
+ di, err := ReadDockerIgnore(diName)
+ if err != nil {
+ t.Fatalf("Expected not to have error, got %s", err)
+ }
+
+ if diLen := len(di); diLen != 0 {
+ t.Fatalf("Expected to have zero dockerignore entry, got %d", diLen)
+ }
+
+ content := fmt.Sprintf("test1\n/test2\n/a/file/here\n\nlastfile")
+ err = ioutil.WriteFile(diName, []byte(content), 0777)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ di, err = ReadDockerIgnore(diName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if di[0] != "test1" {
+ t.Fatalf("First element is not test1")
+ }
+ if di[1] != "/test2" {
+ t.Fatalf("Second element is not /test2")
+ }
+ if di[2] != "/a/file/here" {
+ t.Fatalf("Third element is not /a/file/here")
+ }
+ if di[3] != "lastfile" {
+ t.Fatalf("Fourth element is not lastfile")
+ }
+}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go
deleted file mode 100644
index e363aa793..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package tar implements access to tar archives.
-// It aims to cover most of the variations, including those produced
-// by GNU and BSD tars.
-//
-// References:
-// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
-// http://www.gnu.org/software/tar/manual/html_node/Standard.html
-// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
-package tar
-
-import (
- "bytes"
- "errors"
- "fmt"
- "os"
- "path"
- "time"
-)
-
-const (
- blockSize = 512
-
- // Types
- TypeReg = '0' // regular file
- TypeRegA = '\x00' // regular file
- TypeLink = '1' // hard link
- TypeSymlink = '2' // symbolic link
- TypeChar = '3' // character device node
- TypeBlock = '4' // block device node
- TypeDir = '5' // directory
- TypeFifo = '6' // fifo node
- TypeCont = '7' // reserved
- TypeXHeader = 'x' // extended header
- TypeXGlobalHeader = 'g' // global extended header
- TypeGNULongName = 'L' // Next file has a long name
- TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name
- TypeGNUSparse = 'S' // sparse file
-)
-
-// A Header represents a single header in a tar archive.
-// Some fields may not be populated.
-type Header struct {
- Name string // name of header file entry
- Mode int64 // permission and mode bits
- Uid int // user id of owner
- Gid int // group id of owner
- Size int64 // length in bytes
- ModTime time.Time // modified time
- Typeflag byte // type of header entry
- Linkname string // target name of link
- Uname string // user name of owner
- Gname string // group name of owner
- Devmajor int64 // major number of character or block device
- Devminor int64 // minor number of character or block device
- AccessTime time.Time // access time
- ChangeTime time.Time // status change time
- Xattrs map[string]string
-}
-
-// File name constants from the tar spec.
-const (
- fileNameSize = 100 // Maximum number of bytes in a standard tar name.
- fileNamePrefixSize = 155 // Maximum number of ustar extension bytes.
-)
-
-// FileInfo returns an os.FileInfo for the Header.
-func (h *Header) FileInfo() os.FileInfo {
- return headerFileInfo{h}
-}
-
-// headerFileInfo implements os.FileInfo.
-type headerFileInfo struct {
- h *Header
-}
-
-func (fi headerFileInfo) Size() int64 { return fi.h.Size }
-func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
-func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
-func (fi headerFileInfo) Sys() interface{} { return fi.h }
-
-// Name returns the base name of the file.
-func (fi headerFileInfo) Name() string {
- if fi.IsDir() {
- return path.Base(path.Clean(fi.h.Name))
- }
- return path.Base(fi.h.Name)
-}
-
-// Mode returns the permission and mode bits for the headerFileInfo.
-func (fi headerFileInfo) Mode() (mode os.FileMode) {
- // Set file permission bits.
- mode = os.FileMode(fi.h.Mode).Perm()
-
- // Set setuid, setgid and sticky bits.
- if fi.h.Mode&c_ISUID != 0 {
- // setuid
- mode |= os.ModeSetuid
- }
- if fi.h.Mode&c_ISGID != 0 {
- // setgid
- mode |= os.ModeSetgid
- }
- if fi.h.Mode&c_ISVTX != 0 {
- // sticky
- mode |= os.ModeSticky
- }
-
- // Set file mode bits.
- // clear perm, setuid, setgid and sticky bits.
- m := os.FileMode(fi.h.Mode) &^ 07777
- if m == c_ISDIR {
- // directory
- mode |= os.ModeDir
- }
- if m == c_ISFIFO {
- // named pipe (FIFO)
- mode |= os.ModeNamedPipe
- }
- if m == c_ISLNK {
- // symbolic link
- mode |= os.ModeSymlink
- }
- if m == c_ISBLK {
- // device file
- mode |= os.ModeDevice
- }
- if m == c_ISCHR {
- // Unix character device
- mode |= os.ModeDevice
- mode |= os.ModeCharDevice
- }
- if m == c_ISSOCK {
- // Unix domain socket
- mode |= os.ModeSocket
- }
-
- switch fi.h.Typeflag {
- case TypeLink, TypeSymlink:
- // hard link, symbolic link
- mode |= os.ModeSymlink
- case TypeChar:
- // character device node
- mode |= os.ModeDevice
- mode |= os.ModeCharDevice
- case TypeBlock:
- // block device node
- mode |= os.ModeDevice
- case TypeDir:
- // directory
- mode |= os.ModeDir
- case TypeFifo:
- // fifo node
- mode |= os.ModeNamedPipe
- }
-
- return mode
-}
-
-// sysStat, if non-nil, populates h from system-dependent fields of fi.
-var sysStat func(fi os.FileInfo, h *Header) error
-
-// Mode constants from the tar spec.
-const (
- c_ISUID = 04000 // Set uid
- c_ISGID = 02000 // Set gid
- c_ISVTX = 01000 // Save text (sticky bit)
- c_ISDIR = 040000 // Directory
- c_ISFIFO = 010000 // FIFO
- c_ISREG = 0100000 // Regular file
- c_ISLNK = 0120000 // Symbolic link
- c_ISBLK = 060000 // Block special file
- c_ISCHR = 020000 // Character special file
- c_ISSOCK = 0140000 // Socket
-)
-
-// Keywords for the PAX Extended Header
-const (
- paxAtime = "atime"
- paxCharset = "charset"
- paxComment = "comment"
- paxCtime = "ctime" // please note that ctime is not a valid pax header.
- paxGid = "gid"
- paxGname = "gname"
- paxLinkpath = "linkpath"
- paxMtime = "mtime"
- paxPath = "path"
- paxSize = "size"
- paxUid = "uid"
- paxUname = "uname"
- paxXattr = "SCHILY.xattr."
- paxNone = ""
-)
-
-// FileInfoHeader creates a partially-populated Header from fi.
-// If fi describes a symlink, FileInfoHeader records link as the link target.
-// If fi describes a directory, a slash is appended to the name.
-// Because os.FileInfo's Name method returns only the base name of
-// the file it describes, it may be necessary to modify the Name field
-// of the returned header to provide the full path name of the file.
-func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
- if fi == nil {
- return nil, errors.New("tar: FileInfo is nil")
- }
- fm := fi.Mode()
- h := &Header{
- Name: fi.Name(),
- ModTime: fi.ModTime(),
- Mode: int64(fm.Perm()), // or'd with c_IS* constants later
- }
- switch {
- case fm.IsRegular():
- h.Mode |= c_ISREG
- h.Typeflag = TypeReg
- h.Size = fi.Size()
- case fi.IsDir():
- h.Typeflag = TypeDir
- h.Mode |= c_ISDIR
- h.Name += "/"
- case fm&os.ModeSymlink != 0:
- h.Typeflag = TypeSymlink
- h.Mode |= c_ISLNK
- h.Linkname = link
- case fm&os.ModeDevice != 0:
- if fm&os.ModeCharDevice != 0 {
- h.Mode |= c_ISCHR
- h.Typeflag = TypeChar
- } else {
- h.Mode |= c_ISBLK
- h.Typeflag = TypeBlock
- }
- case fm&os.ModeNamedPipe != 0:
- h.Typeflag = TypeFifo
- h.Mode |= c_ISFIFO
- case fm&os.ModeSocket != 0:
- h.Mode |= c_ISSOCK
- default:
- return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
- }
- if fm&os.ModeSetuid != 0 {
- h.Mode |= c_ISUID
- }
- if fm&os.ModeSetgid != 0 {
- h.Mode |= c_ISGID
- }
- if fm&os.ModeSticky != 0 {
- h.Mode |= c_ISVTX
- }
- if sysStat != nil {
- return h, sysStat(fi, h)
- }
- return h, nil
-}
-
-var zeroBlock = make([]byte, blockSize)
-
-// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.
-// We compute and return both.
-func checksum(header []byte) (unsigned int64, signed int64) {
- for i := 0; i < len(header); i++ {
- if i == 148 {
- // The chksum field (header[148:156]) is special: it should be treated as space bytes.
- unsigned += ' ' * 8
- signed += ' ' * 8
- i += 7
- continue
- }
- unsigned += int64(header[i])
- signed += int64(int8(header[i]))
- }
- return
-}
-
-type slicer []byte
-
-func (sp *slicer) next(n int) (b []byte) {
- s := *sp
- b, *sp = s[0:n], s[n:]
- return
-}
-
-func isASCII(s string) bool {
- for _, c := range s {
- if c >= 0x80 {
- return false
- }
- }
- return true
-}
-
-func toASCII(s string) string {
- if isASCII(s) {
- return s
- }
- var buf bytes.Buffer
- for _, c := range s {
- if c < 0x80 {
- buf.WriteByte(byte(c))
- }
- }
- return buf.String()
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go
deleted file mode 100644
index 351eaa0e6..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tar_test
-
-import (
- "archive/tar"
- "bytes"
- "fmt"
- "io"
- "log"
- "os"
-)
-
-func Example() {
- // Create a buffer to write our archive to.
- buf := new(bytes.Buffer)
-
- // Create a new tar archive.
- tw := tar.NewWriter(buf)
-
- // Add some files to the archive.
- var files = []struct {
- Name, Body string
- }{
- {"readme.txt", "This archive contains some text files."},
- {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
- {"todo.txt", "Get animal handling licence."},
- }
- for _, file := range files {
- hdr := &tar.Header{
- Name: file.Name,
- Size: int64(len(file.Body)),
- }
- if err := tw.WriteHeader(hdr); err != nil {
- log.Fatalln(err)
- }
- if _, err := tw.Write([]byte(file.Body)); err != nil {
- log.Fatalln(err)
- }
- }
- // Make sure to check the error on Close.
- if err := tw.Close(); err != nil {
- log.Fatalln(err)
- }
-
- // Open the tar archive for reading.
- r := bytes.NewReader(buf.Bytes())
- tr := tar.NewReader(r)
-
- // Iterate through the files in the archive.
- for {
- hdr, err := tr.Next()
- if err == io.EOF {
- // end of tar archive
- break
- }
- if err != nil {
- log.Fatalln(err)
- }
- fmt.Printf("Contents of %s:\n", hdr.Name)
- if _, err := io.Copy(os.Stdout, tr); err != nil {
- log.Fatalln(err)
- }
- fmt.Println()
- }
-
- // Output:
- // Contents of readme.txt:
- // This archive contains some text files.
- // Contents of gopher.txt:
- // Gopher names:
- // George
- // Geoffrey
- // Gonzo
- // Contents of todo.txt:
- // Get animal handling licence.
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go
deleted file mode 100644
index a27559d0f..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go
+++ /dev/null
@@ -1,820 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tar
-
-// TODO(dsymonds):
-// - pax extensions
-
-import (
- "bytes"
- "errors"
- "io"
- "io/ioutil"
- "os"
- "strconv"
- "strings"
- "time"
-)
-
-var (
- ErrHeader = errors.New("archive/tar: invalid tar header")
-)
-
-const maxNanoSecondIntSize = 9
-
-// A Reader provides sequential access to the contents of a tar archive.
-// A tar archive consists of a sequence of files.
-// The Next method advances to the next file in the archive (including the first),
-// and then it can be treated as an io.Reader to access the file's data.
-type Reader struct {
- r io.Reader
- err error
- pad int64 // amount of padding (ignored) after current file entry
- curr numBytesReader // reader for current file entry
- hdrBuff [blockSize]byte // buffer to use in readHeader
-}
-
-// A numBytesReader is an io.Reader with a numBytes method, returning the number
-// of bytes remaining in the underlying encoded data.
-type numBytesReader interface {
- io.Reader
- numBytes() int64
-}
-
-// A regFileReader is a numBytesReader for reading file data from a tar archive.
-type regFileReader struct {
- r io.Reader // underlying reader
- nb int64 // number of unread bytes for current file entry
-}
-
-// A sparseFileReader is a numBytesReader for reading sparse file data from a tar archive.
-type sparseFileReader struct {
- rfr *regFileReader // reads the sparse-encoded file data
- sp []sparseEntry // the sparse map for the file
- pos int64 // keeps track of file position
- tot int64 // total size of the file
-}
-
-// Keywords for GNU sparse files in a PAX extended header
-const (
- paxGNUSparseNumBlocks = "GNU.sparse.numblocks"
- paxGNUSparseOffset = "GNU.sparse.offset"
- paxGNUSparseNumBytes = "GNU.sparse.numbytes"
- paxGNUSparseMap = "GNU.sparse.map"
- paxGNUSparseName = "GNU.sparse.name"
- paxGNUSparseMajor = "GNU.sparse.major"
- paxGNUSparseMinor = "GNU.sparse.minor"
- paxGNUSparseSize = "GNU.sparse.size"
- paxGNUSparseRealSize = "GNU.sparse.realsize"
-)
-
-// Keywords for old GNU sparse headers
-const (
- oldGNUSparseMainHeaderOffset = 386
- oldGNUSparseMainHeaderIsExtendedOffset = 482
- oldGNUSparseMainHeaderNumEntries = 4
- oldGNUSparseExtendedHeaderIsExtendedOffset = 504
- oldGNUSparseExtendedHeaderNumEntries = 21
- oldGNUSparseOffsetSize = 12
- oldGNUSparseNumBytesSize = 12
-)
-
-// NewReader creates a new Reader reading from r.
-func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
-
-// Next advances to the next entry in the tar archive.
-func (tr *Reader) Next() (*Header, error) {
- var hdr *Header
- if tr.err == nil {
- tr.skipUnread()
- }
- if tr.err != nil {
- return hdr, tr.err
- }
- hdr = tr.readHeader()
- if hdr == nil {
- return hdr, tr.err
- }
- // Check for PAX/GNU header.
- switch hdr.Typeflag {
- case TypeXHeader:
- // PAX extended header
- headers, err := parsePAX(tr)
- if err != nil {
- return nil, err
- }
- // We actually read the whole file,
- // but this skips alignment padding
- tr.skipUnread()
- hdr = tr.readHeader()
- mergePAX(hdr, headers)
-
- // Check for a PAX format sparse file
- sp, err := tr.checkForGNUSparsePAXHeaders(hdr, headers)
- if err != nil {
- tr.err = err
- return nil, err
- }
- if sp != nil {
- // Current file is a PAX format GNU sparse file.
- // Set the current file reader to a sparse file reader.
- tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size}
- }
- return hdr, nil
- case TypeGNULongName:
- // We have a GNU long name header. Its contents are the real file name.
- realname, err := ioutil.ReadAll(tr)
- if err != nil {
- return nil, err
- }
- hdr, err := tr.Next()
- hdr.Name = cString(realname)
- return hdr, err
- case TypeGNULongLink:
- // We have a GNU long link header.
- realname, err := ioutil.ReadAll(tr)
- if err != nil {
- return nil, err
- }
- hdr, err := tr.Next()
- hdr.Linkname = cString(realname)
- return hdr, err
- }
- return hdr, tr.err
-}
-
-// checkForGNUSparsePAXHeaders checks the PAX headers for GNU sparse headers. If they are found, then
-// this function reads the sparse map and returns it. Unknown sparse formats are ignored, causing the file to
-// be treated as a regular file.
-func (tr *Reader) checkForGNUSparsePAXHeaders(hdr *Header, headers map[string]string) ([]sparseEntry, error) {
- var sparseFormat string
-
- // Check for sparse format indicators
- major, majorOk := headers[paxGNUSparseMajor]
- minor, minorOk := headers[paxGNUSparseMinor]
- sparseName, sparseNameOk := headers[paxGNUSparseName]
- _, sparseMapOk := headers[paxGNUSparseMap]
- sparseSize, sparseSizeOk := headers[paxGNUSparseSize]
- sparseRealSize, sparseRealSizeOk := headers[paxGNUSparseRealSize]
-
- // Identify which, if any, sparse format applies from which PAX headers are set
- if majorOk && minorOk {
- sparseFormat = major + "." + minor
- } else if sparseNameOk && sparseMapOk {
- sparseFormat = "0.1"
- } else if sparseSizeOk {
- sparseFormat = "0.0"
- } else {
- // Not a PAX format GNU sparse file.
- return nil, nil
- }
-
- // Check for unknown sparse format
- if sparseFormat != "0.0" && sparseFormat != "0.1" && sparseFormat != "1.0" {
- return nil, nil
- }
-
- // Update hdr from GNU sparse PAX headers
- if sparseNameOk {
- hdr.Name = sparseName
- }
- if sparseSizeOk {
- realSize, err := strconv.ParseInt(sparseSize, 10, 0)
- if err != nil {
- return nil, ErrHeader
- }
- hdr.Size = realSize
- } else if sparseRealSizeOk {
- realSize, err := strconv.ParseInt(sparseRealSize, 10, 0)
- if err != nil {
- return nil, ErrHeader
- }
- hdr.Size = realSize
- }
-
- // Set up the sparse map, according to the particular sparse format in use
- var sp []sparseEntry
- var err error
- switch sparseFormat {
- case "0.0", "0.1":
- sp, err = readGNUSparseMap0x1(headers)
- case "1.0":
- sp, err = readGNUSparseMap1x0(tr.curr)
- }
- return sp, err
-}
-
-// mergePAX merges well known headers according to PAX standard.
-// In general headers with the same name as those found
-// in the header struct overwrite those found in the header
-// struct with higher precision or longer values. Esp. useful
-// for name and linkname fields.
-func mergePAX(hdr *Header, headers map[string]string) error {
- for k, v := range headers {
- switch k {
- case paxPath:
- hdr.Name = v
- case paxLinkpath:
- hdr.Linkname = v
- case paxGname:
- hdr.Gname = v
- case paxUname:
- hdr.Uname = v
- case paxUid:
- uid, err := strconv.ParseInt(v, 10, 0)
- if err != nil {
- return err
- }
- hdr.Uid = int(uid)
- case paxGid:
- gid, err := strconv.ParseInt(v, 10, 0)
- if err != nil {
- return err
- }
- hdr.Gid = int(gid)
- case paxAtime:
- t, err := parsePAXTime(v)
- if err != nil {
- return err
- }
- hdr.AccessTime = t
- case paxMtime:
- t, err := parsePAXTime(v)
- if err != nil {
- return err
- }
- hdr.ModTime = t
- case paxCtime:
- t, err := parsePAXTime(v)
- if err != nil {
- return err
- }
- hdr.ChangeTime = t
- case paxSize:
- size, err := strconv.ParseInt(v, 10, 0)
- if err != nil {
- return err
- }
- hdr.Size = int64(size)
- default:
- if strings.HasPrefix(k, paxXattr) {
- if hdr.Xattrs == nil {
- hdr.Xattrs = make(map[string]string)
- }
- hdr.Xattrs[k[len(paxXattr):]] = v
- }
- }
- }
- return nil
-}
-
-// parsePAXTime takes a string of the form %d.%d as described in
-// the PAX specification.
-func parsePAXTime(t string) (time.Time, error) {
- buf := []byte(t)
- pos := bytes.IndexByte(buf, '.')
- var seconds, nanoseconds int64
- var err error
- if pos == -1 {
- seconds, err = strconv.ParseInt(t, 10, 0)
- if err != nil {
- return time.Time{}, err
- }
- } else {
- seconds, err = strconv.ParseInt(string(buf[:pos]), 10, 0)
- if err != nil {
- return time.Time{}, err
- }
- nano_buf := string(buf[pos+1:])
- // Pad as needed before converting to a decimal.
- // For example .030 -> .030000000 -> 30000000 nanoseconds
- if len(nano_buf) < maxNanoSecondIntSize {
- // Right pad
- nano_buf += strings.Repeat("0", maxNanoSecondIntSize-len(nano_buf))
- } else if len(nano_buf) > maxNanoSecondIntSize {
- // Right truncate
- nano_buf = nano_buf[:maxNanoSecondIntSize]
- }
- nanoseconds, err = strconv.ParseInt(string(nano_buf), 10, 0)
- if err != nil {
- return time.Time{}, err
- }
- }
- ts := time.Unix(seconds, nanoseconds)
- return ts, nil
-}
-
-// parsePAX parses PAX headers.
-// If an extended header (type 'x') is invalid, ErrHeader is returned
-func parsePAX(r io.Reader) (map[string]string, error) {
- buf, err := ioutil.ReadAll(r)
- if err != nil {
- return nil, err
- }
-
- // For GNU PAX sparse format 0.0 support.
- // This function transforms the sparse format 0.0 headers into sparse format 0.1 headers.
- var sparseMap bytes.Buffer
-
- headers := make(map[string]string)
- // Each record is constructed as
- // "%d %s=%s\n", length, keyword, value
- for len(buf) > 0 {
- // or the header was empty to start with.
- var sp int
- // The size field ends at the first space.
- sp = bytes.IndexByte(buf, ' ')
- if sp == -1 {
- return nil, ErrHeader
- }
- // Parse the first token as a decimal integer.
- n, err := strconv.ParseInt(string(buf[:sp]), 10, 0)
- if err != nil {
- return nil, ErrHeader
- }
- // Extract everything between the decimal and the n -1 on the
- // beginning to eat the ' ', -1 on the end to skip the newline.
- var record []byte
- record, buf = buf[sp+1:n-1], buf[n:]
- // The first equals is guaranteed to mark the end of the key.
- // Everything else is value.
- eq := bytes.IndexByte(record, '=')
- if eq == -1 {
- return nil, ErrHeader
- }
- key, value := record[:eq], record[eq+1:]
-
- keyStr := string(key)
- if keyStr == paxGNUSparseOffset || keyStr == paxGNUSparseNumBytes {
- // GNU sparse format 0.0 special key. Write to sparseMap instead of using the headers map.
- sparseMap.Write(value)
- sparseMap.Write([]byte{','})
- } else {
- // Normal key. Set the value in the headers map.
- headers[keyStr] = string(value)
- }
- }
- if sparseMap.Len() != 0 {
- // Add sparse info to headers, chopping off the extra comma
- sparseMap.Truncate(sparseMap.Len() - 1)
- headers[paxGNUSparseMap] = sparseMap.String()
- }
- return headers, nil
-}
-
-// cString parses bytes as a NUL-terminated C-style string.
-// If a NUL byte is not found then the whole slice is returned as a string.
-func cString(b []byte) string {
- n := 0
- for n < len(b) && b[n] != 0 {
- n++
- }
- return string(b[0:n])
-}
-
-func (tr *Reader) octal(b []byte) int64 {
- // Check for binary format first.
- if len(b) > 0 && b[0]&0x80 != 0 {
- var x int64
- for i, c := range b {
- if i == 0 {
- c &= 0x7f // ignore signal bit in first byte
- }
- x = x<<8 | int64(c)
- }
- return x
- }
-
- // Because unused fields are filled with NULs, we need
- // to skip leading NULs. Fields may also be padded with
- // spaces or NULs.
- // So we remove leading and trailing NULs and spaces to
- // be sure.
- b = bytes.Trim(b, " \x00")
-
- if len(b) == 0 {
- return 0
- }
- x, err := strconv.ParseUint(cString(b), 8, 64)
- if err != nil {
- tr.err = err
- }
- return int64(x)
-}
-
-// skipUnread skips any unread bytes in the existing file entry, as well as any alignment padding.
-func (tr *Reader) skipUnread() {
- nr := tr.numBytes() + tr.pad // number of bytes to skip
- tr.curr, tr.pad = nil, 0
- if sr, ok := tr.r.(io.Seeker); ok {
- if _, err := sr.Seek(nr, os.SEEK_CUR); err == nil {
- return
- }
- }
- _, tr.err = io.CopyN(ioutil.Discard, tr.r, nr)
-}
-
-func (tr *Reader) verifyChecksum(header []byte) bool {
- if tr.err != nil {
- return false
- }
-
- given := tr.octal(header[148:156])
- unsigned, signed := checksum(header)
- return given == unsigned || given == signed
-}
-
-func (tr *Reader) readHeader() *Header {
- header := tr.hdrBuff[:]
- copy(header, zeroBlock)
-
- if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil {
- return nil
- }
-
- // Two blocks of zero bytes marks the end of the archive.
- if bytes.Equal(header, zeroBlock[0:blockSize]) {
- if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil {
- return nil
- }
- if bytes.Equal(header, zeroBlock[0:blockSize]) {
- tr.err = io.EOF
- } else {
- tr.err = ErrHeader // zero block and then non-zero block
- }
- return nil
- }
-
- if !tr.verifyChecksum(header) {
- tr.err = ErrHeader
- return nil
- }
-
- // Unpack
- hdr := new(Header)
- s := slicer(header)
-
- hdr.Name = cString(s.next(100))
- hdr.Mode = tr.octal(s.next(8))
- hdr.Uid = int(tr.octal(s.next(8)))
- hdr.Gid = int(tr.octal(s.next(8)))
- hdr.Size = tr.octal(s.next(12))
- hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0)
- s.next(8) // chksum
- hdr.Typeflag = s.next(1)[0]
- hdr.Linkname = cString(s.next(100))
-
- // The remainder of the header depends on the value of magic.
- // The original (v7) version of tar had no explicit magic field,
- // so its magic bytes, like the rest of the block, are NULs.
- magic := string(s.next(8)) // contains version field as well.
- var format string
- switch {
- case magic[:6] == "ustar\x00": // POSIX tar (1003.1-1988)
- if string(header[508:512]) == "tar\x00" {
- format = "star"
- } else {
- format = "posix"
- }
- case magic == "ustar \x00": // old GNU tar
- format = "gnu"
- }
-
- switch format {
- case "posix", "gnu", "star":
- hdr.Uname = cString(s.next(32))
- hdr.Gname = cString(s.next(32))
- devmajor := s.next(8)
- devminor := s.next(8)
- if hdr.Typeflag == TypeChar || hdr.Typeflag == TypeBlock {
- hdr.Devmajor = tr.octal(devmajor)
- hdr.Devminor = tr.octal(devminor)
- }
- var prefix string
- switch format {
- case "posix", "gnu":
- prefix = cString(s.next(155))
- case "star":
- prefix = cString(s.next(131))
- hdr.AccessTime = time.Unix(tr.octal(s.next(12)), 0)
- hdr.ChangeTime = time.Unix(tr.octal(s.next(12)), 0)
- }
- if len(prefix) > 0 {
- hdr.Name = prefix + "/" + hdr.Name
- }
- }
-
- if tr.err != nil {
- tr.err = ErrHeader
- return nil
- }
-
- // Maximum value of hdr.Size is 64 GB (12 octal digits),
- // so there's no risk of int64 overflowing.
- nb := int64(hdr.Size)
- tr.pad = -nb & (blockSize - 1) // blockSize is a power of two
-
- // Set the current file reader.
- tr.curr = ®FileReader{r: tr.r, nb: nb}
-
- // Check for old GNU sparse format entry.
- if hdr.Typeflag == TypeGNUSparse {
- // Get the real size of the file.
- hdr.Size = tr.octal(header[483:495])
-
- // Read the sparse map.
- sp := tr.readOldGNUSparseMap(header)
- if tr.err != nil {
- return nil
- }
- // Current file is a GNU sparse file. Update the current file reader.
- tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size}
- }
-
- return hdr
-}
-
-// A sparseEntry holds a single entry in a sparse file's sparse map.
-// A sparse entry indicates the offset and size in a sparse file of a
-// block of data.
-type sparseEntry struct {
- offset int64
- numBytes int64
-}
-
-// readOldGNUSparseMap reads the sparse map as stored in the old GNU sparse format.
-// The sparse map is stored in the tar header if it's small enough. If it's larger than four entries,
-// then one or more extension headers are used to store the rest of the sparse map.
-func (tr *Reader) readOldGNUSparseMap(header []byte) []sparseEntry {
- isExtended := header[oldGNUSparseMainHeaderIsExtendedOffset] != 0
- spCap := oldGNUSparseMainHeaderNumEntries
- if isExtended {
- spCap += oldGNUSparseExtendedHeaderNumEntries
- }
- sp := make([]sparseEntry, 0, spCap)
- s := slicer(header[oldGNUSparseMainHeaderOffset:])
-
- // Read the four entries from the main tar header
- for i := 0; i < oldGNUSparseMainHeaderNumEntries; i++ {
- offset := tr.octal(s.next(oldGNUSparseOffsetSize))
- numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize))
- if tr.err != nil {
- tr.err = ErrHeader
- return nil
- }
- if offset == 0 && numBytes == 0 {
- break
- }
- sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
- }
-
- for isExtended {
- // There are more entries. Read an extension header and parse its entries.
- sparseHeader := make([]byte, blockSize)
- if _, tr.err = io.ReadFull(tr.r, sparseHeader); tr.err != nil {
- return nil
- }
- isExtended = sparseHeader[oldGNUSparseExtendedHeaderIsExtendedOffset] != 0
- s = slicer(sparseHeader)
- for i := 0; i < oldGNUSparseExtendedHeaderNumEntries; i++ {
- offset := tr.octal(s.next(oldGNUSparseOffsetSize))
- numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize))
- if tr.err != nil {
- tr.err = ErrHeader
- return nil
- }
- if offset == 0 && numBytes == 0 {
- break
- }
- sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
- }
- }
- return sp
-}
-
-// readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format version 1.0.
-// The sparse map is stored just before the file data and padded out to the nearest block boundary.
-func readGNUSparseMap1x0(r io.Reader) ([]sparseEntry, error) {
- buf := make([]byte, 2*blockSize)
- sparseHeader := buf[:blockSize]
-
- // readDecimal is a helper function to read a decimal integer from the sparse map
- // while making sure to read from the file in blocks of size blockSize
- readDecimal := func() (int64, error) {
- // Look for newline
- nl := bytes.IndexByte(sparseHeader, '\n')
- if nl == -1 {
- if len(sparseHeader) >= blockSize {
- // This is an error
- return 0, ErrHeader
- }
- oldLen := len(sparseHeader)
- newLen := oldLen + blockSize
- if cap(sparseHeader) < newLen {
- // There's more header, but we need to make room for the next block
- copy(buf, sparseHeader)
- sparseHeader = buf[:newLen]
- } else {
- // There's more header, and we can just reslice
- sparseHeader = sparseHeader[:newLen]
- }
-
- // Now that sparseHeader is large enough, read next block
- if _, err := io.ReadFull(r, sparseHeader[oldLen:newLen]); err != nil {
- return 0, err
- }
-
- // Look for a newline in the new data
- nl = bytes.IndexByte(sparseHeader[oldLen:newLen], '\n')
- if nl == -1 {
- // This is an error
- return 0, ErrHeader
- }
- nl += oldLen // We want the position from the beginning
- }
- // Now that we've found a newline, read a number
- n, err := strconv.ParseInt(string(sparseHeader[:nl]), 10, 0)
- if err != nil {
- return 0, ErrHeader
- }
-
- // Update sparseHeader to consume this number
- sparseHeader = sparseHeader[nl+1:]
- return n, nil
- }
-
- // Read the first block
- if _, err := io.ReadFull(r, sparseHeader); err != nil {
- return nil, err
- }
-
- // The first line contains the number of entries
- numEntries, err := readDecimal()
- if err != nil {
- return nil, err
- }
-
- // Read all the entries
- sp := make([]sparseEntry, 0, numEntries)
- for i := int64(0); i < numEntries; i++ {
- // Read the offset
- offset, err := readDecimal()
- if err != nil {
- return nil, err
- }
- // Read numBytes
- numBytes, err := readDecimal()
- if err != nil {
- return nil, err
- }
-
- sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
- }
-
- return sp, nil
-}
-
-// readGNUSparseMap0x1 reads the sparse map as stored in GNU's PAX sparse format version 0.1.
-// The sparse map is stored in the PAX headers.
-func readGNUSparseMap0x1(headers map[string]string) ([]sparseEntry, error) {
- // Get number of entries
- numEntriesStr, ok := headers[paxGNUSparseNumBlocks]
- if !ok {
- return nil, ErrHeader
- }
- numEntries, err := strconv.ParseInt(numEntriesStr, 10, 0)
- if err != nil {
- return nil, ErrHeader
- }
-
- sparseMap := strings.Split(headers[paxGNUSparseMap], ",")
-
- // There should be two numbers in sparseMap for each entry
- if int64(len(sparseMap)) != 2*numEntries {
- return nil, ErrHeader
- }
-
- // Loop through the entries in the sparse map
- sp := make([]sparseEntry, 0, numEntries)
- for i := int64(0); i < numEntries; i++ {
- offset, err := strconv.ParseInt(sparseMap[2*i], 10, 0)
- if err != nil {
- return nil, ErrHeader
- }
- numBytes, err := strconv.ParseInt(sparseMap[2*i+1], 10, 0)
- if err != nil {
- return nil, ErrHeader
- }
- sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
- }
-
- return sp, nil
-}
-
-// numBytes returns the number of bytes left to read in the current file's entry
-// in the tar archive, or 0 if there is no current file.
-func (tr *Reader) numBytes() int64 {
- if tr.curr == nil {
- // No current file, so no bytes
- return 0
- }
- return tr.curr.numBytes()
-}
-
-// Read reads from the current entry in the tar archive.
-// It returns 0, io.EOF when it reaches the end of that entry,
-// until Next is called to advance to the next entry.
-func (tr *Reader) Read(b []byte) (n int, err error) {
- if tr.curr == nil {
- return 0, io.EOF
- }
- n, err = tr.curr.Read(b)
- if err != nil && err != io.EOF {
- tr.err = err
- }
- return
-}
-
-func (rfr *regFileReader) Read(b []byte) (n int, err error) {
- if rfr.nb == 0 {
- // file consumed
- return 0, io.EOF
- }
- if int64(len(b)) > rfr.nb {
- b = b[0:rfr.nb]
- }
- n, err = rfr.r.Read(b)
- rfr.nb -= int64(n)
-
- if err == io.EOF && rfr.nb > 0 {
- err = io.ErrUnexpectedEOF
- }
- return
-}
-
-// numBytes returns the number of bytes left to read in the file's data in the tar archive.
-func (rfr *regFileReader) numBytes() int64 {
- return rfr.nb
-}
-
-// readHole reads a sparse file hole ending at offset toOffset
-func (sfr *sparseFileReader) readHole(b []byte, toOffset int64) int {
- n64 := toOffset - sfr.pos
- if n64 > int64(len(b)) {
- n64 = int64(len(b))
- }
- n := int(n64)
- for i := 0; i < n; i++ {
- b[i] = 0
- }
- sfr.pos += n64
- return n
-}
-
-// Read reads the sparse file data in expanded form.
-func (sfr *sparseFileReader) Read(b []byte) (n int, err error) {
- if len(sfr.sp) == 0 {
- // No more data fragments to read from.
- if sfr.pos < sfr.tot {
- // We're in the last hole
- n = sfr.readHole(b, sfr.tot)
- return
- }
- // Otherwise, we're at the end of the file
- return 0, io.EOF
- }
- if sfr.pos < sfr.sp[0].offset {
- // We're in a hole
- n = sfr.readHole(b, sfr.sp[0].offset)
- return
- }
-
- // We're not in a hole, so we'll read from the next data fragment
- posInFragment := sfr.pos - sfr.sp[0].offset
- bytesLeft := sfr.sp[0].numBytes - posInFragment
- if int64(len(b)) > bytesLeft {
- b = b[0:bytesLeft]
- }
-
- n, err = sfr.rfr.Read(b)
- sfr.pos += int64(n)
-
- if int64(n) == bytesLeft {
- // We're done with this fragment
- sfr.sp = sfr.sp[1:]
- }
-
- if err == io.EOF && sfr.pos < sfr.tot {
- // We reached the end of the last fragment's data, but there's a final hole
- err = nil
- }
- return
-}
-
-// numBytes returns the number of bytes left to read in the sparse file's
-// sparse-encoded data in the tar archive.
-func (sfr *sparseFileReader) numBytes() int64 {
- return sfr.rfr.nb
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go
deleted file mode 100644
index 9601ffe45..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go
+++ /dev/null
@@ -1,743 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tar
-
-import (
- "bytes"
- "crypto/md5"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "reflect"
- "strings"
- "testing"
- "time"
-)
-
-type untarTest struct {
- file string
- headers []*Header
- cksums []string
-}
-
-var gnuTarTest = &untarTest{
- file: "testdata/gnu.tar",
- headers: []*Header{
- {
- Name: "small.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 5,
- ModTime: time.Unix(1244428340, 0),
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- },
- {
- Name: "small2.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 11,
- ModTime: time.Unix(1244436044, 0),
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- },
- },
- cksums: []string{
- "e38b27eaccb4391bdec553a7f3ae6b2f",
- "c65bd2e50a56a2138bf1716f2fd56fe9",
- },
-}
-
-var sparseTarTest = &untarTest{
- file: "testdata/sparse-formats.tar",
- headers: []*Header{
- {
- Name: "sparse-gnu",
- Mode: 420,
- Uid: 1000,
- Gid: 1000,
- Size: 200,
- ModTime: time.Unix(1392395740, 0),
- Typeflag: 0x53,
- Linkname: "",
- Uname: "david",
- Gname: "david",
- Devmajor: 0,
- Devminor: 0,
- },
- {
- Name: "sparse-posix-0.0",
- Mode: 420,
- Uid: 1000,
- Gid: 1000,
- Size: 200,
- ModTime: time.Unix(1392342187, 0),
- Typeflag: 0x30,
- Linkname: "",
- Uname: "david",
- Gname: "david",
- Devmajor: 0,
- Devminor: 0,
- },
- {
- Name: "sparse-posix-0.1",
- Mode: 420,
- Uid: 1000,
- Gid: 1000,
- Size: 200,
- ModTime: time.Unix(1392340456, 0),
- Typeflag: 0x30,
- Linkname: "",
- Uname: "david",
- Gname: "david",
- Devmajor: 0,
- Devminor: 0,
- },
- {
- Name: "sparse-posix-1.0",
- Mode: 420,
- Uid: 1000,
- Gid: 1000,
- Size: 200,
- ModTime: time.Unix(1392337404, 0),
- Typeflag: 0x30,
- Linkname: "",
- Uname: "david",
- Gname: "david",
- Devmajor: 0,
- Devminor: 0,
- },
- {
- Name: "end",
- Mode: 420,
- Uid: 1000,
- Gid: 1000,
- Size: 4,
- ModTime: time.Unix(1392398319, 0),
- Typeflag: 0x30,
- Linkname: "",
- Uname: "david",
- Gname: "david",
- Devmajor: 0,
- Devminor: 0,
- },
- },
- cksums: []string{
- "6f53234398c2449fe67c1812d993012f",
- "6f53234398c2449fe67c1812d993012f",
- "6f53234398c2449fe67c1812d993012f",
- "6f53234398c2449fe67c1812d993012f",
- "b0061974914468de549a2af8ced10316",
- },
-}
-
-var untarTests = []*untarTest{
- gnuTarTest,
- sparseTarTest,
- {
- file: "testdata/star.tar",
- headers: []*Header{
- {
- Name: "small.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 5,
- ModTime: time.Unix(1244592783, 0),
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- AccessTime: time.Unix(1244592783, 0),
- ChangeTime: time.Unix(1244592783, 0),
- },
- {
- Name: "small2.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 11,
- ModTime: time.Unix(1244592783, 0),
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- AccessTime: time.Unix(1244592783, 0),
- ChangeTime: time.Unix(1244592783, 0),
- },
- },
- },
- {
- file: "testdata/v7.tar",
- headers: []*Header{
- {
- Name: "small.txt",
- Mode: 0444,
- Uid: 73025,
- Gid: 5000,
- Size: 5,
- ModTime: time.Unix(1244593104, 0),
- Typeflag: '\x00',
- },
- {
- Name: "small2.txt",
- Mode: 0444,
- Uid: 73025,
- Gid: 5000,
- Size: 11,
- ModTime: time.Unix(1244593104, 0),
- Typeflag: '\x00',
- },
- },
- },
- {
- file: "testdata/pax.tar",
- headers: []*Header{
- {
- Name: "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
- Mode: 0664,
- Uid: 1000,
- Gid: 1000,
- Uname: "shane",
- Gname: "shane",
- Size: 7,
- ModTime: time.Unix(1350244992, 23960108),
- ChangeTime: time.Unix(1350244992, 23960108),
- AccessTime: time.Unix(1350244992, 23960108),
- Typeflag: TypeReg,
- },
- {
- Name: "a/b",
- Mode: 0777,
- Uid: 1000,
- Gid: 1000,
- Uname: "shane",
- Gname: "shane",
- Size: 0,
- ModTime: time.Unix(1350266320, 910238425),
- ChangeTime: time.Unix(1350266320, 910238425),
- AccessTime: time.Unix(1350266320, 910238425),
- Typeflag: TypeSymlink,
- Linkname: "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
- },
- },
- },
- {
- file: "testdata/nil-uid.tar", // golang.org/issue/5290
- headers: []*Header{
- {
- Name: "P1050238.JPG.log",
- Mode: 0664,
- Uid: 0,
- Gid: 0,
- Size: 14,
- ModTime: time.Unix(1365454838, 0),
- Typeflag: TypeReg,
- Linkname: "",
- Uname: "eyefi",
- Gname: "eyefi",
- Devmajor: 0,
- Devminor: 0,
- },
- },
- },
- {
- file: "testdata/xattrs.tar",
- headers: []*Header{
- {
- Name: "small.txt",
- Mode: 0644,
- Uid: 1000,
- Gid: 10,
- Size: 5,
- ModTime: time.Unix(1386065770, 448252320),
- Typeflag: '0',
- Uname: "alex",
- Gname: "wheel",
- AccessTime: time.Unix(1389782991, 419875220),
- ChangeTime: time.Unix(1389782956, 794414986),
- Xattrs: map[string]string{
- "user.key": "value",
- "user.key2": "value2",
- // Interestingly, selinux encodes the terminating null inside the xattr
- "security.selinux": "unconfined_u:object_r:default_t:s0\x00",
- },
- },
- {
- Name: "small2.txt",
- Mode: 0644,
- Uid: 1000,
- Gid: 10,
- Size: 11,
- ModTime: time.Unix(1386065770, 449252304),
- Typeflag: '0',
- Uname: "alex",
- Gname: "wheel",
- AccessTime: time.Unix(1389782991, 419875220),
- ChangeTime: time.Unix(1386065770, 449252304),
- Xattrs: map[string]string{
- "security.selinux": "unconfined_u:object_r:default_t:s0\x00",
- },
- },
- },
- },
-}
-
-func TestReader(t *testing.T) {
-testLoop:
- for i, test := range untarTests {
- f, err := os.Open(test.file)
- if err != nil {
- t.Errorf("test %d: Unexpected error: %v", i, err)
- continue
- }
- defer f.Close()
- tr := NewReader(f)
- for j, header := range test.headers {
- hdr, err := tr.Next()
- if err != nil || hdr == nil {
- t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err)
- f.Close()
- continue testLoop
- }
- if !reflect.DeepEqual(*hdr, *header) {
- t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
- i, j, *hdr, *header)
- }
- }
- hdr, err := tr.Next()
- if err == io.EOF {
- continue testLoop
- }
- if hdr != nil || err != nil {
- t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, hdr, err)
- }
- }
-}
-
-func TestPartialRead(t *testing.T) {
- f, err := os.Open("testdata/gnu.tar")
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- defer f.Close()
-
- tr := NewReader(f)
-
- // Read the first four bytes; Next() should skip the last byte.
- hdr, err := tr.Next()
- if err != nil || hdr == nil {
- t.Fatalf("Didn't get first file: %v", err)
- }
- buf := make([]byte, 4)
- if _, err := io.ReadFull(tr, buf); err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- if expected := []byte("Kilt"); !bytes.Equal(buf, expected) {
- t.Errorf("Contents = %v, want %v", buf, expected)
- }
-
- // Second file
- hdr, err = tr.Next()
- if err != nil || hdr == nil {
- t.Fatalf("Didn't get second file: %v", err)
- }
- buf = make([]byte, 6)
- if _, err := io.ReadFull(tr, buf); err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- if expected := []byte("Google"); !bytes.Equal(buf, expected) {
- t.Errorf("Contents = %v, want %v", buf, expected)
- }
-}
-
-func TestIncrementalRead(t *testing.T) {
- test := gnuTarTest
- f, err := os.Open(test.file)
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- defer f.Close()
-
- tr := NewReader(f)
-
- headers := test.headers
- cksums := test.cksums
- nread := 0
-
- // loop over all files
- for ; ; nread++ {
- hdr, err := tr.Next()
- if hdr == nil || err == io.EOF {
- break
- }
-
- // check the header
- if !reflect.DeepEqual(*hdr, *headers[nread]) {
- t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
- *hdr, headers[nread])
- }
-
- // read file contents in little chunks EOF,
- // checksumming all the way
- h := md5.New()
- rdbuf := make([]uint8, 8)
- for {
- nr, err := tr.Read(rdbuf)
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Errorf("Read: unexpected error %v\n", err)
- break
- }
- h.Write(rdbuf[0:nr])
- }
- // verify checksum
- have := fmt.Sprintf("%x", h.Sum(nil))
- want := cksums[nread]
- if want != have {
- t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want)
- }
- }
- if nread != len(headers) {
- t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread)
- }
-}
-
-func TestNonSeekable(t *testing.T) {
- test := gnuTarTest
- f, err := os.Open(test.file)
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- defer f.Close()
-
- type readerOnly struct {
- io.Reader
- }
- tr := NewReader(readerOnly{f})
- nread := 0
-
- for ; ; nread++ {
- _, err := tr.Next()
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- }
-
- if nread != len(test.headers) {
- t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(test.headers), nread)
- }
-}
-
-func TestParsePAXHeader(t *testing.T) {
- paxTests := [][3]string{
- {"a", "a=name", "10 a=name\n"}, // Test case involving multiple acceptable lengths
- {"a", "a=name", "9 a=name\n"}, // Test case involving multiple acceptable length
- {"mtime", "mtime=1350244992.023960108", "30 mtime=1350244992.023960108\n"}}
- for _, test := range paxTests {
- key, expected, raw := test[0], test[1], test[2]
- reader := bytes.NewReader([]byte(raw))
- headers, err := parsePAX(reader)
- if err != nil {
- t.Errorf("Couldn't parse correctly formatted headers: %v", err)
- continue
- }
- if strings.EqualFold(headers[key], expected) {
- t.Errorf("mtime header incorrectly parsed: got %s, wanted %s", headers[key], expected)
- continue
- }
- trailer := make([]byte, 100)
- n, err := reader.Read(trailer)
- if err != io.EOF || n != 0 {
- t.Error("Buffer wasn't consumed")
- }
- }
- badHeader := bytes.NewReader([]byte("3 somelongkey="))
- if _, err := parsePAX(badHeader); err != ErrHeader {
- t.Fatal("Unexpected success when parsing bad header")
- }
-}
-
-func TestParsePAXTime(t *testing.T) {
- // Some valid PAX time values
- timestamps := map[string]time.Time{
- "1350244992.023960108": time.Unix(1350244992, 23960108), // The common case
- "1350244992.02396010": time.Unix(1350244992, 23960100), // Lower precision value
- "1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value
- "1350244992": time.Unix(1350244992, 0), // Low precision value
- }
- for input, expected := range timestamps {
- ts, err := parsePAXTime(input)
- if err != nil {
- t.Fatal(err)
- }
- if !ts.Equal(expected) {
- t.Fatalf("Time parsing failure %s %s", ts, expected)
- }
- }
-}
-
-func TestMergePAX(t *testing.T) {
- hdr := new(Header)
- // Test a string, integer, and time based value.
- headers := map[string]string{
- "path": "a/b/c",
- "uid": "1000",
- "mtime": "1350244992.023960108",
- }
- err := mergePAX(hdr, headers)
- if err != nil {
- t.Fatal(err)
- }
- want := &Header{
- Name: "a/b/c",
- Uid: 1000,
- ModTime: time.Unix(1350244992, 23960108),
- }
- if !reflect.DeepEqual(hdr, want) {
- t.Errorf("incorrect merge: got %+v, want %+v", hdr, want)
- }
-}
-
-func TestSparseEndToEnd(t *testing.T) {
- test := sparseTarTest
- f, err := os.Open(test.file)
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- defer f.Close()
-
- tr := NewReader(f)
-
- headers := test.headers
- cksums := test.cksums
- nread := 0
-
- // loop over all files
- for ; ; nread++ {
- hdr, err := tr.Next()
- if hdr == nil || err == io.EOF {
- break
- }
-
- // check the header
- if !reflect.DeepEqual(*hdr, *headers[nread]) {
- t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
- *hdr, headers[nread])
- }
-
- // read and checksum the file data
- h := md5.New()
- _, err = io.Copy(h, tr)
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
-
- // verify checksum
- have := fmt.Sprintf("%x", h.Sum(nil))
- want := cksums[nread]
- if want != have {
- t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want)
- }
- }
- if nread != len(headers) {
- t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread)
- }
-}
-
-type sparseFileReadTest struct {
- sparseData []byte
- sparseMap []sparseEntry
- realSize int64
- expected []byte
-}
-
-var sparseFileReadTests = []sparseFileReadTest{
- {
- sparseData: []byte("abcde"),
- sparseMap: []sparseEntry{
- {offset: 0, numBytes: 2},
- {offset: 5, numBytes: 3},
- },
- realSize: 8,
- expected: []byte("ab\x00\x00\x00cde"),
- },
- {
- sparseData: []byte("abcde"),
- sparseMap: []sparseEntry{
- {offset: 0, numBytes: 2},
- {offset: 5, numBytes: 3},
- },
- realSize: 10,
- expected: []byte("ab\x00\x00\x00cde\x00\x00"),
- },
- {
- sparseData: []byte("abcde"),
- sparseMap: []sparseEntry{
- {offset: 1, numBytes: 3},
- {offset: 6, numBytes: 2},
- },
- realSize: 8,
- expected: []byte("\x00abc\x00\x00de"),
- },
- {
- sparseData: []byte("abcde"),
- sparseMap: []sparseEntry{
- {offset: 1, numBytes: 3},
- {offset: 6, numBytes: 2},
- },
- realSize: 10,
- expected: []byte("\x00abc\x00\x00de\x00\x00"),
- },
- {
- sparseData: []byte(""),
- sparseMap: nil,
- realSize: 2,
- expected: []byte("\x00\x00"),
- },
-}
-
-func TestSparseFileReader(t *testing.T) {
- for i, test := range sparseFileReadTests {
- r := bytes.NewReader(test.sparseData)
- nb := int64(r.Len())
- sfr := &sparseFileReader{
- rfr: ®FileReader{r: r, nb: nb},
- sp: test.sparseMap,
- pos: 0,
- tot: test.realSize,
- }
- if sfr.numBytes() != nb {
- t.Errorf("test %d: Before reading, sfr.numBytes() = %d, want %d", i, sfr.numBytes(), nb)
- }
- buf, err := ioutil.ReadAll(sfr)
- if err != nil {
- t.Errorf("test %d: Unexpected error: %v", i, err)
- }
- if e := test.expected; !bytes.Equal(buf, e) {
- t.Errorf("test %d: Contents = %v, want %v", i, buf, e)
- }
- if sfr.numBytes() != 0 {
- t.Errorf("test %d: After draining the reader, numBytes() was nonzero", i)
- }
- }
-}
-
-func TestSparseIncrementalRead(t *testing.T) {
- sparseMap := []sparseEntry{{10, 2}}
- sparseData := []byte("Go")
- expected := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Go\x00\x00\x00\x00\x00\x00\x00\x00"
-
- r := bytes.NewReader(sparseData)
- nb := int64(r.Len())
- sfr := &sparseFileReader{
- rfr: ®FileReader{r: r, nb: nb},
- sp: sparseMap,
- pos: 0,
- tot: int64(len(expected)),
- }
-
- // We'll read the data 6 bytes at a time, with a hole of size 10 at
- // the beginning and one of size 8 at the end.
- var outputBuf bytes.Buffer
- buf := make([]byte, 6)
- for {
- n, err := sfr.Read(buf)
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Errorf("Read: unexpected error %v\n", err)
- }
- if n > 0 {
- _, err := outputBuf.Write(buf[:n])
- if err != nil {
- t.Errorf("Write: unexpected error %v\n", err)
- }
- }
- }
- got := outputBuf.String()
- if got != expected {
- t.Errorf("Contents = %v, want %v", got, expected)
- }
-}
-
-func TestReadGNUSparseMap0x1(t *testing.T) {
- headers := map[string]string{
- paxGNUSparseNumBlocks: "4",
- paxGNUSparseMap: "0,5,10,5,20,5,30,5",
- }
- expected := []sparseEntry{
- {offset: 0, numBytes: 5},
- {offset: 10, numBytes: 5},
- {offset: 20, numBytes: 5},
- {offset: 30, numBytes: 5},
- }
-
- sp, err := readGNUSparseMap0x1(headers)
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if !reflect.DeepEqual(sp, expected) {
- t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected)
- }
-}
-
-func TestReadGNUSparseMap1x0(t *testing.T) {
- // This test uses lots of holes so the sparse header takes up more than two blocks
- numEntries := 100
- expected := make([]sparseEntry, 0, numEntries)
- sparseMap := new(bytes.Buffer)
-
- fmt.Fprintf(sparseMap, "%d\n", numEntries)
- for i := 0; i < numEntries; i++ {
- offset := int64(2048 * i)
- numBytes := int64(1024)
- expected = append(expected, sparseEntry{offset: offset, numBytes: numBytes})
- fmt.Fprintf(sparseMap, "%d\n%d\n", offset, numBytes)
- }
-
- // Make the header the smallest multiple of blockSize that fits the sparseMap
- headerBlocks := (sparseMap.Len() + blockSize - 1) / blockSize
- bufLen := blockSize * headerBlocks
- buf := make([]byte, bufLen)
- copy(buf, sparseMap.Bytes())
-
- // Get an reader to read the sparse map
- r := bytes.NewReader(buf)
-
- // Read the sparse map
- sp, err := readGNUSparseMap1x0(r)
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if !reflect.DeepEqual(sp, expected) {
- t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected)
- }
-}
-
-func TestUninitializedRead(t *testing.T) {
- test := gnuTarTest
- f, err := os.Open(test.file)
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
- defer f.Close()
-
- tr := NewReader(f)
- _, err = tr.Read([]byte{})
- if err == nil || err != io.EOF {
- t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF)
- }
-
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go
deleted file mode 100644
index cf9cc79c5..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux dragonfly openbsd solaris
-
-package tar
-
-import (
- "syscall"
- "time"
-)
-
-func statAtime(st *syscall.Stat_t) time.Time {
- return time.Unix(st.Atim.Unix())
-}
-
-func statCtime(st *syscall.Stat_t) time.Time {
- return time.Unix(st.Ctim.Unix())
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go
deleted file mode 100644
index 6f17dbe30..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build darwin freebsd netbsd
-
-package tar
-
-import (
- "syscall"
- "time"
-)
-
-func statAtime(st *syscall.Stat_t) time.Time {
- return time.Unix(st.Atimespec.Unix())
-}
-
-func statCtime(st *syscall.Stat_t) time.Time {
- return time.Unix(st.Ctimespec.Unix())
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go
deleted file mode 100644
index cb843db4c..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux darwin dragonfly freebsd openbsd netbsd solaris
-
-package tar
-
-import (
- "os"
- "syscall"
-)
-
-func init() {
- sysStat = statUnix
-}
-
-func statUnix(fi os.FileInfo, h *Header) error {
- sys, ok := fi.Sys().(*syscall.Stat_t)
- if !ok {
- return nil
- }
- h.Uid = int(sys.Uid)
- h.Gid = int(sys.Gid)
- // TODO(bradfitz): populate username & group. os/user
- // doesn't cache LookupId lookups, and lacks group
- // lookup functions.
- h.AccessTime = statAtime(sys)
- h.ChangeTime = statCtime(sys)
- // TODO(bradfitz): major/minor device numbers?
- return nil
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go
deleted file mode 100644
index ed333f3ea..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go
+++ /dev/null
@@ -1,284 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tar
-
-import (
- "bytes"
- "io/ioutil"
- "os"
- "path"
- "reflect"
- "strings"
- "testing"
- "time"
-)
-
-func TestFileInfoHeader(t *testing.T) {
- fi, err := os.Stat("testdata/small.txt")
- if err != nil {
- t.Fatal(err)
- }
- h, err := FileInfoHeader(fi, "")
- if err != nil {
- t.Fatalf("FileInfoHeader: %v", err)
- }
- if g, e := h.Name, "small.txt"; g != e {
- t.Errorf("Name = %q; want %q", g, e)
- }
- if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISREG; g != e {
- t.Errorf("Mode = %#o; want %#o", g, e)
- }
- if g, e := h.Size, int64(5); g != e {
- t.Errorf("Size = %v; want %v", g, e)
- }
- if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
- t.Errorf("ModTime = %v; want %v", g, e)
- }
- // FileInfoHeader should error when passing nil FileInfo
- if _, err := FileInfoHeader(nil, ""); err == nil {
- t.Fatalf("Expected error when passing nil to FileInfoHeader")
- }
-}
-
-func TestFileInfoHeaderDir(t *testing.T) {
- fi, err := os.Stat("testdata")
- if err != nil {
- t.Fatal(err)
- }
- h, err := FileInfoHeader(fi, "")
- if err != nil {
- t.Fatalf("FileInfoHeader: %v", err)
- }
- if g, e := h.Name, "testdata/"; g != e {
- t.Errorf("Name = %q; want %q", g, e)
- }
- // Ignoring c_ISGID for golang.org/issue/4867
- if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm())|c_ISDIR; g != e {
- t.Errorf("Mode = %#o; want %#o", g, e)
- }
- if g, e := h.Size, int64(0); g != e {
- t.Errorf("Size = %v; want %v", g, e)
- }
- if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
- t.Errorf("ModTime = %v; want %v", g, e)
- }
-}
-
-func TestFileInfoHeaderSymlink(t *testing.T) {
- h, err := FileInfoHeader(symlink{}, "some-target")
- if err != nil {
- t.Fatal(err)
- }
- if g, e := h.Name, "some-symlink"; g != e {
- t.Errorf("Name = %q; want %q", g, e)
- }
- if g, e := h.Linkname, "some-target"; g != e {
- t.Errorf("Linkname = %q; want %q", g, e)
- }
-}
-
-type symlink struct{}
-
-func (symlink) Name() string { return "some-symlink" }
-func (symlink) Size() int64 { return 0 }
-func (symlink) Mode() os.FileMode { return os.ModeSymlink }
-func (symlink) ModTime() time.Time { return time.Time{} }
-func (symlink) IsDir() bool { return false }
-func (symlink) Sys() interface{} { return nil }
-
-func TestRoundTrip(t *testing.T) {
- data := []byte("some file contents")
-
- var b bytes.Buffer
- tw := NewWriter(&b)
- hdr := &Header{
- Name: "file.txt",
- Uid: 1 << 21, // too big for 8 octal digits
- Size: int64(len(data)),
- ModTime: time.Now(),
- }
- // tar only supports second precision.
- hdr.ModTime = hdr.ModTime.Add(-time.Duration(hdr.ModTime.Nanosecond()) * time.Nanosecond)
- if err := tw.WriteHeader(hdr); err != nil {
- t.Fatalf("tw.WriteHeader: %v", err)
- }
- if _, err := tw.Write(data); err != nil {
- t.Fatalf("tw.Write: %v", err)
- }
- if err := tw.Close(); err != nil {
- t.Fatalf("tw.Close: %v", err)
- }
-
- // Read it back.
- tr := NewReader(&b)
- rHdr, err := tr.Next()
- if err != nil {
- t.Fatalf("tr.Next: %v", err)
- }
- if !reflect.DeepEqual(rHdr, hdr) {
- t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
- }
- rData, err := ioutil.ReadAll(tr)
- if err != nil {
- t.Fatalf("Read: %v", err)
- }
- if !bytes.Equal(rData, data) {
- t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
- }
-}
-
-type headerRoundTripTest struct {
- h *Header
- fm os.FileMode
-}
-
-func TestHeaderRoundTrip(t *testing.T) {
- golden := []headerRoundTripTest{
- // regular file.
- {
- h: &Header{
- Name: "test.txt",
- Mode: 0644 | c_ISREG,
- Size: 12,
- ModTime: time.Unix(1360600916, 0),
- Typeflag: TypeReg,
- },
- fm: 0644,
- },
- // hard link.
- {
- h: &Header{
- Name: "hard.txt",
- Mode: 0644 | c_ISLNK,
- Size: 0,
- ModTime: time.Unix(1360600916, 0),
- Typeflag: TypeLink,
- },
- fm: 0644 | os.ModeSymlink,
- },
- // symbolic link.
- {
- h: &Header{
- Name: "link.txt",
- Mode: 0777 | c_ISLNK,
- Size: 0,
- ModTime: time.Unix(1360600852, 0),
- Typeflag: TypeSymlink,
- },
- fm: 0777 | os.ModeSymlink,
- },
- // character device node.
- {
- h: &Header{
- Name: "dev/null",
- Mode: 0666 | c_ISCHR,
- Size: 0,
- ModTime: time.Unix(1360578951, 0),
- Typeflag: TypeChar,
- },
- fm: 0666 | os.ModeDevice | os.ModeCharDevice,
- },
- // block device node.
- {
- h: &Header{
- Name: "dev/sda",
- Mode: 0660 | c_ISBLK,
- Size: 0,
- ModTime: time.Unix(1360578954, 0),
- Typeflag: TypeBlock,
- },
- fm: 0660 | os.ModeDevice,
- },
- // directory.
- {
- h: &Header{
- Name: "dir/",
- Mode: 0755 | c_ISDIR,
- Size: 0,
- ModTime: time.Unix(1360601116, 0),
- Typeflag: TypeDir,
- },
- fm: 0755 | os.ModeDir,
- },
- // fifo node.
- {
- h: &Header{
- Name: "dev/initctl",
- Mode: 0600 | c_ISFIFO,
- Size: 0,
- ModTime: time.Unix(1360578949, 0),
- Typeflag: TypeFifo,
- },
- fm: 0600 | os.ModeNamedPipe,
- },
- // setuid.
- {
- h: &Header{
- Name: "bin/su",
- Mode: 0755 | c_ISREG | c_ISUID,
- Size: 23232,
- ModTime: time.Unix(1355405093, 0),
- Typeflag: TypeReg,
- },
- fm: 0755 | os.ModeSetuid,
- },
- // setguid.
- {
- h: &Header{
- Name: "group.txt",
- Mode: 0750 | c_ISREG | c_ISGID,
- Size: 0,
- ModTime: time.Unix(1360602346, 0),
- Typeflag: TypeReg,
- },
- fm: 0750 | os.ModeSetgid,
- },
- // sticky.
- {
- h: &Header{
- Name: "sticky.txt",
- Mode: 0600 | c_ISREG | c_ISVTX,
- Size: 7,
- ModTime: time.Unix(1360602540, 0),
- Typeflag: TypeReg,
- },
- fm: 0600 | os.ModeSticky,
- },
- }
-
- for i, g := range golden {
- fi := g.h.FileInfo()
- h2, err := FileInfoHeader(fi, "")
- if err != nil {
- t.Error(err)
- continue
- }
- if strings.Contains(fi.Name(), "/") {
- t.Errorf("FileInfo of %q contains slash: %q", g.h.Name, fi.Name())
- }
- name := path.Base(g.h.Name)
- if fi.IsDir() {
- name += "/"
- }
- if got, want := h2.Name, name; got != want {
- t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
- }
- if got, want := h2.Size, g.h.Size; got != want {
- t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
- }
- if got, want := h2.Mode, g.h.Mode; got != want {
- t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
- }
- if got, want := fi.Mode(), g.fm; got != want {
- t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
- }
- if got, want := h2.ModTime, g.h.ModTime; got != want {
- t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
- }
- if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h {
- t.Errorf("i=%d: Sys didn't return original *Header", i)
- }
- }
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar
deleted file mode 100644
index fc899dc8d..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar
deleted file mode 100644
index cc9cfaa33..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar
deleted file mode 100644
index 9bc24b658..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt
deleted file mode 100644
index b249bfc51..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt
+++ /dev/null
@@ -1 +0,0 @@
-Kilts
\ No newline at end of file
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt
deleted file mode 100644
index 394ee3ecd..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt
+++ /dev/null
@@ -1 +0,0 @@
-Google.com
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar
deleted file mode 100644
index 8bd4e74d5..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar
deleted file mode 100644
index 59e2d4e60..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar
deleted file mode 100644
index 29679d9a3..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar
deleted file mode 100644
index eb65fc941..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar
deleted file mode 100644
index 5960ee824..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar
deleted file mode 100644
index 753e883ce..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar
deleted file mode 100644
index e6d816ad0..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar
deleted file mode 100644
index 9701950ed..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go
deleted file mode 100644
index dafb2cabf..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tar
-
-// TODO(dsymonds):
-// - catch more errors (no first header, etc.)
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "os"
- "path"
- "strconv"
- "strings"
- "time"
-)
-
-var (
- ErrWriteTooLong = errors.New("archive/tar: write too long")
- ErrFieldTooLong = errors.New("archive/tar: header field too long")
- ErrWriteAfterClose = errors.New("archive/tar: write after close")
- errNameTooLong = errors.New("archive/tar: name too long")
- errInvalidHeader = errors.New("archive/tar: header field too long or contains invalid values")
-)
-
-// A Writer provides sequential writing of a tar archive in POSIX.1 format.
-// A tar archive consists of a sequence of files.
-// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
-// writing at most hdr.Size bytes in total.
-type Writer struct {
- w io.Writer
- err error
- nb int64 // number of unwritten bytes for current file entry
- pad int64 // amount of padding to write after current file entry
- closed bool
- usedBinary bool // whether the binary numeric field extension was used
- preferPax bool // use pax header instead of binary numeric header
- hdrBuff [blockSize]byte // buffer to use in writeHeader when writing a regular header
- paxHdrBuff [blockSize]byte // buffer to use in writeHeader when writing a pax header
-}
-
-// NewWriter creates a new Writer writing to w.
-func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
-
-// Flush finishes writing the current file (optional).
-func (tw *Writer) Flush() error {
- if tw.nb > 0 {
- tw.err = fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb)
- return tw.err
- }
-
- n := tw.nb + tw.pad
- for n > 0 && tw.err == nil {
- nr := n
- if nr > blockSize {
- nr = blockSize
- }
- var nw int
- nw, tw.err = tw.w.Write(zeroBlock[0:nr])
- n -= int64(nw)
- }
- tw.nb = 0
- tw.pad = 0
- return tw.err
-}
-
-// Write s into b, terminating it with a NUL if there is room.
-// If the value is too long for the field and allowPax is true add a paxheader record instead
-func (tw *Writer) cString(b []byte, s string, allowPax bool, paxKeyword string, paxHeaders map[string]string) {
- needsPaxHeader := allowPax && len(s) > len(b) || !isASCII(s)
- if needsPaxHeader {
- paxHeaders[paxKeyword] = s
- return
- }
- if len(s) > len(b) {
- if tw.err == nil {
- tw.err = ErrFieldTooLong
- }
- return
- }
- ascii := toASCII(s)
- copy(b, ascii)
- if len(ascii) < len(b) {
- b[len(ascii)] = 0
- }
-}
-
-// Encode x as an octal ASCII string and write it into b with leading zeros.
-func (tw *Writer) octal(b []byte, x int64) {
- s := strconv.FormatInt(x, 8)
- // leading zeros, but leave room for a NUL.
- for len(s)+1 < len(b) {
- s = "0" + s
- }
- tw.cString(b, s, false, paxNone, nil)
-}
-
-// Write x into b, either as octal or as binary (GNUtar/star extension).
-// If the value is too long for the field and writingPax is enabled both for the field and the add a paxheader record instead
-func (tw *Writer) numeric(b []byte, x int64, allowPax bool, paxKeyword string, paxHeaders map[string]string) {
- // Try octal first.
- s := strconv.FormatInt(x, 8)
- if len(s) < len(b) {
- tw.octal(b, x)
- return
- }
-
- // If it is too long for octal, and pax is preferred, use a pax header
- if allowPax && tw.preferPax {
- tw.octal(b, 0)
- s := strconv.FormatInt(x, 10)
- paxHeaders[paxKeyword] = s
- return
- }
-
- // Too big: use binary (big-endian).
- tw.usedBinary = true
- for i := len(b) - 1; x > 0 && i >= 0; i-- {
- b[i] = byte(x)
- x >>= 8
- }
- b[0] |= 0x80 // highest bit indicates binary format
-}
-
-var (
- minTime = time.Unix(0, 0)
- // There is room for 11 octal digits (33 bits) of mtime.
- maxTime = minTime.Add((1<<33 - 1) * time.Second)
-)
-
-// WriteHeader writes hdr and prepares to accept the file's contents.
-// WriteHeader calls Flush if it is not the first header.
-// Calling after a Close will return ErrWriteAfterClose.
-func (tw *Writer) WriteHeader(hdr *Header) error {
- return tw.writeHeader(hdr, true)
-}
-
-// WriteHeader writes hdr and prepares to accept the file's contents.
-// WriteHeader calls Flush if it is not the first header.
-// Calling after a Close will return ErrWriteAfterClose.
-// As this method is called internally by writePax header to allow it to
-// suppress writing the pax header.
-func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
- if tw.closed {
- return ErrWriteAfterClose
- }
- if tw.err == nil {
- tw.Flush()
- }
- if tw.err != nil {
- return tw.err
- }
-
- // a map to hold pax header records, if any are needed
- paxHeaders := make(map[string]string)
-
- // TODO(shanemhansen): we might want to use PAX headers for
- // subsecond time resolution, but for now let's just capture
- // too long fields or non ascii characters
-
- var header []byte
-
- // We need to select which scratch buffer to use carefully,
- // since this method is called recursively to write PAX headers.
- // If allowPax is true, this is the non-recursive call, and we will use hdrBuff.
- // If allowPax is false, we are being called by writePAXHeader, and hdrBuff is
- // already being used by the non-recursive call, so we must use paxHdrBuff.
- header = tw.hdrBuff[:]
- if !allowPax {
- header = tw.paxHdrBuff[:]
- }
- copy(header, zeroBlock)
- s := slicer(header)
-
- // keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
- pathHeaderBytes := s.next(fileNameSize)
-
- tw.cString(pathHeaderBytes, hdr.Name, true, paxPath, paxHeaders)
-
- // Handle out of range ModTime carefully.
- var modTime int64
- if !hdr.ModTime.Before(minTime) && !hdr.ModTime.After(maxTime) {
- modTime = hdr.ModTime.Unix()
- }
-
- tw.octal(s.next(8), hdr.Mode) // 100:108
- tw.numeric(s.next(8), int64(hdr.Uid), true, paxUid, paxHeaders) // 108:116
- tw.numeric(s.next(8), int64(hdr.Gid), true, paxGid, paxHeaders) // 116:124
- tw.numeric(s.next(12), hdr.Size, true, paxSize, paxHeaders) // 124:136
- tw.numeric(s.next(12), modTime, false, paxNone, nil) // 136:148 --- consider using pax for finer granularity
- s.next(8) // chksum (148:156)
- s.next(1)[0] = hdr.Typeflag // 156:157
-
- tw.cString(s.next(100), hdr.Linkname, true, paxLinkpath, paxHeaders)
-
- copy(s.next(8), []byte("ustar\x0000")) // 257:265
- tw.cString(s.next(32), hdr.Uname, true, paxUname, paxHeaders) // 265:297
- tw.cString(s.next(32), hdr.Gname, true, paxGname, paxHeaders) // 297:329
- tw.numeric(s.next(8), hdr.Devmajor, false, paxNone, nil) // 329:337
- tw.numeric(s.next(8), hdr.Devminor, false, paxNone, nil) // 337:345
-
- // keep a reference to the prefix to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
- prefixHeaderBytes := s.next(155)
- tw.cString(prefixHeaderBytes, "", false, paxNone, nil) // 345:500 prefix
-
- // Use the GNU magic instead of POSIX magic if we used any GNU extensions.
- if tw.usedBinary {
- copy(header[257:265], []byte("ustar \x00"))
- }
-
- _, paxPathUsed := paxHeaders[paxPath]
- // try to use a ustar header when only the name is too long
- if !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed {
- suffix := hdr.Name
- prefix := ""
- if len(hdr.Name) > fileNameSize && isASCII(hdr.Name) {
- var err error
- prefix, suffix, err = tw.splitUSTARLongName(hdr.Name)
- if err == nil {
- // ok we can use a ustar long name instead of pax, now correct the fields
-
- // remove the path field from the pax header. this will suppress the pax header
- delete(paxHeaders, paxPath)
-
- // update the path fields
- tw.cString(pathHeaderBytes, suffix, false, paxNone, nil)
- tw.cString(prefixHeaderBytes, prefix, false, paxNone, nil)
-
- // Use the ustar magic if we used ustar long names.
- if len(prefix) > 0 && !tw.usedBinary {
- copy(header[257:265], []byte("ustar\x00"))
- }
- }
- }
- }
-
- // The chksum field is terminated by a NUL and a space.
- // This is different from the other octal fields.
- chksum, _ := checksum(header)
- tw.octal(header[148:155], chksum)
- header[155] = ' '
-
- if tw.err != nil {
- // problem with header; probably integer too big for a field.
- return tw.err
- }
-
- if allowPax {
- for k, v := range hdr.Xattrs {
- paxHeaders[paxXattr+k] = v
- }
- }
-
- if len(paxHeaders) > 0 {
- if !allowPax {
- return errInvalidHeader
- }
- if err := tw.writePAXHeader(hdr, paxHeaders); err != nil {
- return err
- }
- }
- tw.nb = int64(hdr.Size)
- tw.pad = (blockSize - (tw.nb % blockSize)) % blockSize
-
- _, tw.err = tw.w.Write(header)
- return tw.err
-}
-
-// writeUSTARLongName splits a USTAR long name hdr.Name.
-// name must be < 256 characters. errNameTooLong is returned
-// if hdr.Name can't be split. The splitting heuristic
-// is compatible with gnu tar.
-func (tw *Writer) splitUSTARLongName(name string) (prefix, suffix string, err error) {
- length := len(name)
- if length > fileNamePrefixSize+1 {
- length = fileNamePrefixSize + 1
- } else if name[length-1] == '/' {
- length--
- }
- i := strings.LastIndex(name[:length], "/")
- // nlen contains the resulting length in the name field.
- // plen contains the resulting length in the prefix field.
- nlen := len(name) - i - 1
- plen := i
- if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize {
- err = errNameTooLong
- return
- }
- prefix, suffix = name[:i], name[i+1:]
- return
-}
-
-// writePaxHeader writes an extended pax header to the
-// archive.
-func (tw *Writer) writePAXHeader(hdr *Header, paxHeaders map[string]string) error {
- // Prepare extended header
- ext := new(Header)
- ext.Typeflag = TypeXHeader
- // Setting ModTime is required for reader parsing to
- // succeed, and seems harmless enough.
- ext.ModTime = hdr.ModTime
- // The spec asks that we namespace our pseudo files
- // with the current pid.
- pid := os.Getpid()
- dir, file := path.Split(hdr.Name)
- fullName := path.Join(dir,
- fmt.Sprintf("PaxHeaders.%d", pid), file)
-
- ascii := toASCII(fullName)
- if len(ascii) > 100 {
- ascii = ascii[:100]
- }
- ext.Name = ascii
- // Construct the body
- var buf bytes.Buffer
-
- for k, v := range paxHeaders {
- fmt.Fprint(&buf, paxHeader(k+"="+v))
- }
-
- ext.Size = int64(len(buf.Bytes()))
- if err := tw.writeHeader(ext, false); err != nil {
- return err
- }
- if _, err := tw.Write(buf.Bytes()); err != nil {
- return err
- }
- if err := tw.Flush(); err != nil {
- return err
- }
- return nil
-}
-
-// paxHeader formats a single pax record, prefixing it with the appropriate length
-func paxHeader(msg string) string {
- const padding = 2 // Extra padding for space and newline
- size := len(msg) + padding
- size += len(strconv.Itoa(size))
- record := fmt.Sprintf("%d %s\n", size, msg)
- if len(record) != size {
- // Final adjustment if adding size increased
- // the number of digits in size
- size = len(record)
- record = fmt.Sprintf("%d %s\n", size, msg)
- }
- return record
-}
-
-// Write writes to the current entry in the tar archive.
-// Write returns the error ErrWriteTooLong if more than
-// hdr.Size bytes are written after WriteHeader.
-func (tw *Writer) Write(b []byte) (n int, err error) {
- if tw.closed {
- err = ErrWriteTooLong
- return
- }
- overwrite := false
- if int64(len(b)) > tw.nb {
- b = b[0:tw.nb]
- overwrite = true
- }
- n, err = tw.w.Write(b)
- tw.nb -= int64(n)
- if err == nil && overwrite {
- err = ErrWriteTooLong
- return
- }
- tw.err = err
- return
-}
-
-// Close closes the tar archive, flushing any unwritten
-// data to the underlying writer.
-func (tw *Writer) Close() error {
- if tw.err != nil || tw.closed {
- return tw.err
- }
- tw.Flush()
- tw.closed = true
- if tw.err != nil {
- return tw.err
- }
-
- // trailer: two zero blocks
- for i := 0; i < 2; i++ {
- _, tw.err = tw.w.Write(zeroBlock)
- if tw.err != nil {
- break
- }
- }
- return tw.err
-}
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go
deleted file mode 100644
index 5e42e322f..000000000
--- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go
+++ /dev/null
@@ -1,491 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tar
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "reflect"
- "strings"
- "testing"
- "testing/iotest"
- "time"
-)
-
-type writerTestEntry struct {
- header *Header
- contents string
-}
-
-type writerTest struct {
- file string // filename of expected output
- entries []*writerTestEntry
-}
-
-var writerTests = []*writerTest{
- // The writer test file was produced with this command:
- // tar (GNU tar) 1.26
- // ln -s small.txt link.txt
- // tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
- {
- file: "testdata/writer.tar",
- entries: []*writerTestEntry{
- {
- header: &Header{
- Name: "small.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 5,
- ModTime: time.Unix(1246508266, 0),
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- },
- contents: "Kilts",
- },
- {
- header: &Header{
- Name: "small2.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 11,
- ModTime: time.Unix(1245217492, 0),
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- },
- contents: "Google.com\n",
- },
- {
- header: &Header{
- Name: "link.txt",
- Mode: 0777,
- Uid: 1000,
- Gid: 1000,
- Size: 0,
- ModTime: time.Unix(1314603082, 0),
- Typeflag: '2',
- Linkname: "small.txt",
- Uname: "strings",
- Gname: "strings",
- },
- // no contents
- },
- },
- },
- // The truncated test file was produced using these commands:
- // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
- // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
- {
- file: "testdata/writer-big.tar",
- entries: []*writerTestEntry{
- {
- header: &Header{
- Name: "tmp/16gig.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 16 << 30,
- ModTime: time.Unix(1254699560, 0),
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- },
- // fake contents
- contents: strings.Repeat("\x00", 4<<10),
- },
- },
- },
- // The truncated test file was produced using these commands:
- // dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
- // tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
- {
- file: "testdata/writer-big-long.tar",
- entries: []*writerTestEntry{
- {
- header: &Header{
- Name: strings.Repeat("longname/", 15) + "16gig.txt",
- Mode: 0644,
- Uid: 1000,
- Gid: 1000,
- Size: 16 << 30,
- ModTime: time.Unix(1399583047, 0),
- Typeflag: '0',
- Uname: "guillaume",
- Gname: "guillaume",
- },
- // fake contents
- contents: strings.Repeat("\x00", 4<<10),
- },
- },
- },
- // This file was produced using gnu tar 1.17
- // gnutar -b 4 --format=ustar (longname/)*15 + file.txt
- {
- file: "testdata/ustar.tar",
- entries: []*writerTestEntry{
- {
- header: &Header{
- Name: strings.Repeat("longname/", 15) + "file.txt",
- Mode: 0644,
- Uid: 0765,
- Gid: 024,
- Size: 06,
- ModTime: time.Unix(1360135598, 0),
- Typeflag: '0',
- Uname: "shane",
- Gname: "staff",
- },
- contents: "hello\n",
- },
- },
- },
-}
-
-// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
-func bytestr(offset int, b []byte) string {
- const rowLen = 32
- s := fmt.Sprintf("%04x ", offset)
- for _, ch := range b {
- switch {
- case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
- s += fmt.Sprintf(" %c", ch)
- default:
- s += fmt.Sprintf(" %02x", ch)
- }
- }
- return s
-}
-
-// Render a pseudo-diff between two blocks of bytes.
-func bytediff(a []byte, b []byte) string {
- const rowLen = 32
- s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
- for offset := 0; len(a)+len(b) > 0; offset += rowLen {
- na, nb := rowLen, rowLen
- if na > len(a) {
- na = len(a)
- }
- if nb > len(b) {
- nb = len(b)
- }
- sa := bytestr(offset, a[0:na])
- sb := bytestr(offset, b[0:nb])
- if sa != sb {
- s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
- }
- a = a[na:]
- b = b[nb:]
- }
- return s
-}
-
-func TestWriter(t *testing.T) {
-testLoop:
- for i, test := range writerTests {
- expected, err := ioutil.ReadFile(test.file)
- if err != nil {
- t.Errorf("test %d: Unexpected error: %v", i, err)
- continue
- }
-
- buf := new(bytes.Buffer)
- tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
- big := false
- for j, entry := range test.entries {
- big = big || entry.header.Size > 1<<10
- if err := tw.WriteHeader(entry.header); err != nil {
- t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
- continue testLoop
- }
- if _, err := io.WriteString(tw, entry.contents); err != nil {
- t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
- continue testLoop
- }
- }
- // Only interested in Close failures for the small tests.
- if err := tw.Close(); err != nil && !big {
- t.Errorf("test %d: Failed closing archive: %v", i, err)
- continue testLoop
- }
-
- actual := buf.Bytes()
- if !bytes.Equal(expected, actual) {
- t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
- i, bytediff(expected, actual))
- }
- if testing.Short() { // The second test is expensive.
- break
- }
- }
-}
-
-func TestPax(t *testing.T) {
- // Create an archive with a large name
- fileinfo, err := os.Stat("testdata/small.txt")
- if err != nil {
- t.Fatal(err)
- }
- hdr, err := FileInfoHeader(fileinfo, "")
- if err != nil {
- t.Fatalf("os.Stat: %v", err)
- }
- // Force a PAX long name to be written
- longName := strings.Repeat("ab", 100)
- contents := strings.Repeat(" ", int(hdr.Size))
- hdr.Name = longName
- var buf bytes.Buffer
- writer := NewWriter(&buf)
- if err := writer.WriteHeader(hdr); err != nil {
- t.Fatal(err)
- }
- if _, err = writer.Write([]byte(contents)); err != nil {
- t.Fatal(err)
- }
- if err := writer.Close(); err != nil {
- t.Fatal(err)
- }
- // Simple test to make sure PAX extensions are in effect
- if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
- t.Fatal("Expected at least one PAX header to be written.")
- }
- // Test that we can get a long name back out of the archive.
- reader := NewReader(&buf)
- hdr, err = reader.Next()
- if err != nil {
- t.Fatal(err)
- }
- if hdr.Name != longName {
- t.Fatal("Couldn't recover long file name")
- }
-}
-
-func TestPaxSymlink(t *testing.T) {
- // Create an archive with a large linkname
- fileinfo, err := os.Stat("testdata/small.txt")
- if err != nil {
- t.Fatal(err)
- }
- hdr, err := FileInfoHeader(fileinfo, "")
- hdr.Typeflag = TypeSymlink
- if err != nil {
- t.Fatalf("os.Stat:1 %v", err)
- }
- // Force a PAX long linkname to be written
- longLinkname := strings.Repeat("1234567890/1234567890", 10)
- hdr.Linkname = longLinkname
-
- hdr.Size = 0
- var buf bytes.Buffer
- writer := NewWriter(&buf)
- if err := writer.WriteHeader(hdr); err != nil {
- t.Fatal(err)
- }
- if err := writer.Close(); err != nil {
- t.Fatal(err)
- }
- // Simple test to make sure PAX extensions are in effect
- if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
- t.Fatal("Expected at least one PAX header to be written.")
- }
- // Test that we can get a long name back out of the archive.
- reader := NewReader(&buf)
- hdr, err = reader.Next()
- if err != nil {
- t.Fatal(err)
- }
- if hdr.Linkname != longLinkname {
- t.Fatal("Couldn't recover long link name")
- }
-}
-
-func TestPaxNonAscii(t *testing.T) {
- // Create an archive with non ascii. These should trigger a pax header
- // because pax headers have a defined utf-8 encoding.
- fileinfo, err := os.Stat("testdata/small.txt")
- if err != nil {
- t.Fatal(err)
- }
-
- hdr, err := FileInfoHeader(fileinfo, "")
- if err != nil {
- t.Fatalf("os.Stat:1 %v", err)
- }
-
- // some sample data
- chineseFilename := "文件名"
- chineseGroupname := "組"
- chineseUsername := "用戶名"
-
- hdr.Name = chineseFilename
- hdr.Gname = chineseGroupname
- hdr.Uname = chineseUsername
-
- contents := strings.Repeat(" ", int(hdr.Size))
-
- var buf bytes.Buffer
- writer := NewWriter(&buf)
- if err := writer.WriteHeader(hdr); err != nil {
- t.Fatal(err)
- }
- if _, err = writer.Write([]byte(contents)); err != nil {
- t.Fatal(err)
- }
- if err := writer.Close(); err != nil {
- t.Fatal(err)
- }
- // Simple test to make sure PAX extensions are in effect
- if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
- t.Fatal("Expected at least one PAX header to be written.")
- }
- // Test that we can get a long name back out of the archive.
- reader := NewReader(&buf)
- hdr, err = reader.Next()
- if err != nil {
- t.Fatal(err)
- }
- if hdr.Name != chineseFilename {
- t.Fatal("Couldn't recover unicode name")
- }
- if hdr.Gname != chineseGroupname {
- t.Fatal("Couldn't recover unicode group")
- }
- if hdr.Uname != chineseUsername {
- t.Fatal("Couldn't recover unicode user")
- }
-}
-
-func TestPaxXattrs(t *testing.T) {
- xattrs := map[string]string{
- "user.key": "value",
- }
-
- // Create an archive with an xattr
- fileinfo, err := os.Stat("testdata/small.txt")
- if err != nil {
- t.Fatal(err)
- }
- hdr, err := FileInfoHeader(fileinfo, "")
- if err != nil {
- t.Fatalf("os.Stat: %v", err)
- }
- contents := "Kilts"
- hdr.Xattrs = xattrs
- var buf bytes.Buffer
- writer := NewWriter(&buf)
- if err := writer.WriteHeader(hdr); err != nil {
- t.Fatal(err)
- }
- if _, err = writer.Write([]byte(contents)); err != nil {
- t.Fatal(err)
- }
- if err := writer.Close(); err != nil {
- t.Fatal(err)
- }
- // Test that we can get the xattrs back out of the archive.
- reader := NewReader(&buf)
- hdr, err = reader.Next()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
- t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
- hdr.Xattrs, xattrs)
- }
-}
-
-func TestPAXHeader(t *testing.T) {
- medName := strings.Repeat("CD", 50)
- longName := strings.Repeat("AB", 100)
- paxTests := [][2]string{
- {paxPath + "=/etc/hosts", "19 path=/etc/hosts\n"},
- {"a=b", "6 a=b\n"}, // Single digit length
- {"a=names", "11 a=names\n"}, // Test case involving carries
- {paxPath + "=" + longName, fmt.Sprintf("210 path=%s\n", longName)},
- {paxPath + "=" + medName, fmt.Sprintf("110 path=%s\n", medName)}}
-
- for _, test := range paxTests {
- key, expected := test[0], test[1]
- if result := paxHeader(key); result != expected {
- t.Fatalf("paxHeader: got %s, expected %s", result, expected)
- }
- }
-}
-
-func TestUSTARLongName(t *testing.T) {
- // Create an archive with a path that failed to split with USTAR extension in previous versions.
- fileinfo, err := os.Stat("testdata/small.txt")
- if err != nil {
- t.Fatal(err)
- }
- hdr, err := FileInfoHeader(fileinfo, "")
- hdr.Typeflag = TypeDir
- if err != nil {
- t.Fatalf("os.Stat:1 %v", err)
- }
- // Force a PAX long name to be written. The name was taken from a practical example
- // that fails and replaced ever char through numbers to anonymize the sample.
- longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
- hdr.Name = longName
-
- hdr.Size = 0
- var buf bytes.Buffer
- writer := NewWriter(&buf)
- if err := writer.WriteHeader(hdr); err != nil {
- t.Fatal(err)
- }
- if err := writer.Close(); err != nil {
- t.Fatal(err)
- }
- // Test that we can get a long name back out of the archive.
- reader := NewReader(&buf)
- hdr, err = reader.Next()
- if err != nil {
- t.Fatal(err)
- }
- if hdr.Name != longName {
- t.Fatal("Couldn't recover long name")
- }
-}
-
-func TestValidTypeflagWithPAXHeader(t *testing.T) {
- var buffer bytes.Buffer
- tw := NewWriter(&buffer)
-
- fileName := strings.Repeat("ab", 100)
-
- hdr := &Header{
- Name: fileName,
- Size: 4,
- Typeflag: 0,
- }
- if err := tw.WriteHeader(hdr); err != nil {
- t.Fatalf("Failed to write header: %s", err)
- }
- if _, err := tw.Write([]byte("fooo")); err != nil {
- t.Fatalf("Failed to write the file's data: %s", err)
- }
- tw.Close()
-
- tr := NewReader(&buffer)
-
- for {
- header, err := tr.Next()
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Fatalf("Failed to read header: %s", err)
- }
- if header.Typeflag != 0 {
- t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
- }
- }
-}
diff --git a/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md b/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md
new file mode 100644
index 000000000..eb72bff93
--- /dev/null
+++ b/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md
@@ -0,0 +1,7 @@
+# 0.7.3
+
+formatter/\*: allow configuration of timestamp layout
+
+# 0.7.2
+
+formatter/text: Add configuration option for time format (#158)
diff --git a/vendor/src/github.com/Sirupsen/logrus/README.md b/vendor/src/github.com/Sirupsen/logrus/README.md
index 512f26e5e..d55f90924 100644
--- a/vendor/src/github.com/Sirupsen/logrus/README.md
+++ b/vendor/src/github.com/Sirupsen/logrus/README.md
@@ -37,11 +37,13 @@ attached, the output is compatible with the
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
```text
-time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
-time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
-time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
-time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
-time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
+time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
+time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
+time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
+time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
+time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
+time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
+exit status 1
```
#### Example
@@ -106,6 +108,16 @@ func main() {
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
+
+ // A common pattern is to re-use fields between logging statements by re-using
+ // the logrus.Entry returned from WithFields()
+ contextLogger := log.WithFields(log.Fields{
+ "common": "this is a common field",
+ "other": "I also should be logged always",
+ })
+
+ contextLogger.Info("I'll be logged with common and other field")
+ contextLogger.Info("Me too")
}
```
@@ -187,31 +199,18 @@ func init() {
}
```
-* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
- Send errors to an exception tracking service compatible with the Airbrake API.
- Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
-* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
- Send errors to the Papertrail hosted logging service via UDP.
-
-* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
- Send errors to remote syslog server.
- Uses standard library `log/syslog` behind the scenes.
-
-* [`github.com/Sirupsen/logrus/hooks/bugsnag`](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go)
- Send errors to the Bugsnag exception tracking service.
-
-* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
- Send errors to a channel in hipchat.
-
-* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
- Send logs to Loggly (https://www.loggly.com/)
-
-* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus)
- Hook for Slack chat.
-
-* [`github.com/wercker/journalhook`](https://github.com/wercker/journalhook).
- Hook for logging to `systemd-journald`.
+| Hook | Description |
+| ----- | ----------- |
+| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
+| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
+| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
+| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
+| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
+| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
+| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
+| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
+| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) |
#### Level logging
diff --git a/vendor/src/github.com/Sirupsen/logrus/formatter.go b/vendor/src/github.com/Sirupsen/logrus/formatter.go
index 038ce9fd2..104d689f1 100644
--- a/vendor/src/github.com/Sirupsen/logrus/formatter.go
+++ b/vendor/src/github.com/Sirupsen/logrus/formatter.go
@@ -1,5 +1,9 @@
package logrus
+import "time"
+
+const DefaultTimestampFormat = time.RFC3339
+
// The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones:
//
diff --git a/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go
index 34b1ccbca..8ea93ddf2 100644
--- a/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go
+++ b/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go
@@ -3,19 +3,27 @@ package logstash
import (
"encoding/json"
"fmt"
+
"github.com/Sirupsen/logrus"
- "time"
)
// Formatter generates json in logstash format.
// Logstash site: http://logstash.net/
type LogstashFormatter struct {
Type string // if not empty use for logstash type field.
+
+ // TimestampFormat sets the format used for timestamps.
+ TimestampFormat string
}
func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
entry.Data["@version"] = 1
- entry.Data["@timestamp"] = entry.Time.Format(time.RFC3339)
+
+ if f.TimestampFormat == "" {
+ f.TimestampFormat = logrus.DefaultTimestampFormat
+ }
+
+ entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
// set message field
v, ok := entry.Data["message"]
diff --git a/vendor/src/github.com/Sirupsen/logrus/json_formatter.go b/vendor/src/github.com/Sirupsen/logrus/json_formatter.go
index 5c4c44bbe..dcc4f1d9f 100644
--- a/vendor/src/github.com/Sirupsen/logrus/json_formatter.go
+++ b/vendor/src/github.com/Sirupsen/logrus/json_formatter.go
@@ -3,10 +3,12 @@ package logrus
import (
"encoding/json"
"fmt"
- "time"
)
-type JSONFormatter struct{}
+type JSONFormatter struct {
+ // TimestampFormat sets the format used for marshaling timestamps.
+ TimestampFormat string
+}
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data := make(Fields, len(entry.Data)+3)
@@ -21,7 +23,12 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
}
}
prefixFieldClashes(data)
- data["time"] = entry.Time.Format(time.RFC3339)
+
+ if f.TimestampFormat == "" {
+ f.TimestampFormat = DefaultTimestampFormat
+ }
+
+ data["time"] = entry.Time.Format(f.TimestampFormat)
data["msg"] = entry.Message
data["level"] = entry.Level.String()
diff --git a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go
index 0a06a1105..612417ff9 100644
--- a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go
+++ b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go
@@ -46,6 +46,9 @@ type TextFormatter struct {
// the time passed since beginning of execution.
FullTimestamp bool
+ // TimestampFormat to use for display when a full timestamp is printed
+ TimestampFormat string
+
// The fields are sorted by default for a consistent output. For applications
// that log extremely frequently and don't use the JSON formatter this may not
// be desired.
@@ -68,11 +71,14 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
+ if f.TimestampFormat == "" {
+ f.TimestampFormat = DefaultTimestampFormat
+ }
if isColored {
f.printColored(b, entry, keys)
} else {
if !f.DisableTimestamp {
- f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
+ f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat))
}
f.appendKeyValue(b, "level", entry.Level.String())
f.appendKeyValue(b, "msg", entry.Message)
@@ -103,7 +109,7 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
if !f.FullTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
} else {
- fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(time.RFC3339), entry.Message)
+ fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message)
}
for _, k := range keys {
v := entry.Data[k]
diff --git a/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go b/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go
index 28a949907..e25a44f67 100644
--- a/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go
+++ b/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go
@@ -3,8 +3,8 @@ package logrus
import (
"bytes"
"errors"
-
"testing"
+ "time"
)
func TestQuoting(t *testing.T) {
@@ -33,5 +33,29 @@ func TestQuoting(t *testing.T) {
checkQuoting(true, errors.New("invalid argument"))
}
+func TestTimestampFormat(t *testing.T) {
+ checkTimeStr := func(format string) {
+ customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
+ customStr, _ := customFormatter.Format(WithField("test", "test"))
+ timeStart := bytes.Index(customStr, ([]byte)("time="))
+ timeEnd := bytes.Index(customStr, ([]byte)("level="))
+ timeStr := customStr[timeStart+5 : timeEnd-1]
+ if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
+ timeStr = timeStr[1 : len(timeStr)-1]
+ }
+ if format == "" {
+ format = time.RFC3339
+ }
+ _, e := time.Parse(format, (string)(timeStr))
+ if e != nil {
+ t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
+ }
+ }
+
+ checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
+ checkTimeStr("Mon Jan _2 15:04:05 2006")
+ checkTimeStr("")
+}
+
// TODO add tests for sorting etc., this requires a parser for the text
// formatter output.
diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go
new file mode 100644
index 000000000..5f091bbc9
--- /dev/null
+++ b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go
@@ -0,0 +1,1459 @@
+package v2
+
+import (
+ "net/http"
+ "regexp"
+
+ "github.com/docker/distribution/digest"
+)
+
+var (
+ nameParameterDescriptor = ParameterDescriptor{
+ Name: "name",
+ Type: "string",
+ Format: RepositoryNameRegexp.String(),
+ Required: true,
+ Description: `Name of the target repository.`,
+ }
+
+ tagParameterDescriptor = ParameterDescriptor{
+ Name: "tag",
+ Type: "string",
+ Format: TagNameRegexp.String(),
+ Required: true,
+ Description: `Tag of the target manifiest.`,
+ }
+
+ uuidParameterDescriptor = ParameterDescriptor{
+ Name: "uuid",
+ Type: "opaque",
+ Required: true,
+ Description: `A uuid identifying the upload. This field can accept almost anything.`,
+ }
+
+ digestPathParameter = ParameterDescriptor{
+ Name: "digest",
+ Type: "path",
+ Required: true,
+ Format: digest.DigestRegexp.String(),
+ Description: `Digest of desired blob.`,
+ }
+
+ hostHeader = ParameterDescriptor{
+ Name: "Host",
+ Type: "string",
+ Description: "Standard HTTP Host Header. Should be set to the registry host.",
+ Format: "]