Compare commits

..

1 Commits

Author SHA1 Message Date
Arnaud Porterie 7e803ba6b2 Bump to version v1.5.0
Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
2015-01-22 13:30:47 -08:00
2685 changed files with 172686 additions and 368378 deletions
Executable
+14
View File
@@ -0,0 +1,14 @@
image: dockercore/docker
env:
- AUTO_GOPATH=1
- DOCKER_GRAPHDRIVER=vfs
- DOCKER_EXECDRIVER=native
script:
# Setup the DockerInDocker environment.
- hack/dind
# Tests relying on StartWithBusybox make Drone time out.
- rm integration-cli/docker_cli_daemon_test.go
- rm integration-cli/docker_cli_exec_test.go
# Validate and test.
- hack/make.sh validate-dco validate-gofmt
- hack/make.sh binary cross test-unit test-integration-cli test-integration test-docker-py
+21 -27
View File
@@ -1,37 +1,31 @@
# Docker project generated files to ignore
# if you want to ignore files created by your editor/tools,
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
*.exe
*.exe~
*.orig
*.rej
*.test
.*.swp
.DS_Store
.bashrc
.dotcloud
.flymake*
.git/
.gopath/
.hg/
.vagrant*
Vagrantfile
a.out
autogen/
bin
build_src
bundles/
docker/docker
docs/AWS_S3_BUCKET
docs/GITCOMMIT
docs/GIT_BRANCH
docs/VERSION
*.exe
.*.swp
a.out
*.orig
build_src
.flymake*
.idea
.DS_Store
docs/_build
docs/_static
docs/_templates
docs/changed-files
# generated by man/man/md2man-all.sh
man/man1
man/man5
pyenv
.gopath/
.dotcloud
*.test
bundles/
.hg/
.git/
vendor/pkg/
pyenv
Vagrantfile
docs/AWS_S3_BUCKET
docs/GIT_BRANCH
docs/VERSION
docs/GITCOMMIT
docs/changed-files
+3 -47
View File
@@ -1,4 +1,4 @@
# Generate AUTHORS: hack/generate-authors.sh
# Generate AUTHORS: project/generate-authors.sh
# Tip for finding duplicates (besides scanning the output of AUTHORS for name
# duplicates that aren't also email duplicates): scan the output of:
@@ -6,24 +6,6 @@
#
# For explanation on this file format: man git-shortlog
Patrick Stapleton <github@gdi2290.com>
Shishir Mahajan <shishir.mahajan@redhat.com> <smahajan@redhat.com>
Erwin van der Koogh <info@erronis.nl>
Ahmed Kamal <email.ahmedkamal@googlemail.com>
Tejesh Mehta <tejesh.mehta@gmail.com> <tj@init.me>
Cristian Staretu <cristian.staretu@gmail.com>
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
Marcus Linke <marcus.linke@gmx.de>
Aleksandrs Fadins <aleks@s-ko.net>
Christopher Latham <sudosurootdev@gmail.com>
Hu Keping <hukeping@huawei.com>
Wayne Chang <wayne@neverfear.org>
Chen Chao <cc272309126@gmail.com>
Daehyeok Mun <daehyeok@gmail.com>
<daehyeok@gmail.com> <daehyeok@daehyeokui-MacBook-Air.local>
<jt@yadutaf.fr> <admin@jtlebi.fr>
<jeff@docker.com> <jefferya@programmerq.net>
<charles.hooper@dotcloud.com> <chooper@plumata.com>
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
@@ -76,7 +58,7 @@ Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
<michael@docker.com> <michael@crosbymichael.com>
<michael@docker.com> <crosby.michael@gmail.com>
<michael@docker.com> <crosbymichael@gmail.com>
<github@developersupport.net> <github@metaliveblog.com>
<github@developersupport.net> <github@metaliveblog.com>
<brandon@ifup.org> <brandon@ifup.co>
<dano@spotify.com> <daniel.norberg@gmail.com>
<danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
@@ -92,6 +74,7 @@ Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
unclejack <unclejacksons@gmail.com> <unclejack@users.noreply.github.com>
<alexl@redhat.com> <alexander.larsson@gmail.com>
Alexandr Morozov <lk4d4math@gmail.com>
<git.nivoc@neverbox.com> <kuehnle@online.de>
@@ -142,30 +125,3 @@ Jessica Frazelle <jess@docker.com> Jessie Frazelle <jfrazelle@users.noreply.gith
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
<oi@truffles.me.uk> <timruffles@googlemail.com>
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
Antonio Murdaca <antonio.murdaca@gmail.com> <me@runcom.ninja>
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@linux.com>
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
Darren Shepherd <darren.s.shepherd@gmail.com> <darren@rancher.com>
Deshi Xiao <dxiao@redhat.com> <dsxiao@dataman-inc.com>
Deshi Xiao <dxiao@redhat.com> <xiaods@gmail.com>
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
Jacob Atzen <jacob@jacobatzen.dk> <jatzen@gmail.com>
Jeff Nickoloff <jeff.nickoloff@gmail.com> <jeff@allingeek.com>
<jess@docker.com> <princess@docker.com>
John Howard (VM) <John.Howard@microsoft.com> John Howard <jhoward@microsoft.com>
Madhu Venugopal <madhu@socketplane.io> <madhu@docker.com>
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
mattyw <mattyw@me.com> <gh@mattyw.net>
resouer <resouer@163.com> <resouer@gmail.com>
AJ Bowen <aj@gandi.net> soulshake <amy@gandi.net>
AJ Bowen <aj@gandi.net> soulshake <aj@gandi.net>
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
Tibor Vass <teabee89@gmail.com> <tiborvass@users.noreply.github.com>
Vincent Bernat <bernat@luffy.cx> <Vincent.Bernat@exoscale.ch>
Yestin Sun <sunyi0804@gmail.com> <yestin.sun@polyera.com>
bin liu <liubin0329@users.noreply.github.com> <liubin0329@gmail.com>
John Howard (VM) <John.Howard@microsoft.com> jhowardmsft <jhoward@microsoft.com>
Ankush Agarwal <ankushagarwal11@gmail.com> <ankushagarwal@users.noreply.github.com>
Tangi COLIN <tangicolin@gmail.com> tangicolin <tangicolin@gmail.com>
+60 -375
View File
File diff suppressed because it is too large Load Diff
+7 -194
View File
@@ -1,192 +1,6 @@
# Changelog
## 1.8.0 (2015-08-06)
### Distribution
+ Trusted pull, push and build, disabled by default
* Make tar layers deterministic between registries
* Don't allow deleting the image of running containers
* Check if a tag name to load is a valid digest
* Allow one character repository names
* Add a more accurate error description for invalid tag name
* Make build cache ignore mtime
### Cli
+ Add support for DOCKER_CONFIG/--config to specify config file dir
+ Add --type flag for docker inspect command
+ Add formatting options to `docker ps` with `--format`
+ Replace `docker -d` with new subcommand `docker daemon`
* Zsh completion updates and improvements
* Add some missing events to bash completion
* Support daemon urls with base paths in `docker -H`
* Validate status= filter to docker ps
* Display when a container is in --net=host in docker ps
* Extend docker inspect to export image metadata related to graph driver
* Restore --default-gateway{,-v6} daemon options
* Add missing unpublished ports in docker ps
* Allow duration strings in `docker events` as --since/--until
* Expose more mounts information in `docker inspect`
### Runtime
+ Add new Fluentd logging driver
+ Allow `docker import` to load from local files
+ Add logging driver for GELF via UDP
+ Allow to copy files from host to containers with `docker cp`
+ Promote volume drivers from experimental to master
+ Add rollover log driver, and --log-driver-opts flag
+ Add memory swappiness tuning options
* Remove cgroup read-only flag when privileged
* Make /proc, /sys, & /dev readonly for readonly containers
* Add cgroup bind mount by default
* Overlay: Export metadata for container and image in `docker inspect`
* Devicemapper: external device activation
* Devicemapper: Compare uuid of base device on startup
* Remove RC4 from the list of registry cipher suites
* Add syslog-facility option
* LXC execdriver compatibility with recent LXC versions
### Plugins
* Separate plugin sockets and specs locations
* Allow TLS connections to plugins
### Bug fixes
- Add missing 'Names' field to /containers/json API output
- Make `docker rmi --dangling` safe when pulling
- Devicemapper: Change default basesize to 100G
- Go Scheduler issue with sync.Mutex and gcc
- Fix issue where Search API endpoint would panic due to empty AuthConfig
- Set image canonical names correctly
- Check dockerinit only if lxc driver is used
- Fix ulimit usage of nproc
- Always attach STDIN if -i,--interactive is specified
- Show error messages when saving container state fails
- Fixed incorrect assumption on --bridge=none treated as disable network
- Check for invalid port specifications in host configuration
- Fix endpoint leave failure for --net=host mode
- Fix goroutine leak in the stats API if the container is not running
- Check for apparmor file before reading it
- Fix DOCKER_TLS_VERIFY being ignored
- Set umask to the default on startup
- Correct the message of pause and unpause a non-running container
- Adjust disallowed CpuShares in container creation
- ZFS: correctly apply selinux context
- Display empty string instead of <nil> when IP opt is nil
- `docker kill` returns error when container is not running
- Fix COPY/ADD quoted/json form
- Fix goroutine leak on logs -f with no output
- Remove panic in nat package on invalid hostport
- Fix container linking in Fedora 22
- Fix error caused using default gateways outside of the allocated range
- Format times in inspect command with a template as RFC3339Nano
- Make registry client to accept 2xx and 3xx http status responses as successful
- Fix race issue that caused the daemon to crash with certain layer downloads failed in a specific order.
- Fix error when the docker ps format was not valid.
- Remove redundant ip forward check.
- Fix issue trying to push images to repository mirrors.
- Fix error cleaning up network entrypoints when there is an initialization issue.
## 1.7.1 (2015-07-14)
#### Runtime
- Fix default user spawning exec process with `docker exec`
- Make `--bridge=none` not to configure the network bridge
- Publish networking stats properly
- Fix implicit devicemapper selection with static binaries
- Fix socket connections that hung intermittently
- Fix bridge interface creation on CentOS/RHEL 6.6
- Fix local dns lookups added to resolv.conf
- Fix copy command mounting volumes
- Fix read/write privileges in volumes mounted with --volumes-from
#### Remote API
- Fix unmarshalling of Command and Entrypoint
- Set limit for minimum client version supported
- Validate port specification
- Return proper errors when attach/reattach fail
#### Distribution
- Fix pulling private images
- Fix fallback between registry V2 and V1
## 1.7.0 (2015-06-16)
#### Runtime
+ Experimental feature: support for out-of-process volume plugins
* The userland proxy can be disabled in favor of hairpin NAT using the daemons `--userland-proxy=false` flag
* The `exec` command supports the `-u|--user` flag to specify the new process owner
+ Default gateway for containers can be specified daemon-wide using the `--default-gateway` and `--default-gateway-v6` flags
+ The CPU CFS (Completely Fair Scheduler) quota can be set in `docker run` using `--cpu-quota`
+ Container block IO can be controlled in `docker run` using`--blkio-weight`
+ ZFS support
+ The `docker logs` command supports a `--since` argument
+ UTS namespace can be shared with the host with `docker run --uts=host`
#### Quality
* Networking stack was entirely rewritten as part of the libnetwork effort
* Engine internals refactoring
* Volumes code was entirely rewritten to support the plugins effort
+ Sending SIGUSR1 to a daemon will dump all goroutines stacks without exiting
#### Build
+ Support ${variable:-value} and ${variable:+value} syntax for environment variables
+ Support resource management flags `--cgroup-parent`, `--cpu-period`, `--cpu-quota`, `--cpuset-cpus`, `--cpuset-mems`
+ git context changes with branches and directories
* The .dockerignore file support exclusion rules
#### Distribution
+ Client support for v2 mirroring support for the official registry
#### Bugfixes
* Firewalld is now supported and will automatically be used when available
* mounting --device recursively
## 1.6.2 (2015-05-13)
#### Runtime
- Revert change prohibiting mounting into /sys
## 1.6.1 (2015-05-07)
#### Security
- Fix read/write /proc paths (CVE-2015-3630)
- Prohibit VOLUME /proc and VOLUME / (CVE-2015-3631)
- Fix opening of file-descriptor 1 (CVE-2015-3627)
- Fix symlink traversal on container respawn allowing local privilege escalation (CVE-2015-3629)
- Prohibit mount of /sys
#### Runtime
- Update AppArmor policy to not allow mounts
## 1.6.0 (2015-04-07)
#### Builder
+ Building images from an image ID
+ Build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...`
+ `commit --change` to apply specified Dockerfile instructions while committing the image
+ `import --change` to apply specified Dockerfile instructions while importing the image
+ Builds no longer continue in the background when canceled with CTRL-C
#### Client
+ Windows Support
#### Runtime
+ Container and image Labels
+ `--cgroup-parent` for specifying a parent cgroup to place container cgroup within
+ Logging drivers, `json-file`, `syslog`, or `none`
+ Pulling images by ID
+ `--ulimit` to set the ulimit on a container
+ `--default-ulimit` option on the daemon which applies to all created containers (and overwritten by `--ulimit` on run)
## 1.5.0 (2015-02-10)
## 1.5.0 (2015-02-05)
#### Builder
+ Dockerfile to use for a given `docker build` can be specified with the `-f` flag
@@ -205,7 +19,6 @@
- Fixed contacting a private registry through a proxy
#### Remote API
+ A new endpoint will stream live container resource metrics and can be accessed with the `docker stats` command
+ Containers can be renamed using the new `rename` endpoint and the associated `docker rename` command
* Container `inspect` endpoint show the ID of `exec` commands running in this container
* Container `inspect` endpoint show the number of times Docker auto-restarted the container
@@ -458,7 +271,7 @@
#### Hack
* Clean up "go test" output from "make test" to be much more readable/scannable.
* Exclude more "definitely not unit tested Go source code" directories from hack/make/test.
* Excluse more "definitely not unit tested Go source code" directories from hack/make/test.
+ Generate md5 and sha256 hashes when building, and upload them via hack/release.sh.
- Include contributed completions in Ubuntu PPA.
+ Add cli integration tests.
@@ -738,7 +551,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
* The ADD instruction now supports caching, which avoids unnecessarily re-uploading the same source content again and again when it hasnt changed
* The new ONBUILD instruction adds to your image a “trigger” instruction to be executed at a later time, when the image is used as the base for another build
* Docker now ships with an experimental storage driver which uses the BTRFS filesystem for copy-on-write
* Docker is officially supported on Mac OS X
* Docker is officially supported on Mac OSX
* The Docker daemon supports systemd socket activation
## 0.7.6 (2014-01-14)
@@ -792,12 +605,12 @@ With the ongoing changes to the networking and execution subsystems of docker te
- Fix ADD caching issue with . prefixed path
- Fix docker build on devicemapper by reverting sparse file tar option
- Fix issue with file caching and prevent wrong cache hit
* Use same error handling while unmarshalling CMD and ENTRYPOINT
* Use same error handling while unmarshalling CMD and ENTRYPOINT
#### Documentation
* Simplify and streamline Amazon Quickstart
* Install instructions use unprefixed Fedora image
* Install instructions use unprefixed fedora image
* Update instructions for mtu flag for Docker on GCE
+ Add Ubuntu Saucy to installation
- Fix for wrong version warning on master instead of latest
@@ -972,7 +785,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
* Improve unit tests
* The test suite now runs all tests even if one fails
* Refactor C in Go (Devmapper)
- Fix OS X compilation
- Fix OSX compilation
## 0.7.0 (2013-11-25)
@@ -990,7 +803,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
#### Runtime
* Improve stability, fixes some race conditions
* Improve stability, fixes some race conditons
* Skip the volumes mounted when deleting the volumes of container.
* Fix layer size computation: handle hard links correctly
* Use the work Path for docker cp CONTAINER:PATH
+147 -226
View File
@@ -1,60 +1,70 @@
# Contributing to Docker
Want to hack on Docker? Awesome! We have a contributor's guide that explains
[setting up a Docker development environment and the contribution
process](https://docs.docker.com/project/who-written-for/).
![Contributors guide](docs/static_files/contributors.png)
This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors. Finally, make sure
you read our [community guidelines](#docker-community-guidelines) before you
start participating.
Want to hack on Docker? Awesome! Here are instructions to get you
started. They are probably not perfect, please let us know if anything
feels wrong or incomplete.
## Topics
* [Reporting Security Issues](#reporting-security-issues)
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
* [Reporting Issues](#reporting-other-issues)
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
* [Reporting Issues](#reporting-issues)
* [Build Environment](#build-environment)
* [Contribution Guidelines](#contribution-guidelines)
* [Community Guidelines](#docker-community-guidelines)
## Reporting security issues
## Reporting Security Issues
The Docker maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
The Docker maintainers take security very seriously. If you discover a security issue,
please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to
[security@docker.com](mailto:security@docker.com),
Please send your report privately to [security@docker.com](mailto:security@docker.com),
please **DO NOT** file a public issue.
Security reports are greatly appreciated and we will publicly thank you for it.
We also like to send gifts&mdash;if you're into Docker schwag, make sure to let
us know. We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
Security reports are greatly appreciated and we will publicly thank you for it. We also
like to send gifts - if you're into Docker shwag make sure to let us know :)
We currently do not offer a paid security bounty program, but are not ruling it out in
the future.
## Design and Cleanup Proposals
## Reporting other issues
When considering a design proposal, we are looking for:
* A description of the problem this design proposal solves
* A pull request, not an issue, that modifies the documentation describing
the feature you are proposing, adding new documentation if necessary.
* Please prefix your issue with `Proposal:` in the title
* Please review [the existing Proposals](https://github.com/docker/docker/pulls?q=is%3Aopen+is%3Apr+label%3AProposal)
before reporting a new one. You can always pair with someone if you both
have the same idea.
When considering a cleanup task, we are looking for:
* A description of the refactors made
* Please note any logic changes if necessary
* A pull request with the code
* Please prefix your PR's title with `Cleanup:` so we can quickly address it.
* Your pull request must remain up to date with master, so rebase as necessary.
## Reporting Issues
A great way to contribute to the project is to send a detailed report when you
encounter an issue. We always appreciate a well-written, thorough bug report,
and will thank you for it!
Check that [our issue database](https://github.com/docker/docker/issues)
doesn't already include that problem or suggestion before submitting an issue.
If you find a match, add a quick "+1" or "I have this problem too." Doing this
helps prioritize the most common problems and requests.
When reporting issues, please include your host OS (Ubuntu 12.04, Fedora 19,
etc). Please include:
When reporting [issues](https://github.com/docker/docker/issues) on
GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc).
Please include:
* The output of `uname -a`.
* The output of `docker version`.
* The output of `docker -D info`.
Please also include the steps required to reproduce the problem if possible and
applicable. This information will help us review and fix your issue faster.
Please also include the steps required to reproduce the problem if
possible and applicable. This information will help us review and fix
your issue faster.
**Issue Report Template**:
### Template
```
Description of problem:
@@ -93,167 +103,123 @@ Additional info:
```
## Build Environment
##Quick contribution tips and guidelines
For instructions on setting up your development environment, please
see our dedicated [dev environment setup
docs](http://docs.docker.com/contributing/devenvironment/).
This section gives the experienced contributor some tips and guidelines.
## Contribution guidelines
###Pull requests are always welcome
### Pull requests are always welcome
Not sure if that typo is worth a pull request? Found a bug and know how to fix
it? Do it! We will appreciate it. Any significant improvement should be
documented as [a GitHub issue](https://github.com/docker/docker/issues) before
anybody starts working on it.
We are always thrilled to receive pull requests, and do our best to
process them as quickly as possible. Not sure if that typo is worth a pull
request? Do it! We will appreciate it.
We are always thrilled to receive pull requests. We do our best to process them
quickly. If your pull request is not accepted on the first try,
don't get discouraged! Our contributor's guide explains [the review process we
use for simple changes](https://docs.docker.com/project/make-a-contribution/).
If your pull request is not accepted on the first try, don't be
discouraged! If there's a problem with the implementation, hopefully you
received feedback on what to improve.
### Design and cleanup proposals
We're trying very hard to keep Docker lean and focused. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement
that feature *on top of* Docker.
You can propose new designs for existing Docker features. You can also design
entirely new features. We really appreciate contributors who want to refactor or
otherwise cleanup our project. For information on making these types of
contributions, see [the advanced contribution
section](https://docs.docker.com/project/advanced-contributing/) in the
contributors guide.
### Discuss your design on the mailing list
We try hard to keep Docker lean and focused. Docker can't do everything for
everybody. This means that we might decide against incorporating a new feature.
However, there might be a way to implement that feature *on top of* Docker.
We recommend discussing your plans [on the mailing
list](https://groups.google.com/forum/?fromgroups#!forum/docker-dev)
before starting to code - especially for more ambitious contributions.
This gives other contributors a chance to point you in the right
direction, give feedback on your design, and maybe point out if someone
else is working on the same thing.
### Talking to other Docker users and contributors
### Create issues...
<table class="tg">
<col width="45%">
<col width="65%">
<tr>
<td>Internet&nbsp;Relay&nbsp;Chat&nbsp;(IRC)</td>
<td>
<p>
IRC a direct line to our most knowledgeable Docker users; we have
both the <code>#docker</code> and <code>#docker-dev</code> group on
<strong>irc.freenode.net</strong>.
IRC is a rich chat protocol but it can overwhelm new users. You can search
<a href="https://botbot.me/freenode/docker/#" target="_blank">our chat archives</a>.
</p>
Read our <a href="https://docs.docker.com/project/get-help/#irc-quickstart" target="_blank">IRC quickstart guide</a> for an easy way to get started.
</td>
</tr>
<tr>
<td>Google Groups</td>
<td>
There are two groups.
<a href="https://groups.google.com/forum/#!forum/docker-user" target="_blank">Docker-user</a>
is for people using Docker containers.
The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
group is for contributors and other people contributing to the Docker
project.
</td>
</tr>
<tr>
<td>Twitter</td>
<td>
You can follow <a href="https://twitter.com/docker/" target="_blank">Docker's Twitter feed</a>
to get updates on our products. You can also tweet us questions or just
share blogs or stories.
</td>
</tr>
<tr>
<td>Stack Overflow</td>
<td>
Stack Overflow has over 7000K Docker questions listed. We regularly
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
and so do many other knowledgeable Docker users.
</td>
</tr>
</table>
Any significant improvement should be documented as [a GitHub
issue](https://github.com/docker/docker/issues) before anybody
starts working on it.
### ...but check for existing issues first!
Please take a moment to check that an issue doesn't already exist
documenting your bug report or improvement proposal. If it does, it
never hurts to add a quick "+1" or "I have this problem too". This will
help prioritize the most common problems and requests.
### Conventions
Fork the repository and make changes on your fork in a feature branch:
- If it's a bug fix branch, name it XXXX-something where XXXX is the number of
the issue.
- If it's a feature branch, create an enhancement issue to announce
your intentions, and name it XXXX-something where XXXX is the number of the
issue.
- If it's a bug fix branch, name it XXXX-something where XXXX is the number of the
issue.
- If it's a feature branch, create an enhancement issue to announce your
intentions, and name it XXXX-something where XXXX is the number of the issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. [Run the full test
suite](https://docs.docker.com/project/test-and-docs/) on your branch before
submitting a pull request.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test suite on
your branch before submitting a pull request.
Update the documentation when creating or modifying features. Test your
documentation changes for clarity, concision, and correctness, as well as a
clean documentation build. See our contributors guide for [our style
guide](https://docs.docker.com/project/doc-style) and instructions on [building
the documentation](https://docs.docker.com/project/test-and-docs/#build-and-test-the-documentation).
Update the documentation when creating or modifying features. Test
your documentation changes for clarity, concision, and correctness, as
well as a clean documentation build. See `docs/README.md` for more
information on building the docs and how they get released.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plug-ins that do this automatically.
Pull request descriptions should be as clear as possible and include a reference
to all the issues that they address.
Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
Commit messages must start with a capitalized and short summary (max. 50 chars)
written in the imperative, followed by an optional, more detailed explanatory
text which is separated from the summary by an empty line.
Commit messages must start with a capitalized and short summary (max. 50
chars) written in the imperative, followed by an optional, more detailed
explanatory text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Post
a comment after pushing. New commits show up in the pull request automatically,
but the reviewers are notified only when you comment.
suggested modifications and push additional commits to your feature branch. Be
sure to post a comment after pushing. The new commits will show up in the pull
request automatically, but the reviewers will not be notified unless you
comment.
Pull requests must be cleanly rebased on top of master without multiple branches
Pull requests must be cleanly rebased ontop of master without multiple branches
mixed into the PR.
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
feature branch to update your pull request rather than `merge master`.
Before you make a pull request, squash your commits into logical units of work
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
set of patches that should be reviewed together: for example, upgrading the
version of a vendored dependency and taking advantage of its now available new
feature constitute two separate units of work. Implementing a new function and
calling it in another file constitute a single logical unit of work. The very
high majority of submissions should have a single commit, so if in doubt: squash
down to one.
Before the pull request is merged, make sure that you squash your commits into
logical units of work using `git rebase -i` and `git push -f`. After every
commit the test suite should be passing. Include documentation changes in the
same commit so that a revert would remove all traces of the feature or fix.
After every commit, [make sure the test suite passes]
((https://docs.docker.com/project/test-and-docs/)). Include documentation
changes in the same pull request so that a revert would remove all traces of
the feature or fix.
Commits that fix or close an issue should include a reference like
`Closes #XXXX` or `Fixes #XXXX`, which will automatically close the
issue when merged.
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
close an issue. Including references automatically closes the issue on a merge.
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
from the Git history.
Please see the [Coding Style](#coding-style) for further guidelines.
Please do not add yourself to the `AUTHORS` file, as it is regenerated
regularly from the Git history.
### Merge approval
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review to
indicate acceptance.
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review
to indicate acceptance.
A change requires LGTMs from an absolute majority of the maintainers of each
component affected. For example, if a change affects `docs/` and `registry/`, it
needs an absolute majority from the maintainers of `docs/` AND, separately, an
absolute majority of the maintainers of `registry/`.
For more details, see the [MAINTAINERS](MAINTAINERS) page.
For more details see [MAINTAINERS.md](project/MAINTAINERS.md)
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
@@ -297,7 +263,7 @@ Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
Using your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
@@ -314,45 +280,45 @@ format right away, but please do adjust your processes for future contributions.
* Step 4: Propose yourself at a scheduled docker meeting in #docker-dev
Don't forget: being a maintainer is a time investment. Make sure you
will have time to make yourself available. You don't have to be a
will have time to make yourself available. You don't have to be a
maintainer to make a difference on the project!
### IRC meetings
### IRC Meetings
There are two monthly meetings taking place on #docker-dev IRC to accommodate all
timezones. Anybody can propose a topic for discussion prior to the meeting.
There are two monthly meetings taking place on #docker-dev IRC to accomodate all timezones.
Anybody can ask for a topic to be discussed prior to the meeting.
If you feel the conversation is going off-topic, feel free to point it out.
For the exact dates and times, have a look at [the irc-minutes
repo](https://github.com/docker/irc-minutes). The minutes also contain all the
notes from previous meetings.
For the exact dates and times, have a look at [the irc-minutes repo](https://github.com/docker/irc-minutes).
They also contain all the notes from previous meetings.
## Docker community guidelines
## Docker Community Guidelines
We want to keep the Docker community awesome, growing and collaborative. We need
your help to keep it that way. To help with this we've come up with some general
guidelines for the community as a whole:
We want to keep the Docker community awesome, growing and collaborative. We
need your help to keep it that way. To help with this we've come up with some
general guidelines for the community as a whole:
* Be nice: Be courteous, respectful and polite to fellow community members:
no regional, racial, gender, or other abuse will be tolerated. We like
nice people way better than mean ones!
* Be nice: Be courteous, respectful and polite to fellow community members: no
regional, racial, gender, or other abuse will be tolerated. We like nice people
way better than mean ones!
* Encourage diversity and participation: Make everyone in our community feel
welcome, regardless of their background and the extent of their
* Encourage diversity and participation: Make everyone in our community
feel welcome, regardless of their background and the extent of their
contributions, and do everything possible to encourage participation in
our community.
* Keep it legal: Basically, don't get us in trouble. Share only content that
you own, do not share private or sensitive information, and don't break
the law.
you own, do not share private or sensitive information, and don't break the
law.
* Stay on topic: Make sure that you are posting to the correct channel and
avoid off-topic discussions. Remember when you update an issue or respond
to an email you are potentially sending to a large number of people. Please
consider this before you update. Also remember that nobody likes spam.
* Stay on topic: Make sure that you are posting to the correct channel
and avoid off-topic discussions. Remember when you update an issue or
respond to an email you are potentially sending to a large number of
people. Please consider this before you update. Also remember that
nobody likes spam.
### Guideline violations — 3 strikes method
### Guideline Violations — 3 Strikes Method
The point of this section is not to find opportunities to punish people, but we
do need a fair way to deal with people who are making our community suck.
@@ -371,65 +337,20 @@ do need a fair way to deal with people who are making our community suck.
* Obvious spammers are banned on first occurrence. If we don't do this, we'll
have spam all over the place.
* Violations are forgiven after 6 months of good behavior, and we won't hold a
grudge.
* Violations are forgiven after 6 months of good behavior, and we won't
hold a grudge.
* People who commit minor infractions will get some education, rather than
hammering them in the 3 strikes process.
* People who commit minor infractions will get some education,
rather than hammering them in the 3 strikes process.
* The rules apply equally to everyone in the community, no matter how much
you've contributed.
* The rules apply equally to everyone in the community, no matter how
much you've contributed.
* Extreme violations of a threatening, abusive, destructive or illegal nature
will be addressed immediately and are not subject to 3 strikes or forgiveness.
will be addressed immediately and are not subject to 3 strikes or
forgiveness.
* Contact abuse@docker.com to report abuse or appeal violations. In the case of
appeals, we know that mistakes happen, and we'll work with you to come up with a
fair solution if there has been a misunderstanding.
appeals, we know that mistakes happen, and we'll work with you to come up with
a fair solution if there has been a misunderstanding.
## Coding Style
Unless explicitly stated, we follow all coding guidelines from the Go
community. While some of these standards may seem arbitrary, they somehow seem
to result in a solid, consistent codebase.
It is possible that the code base does not currently comply with these
guidelines. We are not looking for a massive PR that fixes this, since that
goes against the spirit of the guidelines. All new contributions should make a
best effort to clean up and make the code base better than they left it.
Obviously, apply your best judgement. Remember, the goal here is to make the
code base easier for humans to navigate and understand. Always keep that in
mind when nudging others to comply.
The rules:
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of
[`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to it's context and no longer.
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
In practice, short methods will have short variable names and globals will
have longer names.
7. No underscores in package names. If you need a compound name, step back,
and re-examine why you need a compound name. If you still think you need a
compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to
warrant it's own package, it has not been written generally enough to be a
part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be
required. No, we don't need another unit testing framework. Assertion
packages are acceptable if they provide _real_ incremental value.
10. Even though we call these "rules" above, they are actually just
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](http://golang.org/doc/effective_go.html). The
[Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
kool-aid is a lot easier than going thirsty.
+26 -83
View File
@@ -19,25 +19,20 @@
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: AppArmor used to mess with privileged mode, but this is no longer
# Note: Apparmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
FROM ubuntu:14.04
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
automake \
bash-completion \
btrfs-tools \
build-essential \
createrepo \
curl \
dpkg-sig \
git \
@@ -54,8 +49,6 @@ RUN apt-get update && apt-get install -y \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.0* \
ubuntu-zfs \
libzfs-dev \
--no-install-recommends
# Get lvm2 source for compiling statically
@@ -70,7 +63,7 @@ RUN cd /usr/local/lvm2 \
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
# Install lxc
ENV LXC_VERSION 1.1.2
ENV LXC_VERSION 1.0.7
RUN mkdir -p /usr/src/lxc \
&& curl -sSL https://linuxcontainers.org/downloads/lxc/lxc-${LXC_VERSION}.tar.gz | tar -v -C /usr/src/lxc/ -xz --strip-components=1
RUN cd /usr/src/lxc \
@@ -80,7 +73,7 @@ RUN cd /usr/src/lxc \
&& ldconfig
# Install Go
ENV GO_VERSION 1.4.2
ENV GO_VERSION 1.4.1
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/local -xz \
&& mkdir -p /go/bin
ENV PATH /go/bin:/usr/local/go/bin:$PATH
@@ -104,50 +97,32 @@ RUN cd /usr/local/go/src \
./make.bash --no-clean 2>&1; \
done
# This has been commented out and kept as reference because we don't support compiling with older Go anymore.
# ENV GOFMT_VERSION 1.3.3
# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
# We still support compiling with older Go, so need to grab older "gofmt"
ENV GOFMT_VERSION 1.3.3
RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
# Update this sha when we upgrade to go 1.5.0
ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9
# Grab Go's cover tool for dead-simple code coverage testing
# Grab Go's vet tool for examining go code to find suspicious constructs
# and help prevent errors that the compiler might not catch
RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
&& go install -v golang.org/x/tools/cmd/cover \
&& go install -v golang.org/x/tools/cmd/vet
# Grab Go's lint tool
ENV GO_LINT_COMMIT f42f5c1c440621302702cb0741e9d2ca547ae80f
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
&& go install -v github.com/golang/lint/golint
RUN go get golang.org/x/tools/cmd/cover
# TODO replace FPM with some very minimal debhelper stuff
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
# Install registry
ENV REGISTRY_COMMIT 2317f721a3d8428215a2b65da4ae85212ed473b4
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
&& rm -rf "$GOPATH"
# Get the "busybox" image source so we can build locally instead of pulling
RUN git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox
# Install notary server
ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7
# Get the "cirros" image source so we can import it instead of fetching it during tests
RUN curl -sSL -o /cirros.tar.gz https://github.com/ewindisch/docker-cirros/raw/1cded459668e8b9dbf4ef976c94c05add9bbd8e9/cirros-0.3.0-x86_64-lxc.tar.gz
# Install registry
ENV REGISTRY_COMMIT c448e0416925a9876d5576e412703c9b8b865e19
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_COMMIT") \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& rm -rf "$GOPATH"
&& git clone https://github.com/docker/distribution.git /go/src/github.com/docker/distribution \
&& (cd /go/src/github.com/docker/distribution && git checkout -q $REGISTRY_COMMIT) \
&& GOPATH=/go/src/github.com/docker/distribution/Godeps/_workspace:/go \
go build -o /go/bin/registry-v2 github.com/docker/distribution/cmd/registry
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT 8a87001d09852058f08a807ab6e8491d57ca1e88
ENV DOCKER_PY_COMMIT aa19d7b6609c6676e8258f6b900dea2eda1dbe95
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT
@@ -168,47 +143,15 @@ RUN useradd --create-home --gid docker unprivilegeduser
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor selinux
ENV DOCKER_BUILDTAGS apparmor selinux btrfs_noversion
# Let us use a .bashrc file
RUN ln -sfv $PWD/.bashrc ~/.bashrc
# Register Docker's bash completion.
RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/
RUN ./contrib/download-frozen-image.sh /docker-frozen-images \
busybox:latest@8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55 \
hello-world:frozen@91c95931e552b11604fea91c2f537284149ec32fff0f700a4769cfd31d7696ae \
jess/unshare@5c9f6ea50341a2a8eb6677527f2bdedbf331ae894a41714fda770fb130f3314d
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
# Download man page generator
# Install man page generator
COPY vendor /go/src/github.com/docker/docker/vendor
# (copy vendor/ because go-md2man needs golang.org/x/net)
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone -b v1.0.3 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
&& git clone -b v1.2 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
&& go get -v -d github.com/cpuguy83/go-md2man \
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
&& rm -rf "$GOPATH"
# Download toml validator
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
&& rm -rf "$GOPATH"
# Build/install the tool for embedding resources in Windows binaries
ENV RSRC_COMMIT e48dbf1b7fc464a9e85fcec450dddf80816b76e0
RUN set -x \
&& git clone https://github.com/akavel/rsrc.git /go/src/github.com/akavel/rsrc \
&& cd /go/src/github.com/akavel/rsrc \
&& git checkout -q $RSRC_COMMIT \
&& go install -v
&& git clone -b v1 https://github.com/cpuguy83/go-md2man.git /go/src/github.com/cpuguy83/go-md2man \
&& git clone -b v1.2 https://github.com/russross/blackfriday.git /go/src/github.com/russross/blackfriday \
&& go install -v github.com/cpuguy83/go-md2man
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
-34
View File
@@ -1,34 +0,0 @@
# docker build -t docker:simple -f Dockerfile.simple .
# docker run --rm docker:simple hack/make.sh dynbinary
# docker run --rm --privileged docker:simple hack/dind hack/make.sh test-unit
# docker run --rm --privileged -v /var/lib/docker docker:simple hack/dind hack/make.sh dynbinary test-integration-cli
# This represents the bare minimum required to build and test Docker.
FROM debian:jessie
# compile and runtime deps
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#build-dependencies
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#runtime-dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
btrfs-tools \
curl \
gcc \
git \
golang \
libdevmapper-dev \
libsqlite3-dev \
\
ca-certificates \
e2fsprogs \
iptables \
procps \
xz-utils \
\
aufs-tools \
lxc \
&& rm -rf /var/lib/apt/lists/*
ENV AUTO_GOPATH 1
WORKDIR /usr/src/docker
COPY . /usr/src/docker
+2 -2
View File
@@ -1,7 +1,7 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@@ -182,7 +182,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
+9 -618
View File
@@ -1,618 +1,9 @@
# Docker maintainers file
#
# This file describes who runs the Docker project and how.
# This is a living document - if you see something out of date or missing,
# speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant
# parser.
[Rules]
[Rules.maintainers]
title = "What is a maintainer?"
text = """
There are different types of maintainers, with different responsibilities, but
all maintainers have 3 things in common:
1) They share responsibility in the project's success.
2) They have made a long-term, recurring time investment to improve the project.
3) They spend that time doing whatever needs to be done, not necessarily what
is the most interesting or fun.
Maintainers are often under-appreciated, because their work is harder to appreciate.
It's easy to appreciate a really cool and technically advanced feature. It's harder
to appreciate the absence of bugs, the slow but steady improvement in stability,
or the reliability of a release process. But those things distinguish a good
project from a great one.
"""
[Rules.bdfl]
title = "The Benevolent dictator for life (BDFL)"
text = """
Docker follows the timeless, highly efficient and totally unfair system
known as [Benevolent dictator for
life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with
yours truly, Solomon Hykes, in the role of BDFL. This means that all
decisions are made, by default, by Solomon. Since making every decision
myself would be highly un-scalable, in practice decisions are spread
across multiple maintainers.
Ideally, the BDFL role is like the Queen of England: awesome crown, but not
an actual operational role day-to-day. The real job of a BDFL is to NEVER GO AWAY.
Every other rule can change, perhaps drastically so, but the BDFL will always
be there, preserving the philosophy and principles of the project, and keeping
ultimate authority over its fate. This gives us great flexibility in experimenting
with various governance models, knowing that we can always press the "reset" button
without fear of fragmentation or deadlock. See the US congress for a counter-example.
BDFL daily routine:
* Is the project governance stuck in a deadlock or irreversibly fragmented?
* If yes: refactor the project governance
* Are there issues or conflicts escalated by core?
* If yes: resolve them
* Go back to polishing that crown.
"""
[Rules.decisions]
title = "How are decisions made?"
text = """
Short answer: EVERYTHING IS A PULL REQUEST.
Docker is an open-source project with an open design philosophy. This
means that the repository is the source of truth for EVERY aspect of the
project, including its philosophy, design, road map, and APIs. *If it's
part of the project, it's in the repo. If it's in the repo, it's part of
the project.*
As a result, all decisions can be expressed as changes to the
repository. An implementation change is a change to the source code. An
API change is a change to the API specification. A philosophy change is
a change to the philosophy manifesto, and so on.
All decisions affecting Docker, big and small, follow the same 3 steps:
* Step 1: Open a pull request. Anyone can do this.
* Step 2: Discuss the pull request. Anyone can do this.
* Step 3: Merge or refuse the pull request. Who does this depends on the nature
of the pull request and which areas of the project it affects. See *review flow*
for details.
Because Docker is such a large and active project, it's important for everyone to know
who is responsible for deciding what. That is determined by a precise set of rules.
* For every *decision* in the project, the rules should designate, in a deterministic way,
who should *decide*.
* For every *problem* in the project, the rules should designate, in a deterministic way,
who should be responsible for *fixing* it.
* For every *question* in the project, the rules should designate, in a deterministic way,
who should be expected to have the *answer*.
"""
[Rules.review]
title = "Review flow"
text = """
Pull requests should be processed according to the following flow:
* For each subsystem affected by the change, the maintainers of the subsystem must approve or refuse it.
It is the responsibility of the subsystem maintainers to process patches affecting them in a timely
manner.
* If the change affects areas of the code which are not part of a subsystem,
or if subsystem maintainers are unable to reach a timely decision, it must be approved by
the core maintainers.
* If the change affects the UI or public APIs, or if it represents a major change in architecture,
the architects must approve or refuse it.
* If the change affects the operations of the project, it must be approved or rejected by
the relevant operators.
* If the change affects the governance, philosophy, goals or principles of the project,
it must be approved by BDFL.
"""
[Rules.DCO]
title = "Helping contributors with the DCO"
text = """
The [DCO or `Sign your work`](
https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work)
requirement is not intended as a roadblock or speed bump.
Some Docker contributors are not as familiar with `git`, or have used a web based
editor, and thus asking them to `git commit --amend -s` is not the best way forward.
In this case, maintainers can update the commits based on clause (c) of the DCO. The
most trivial way for a contributor to allow the maintainer to do this, is to add
a DCO signature in a Pull Requests's comment, or a maintainer can simply note that
the change is sufficiently trivial that it does not substantivly change the existing
contribution - i.e., a spelling change.
When you add someone's DCO, please also add your own to keep a log.
"""
[Rules.holiday]
title = "I'm a maintainer, and I'm going on holiday"
text = """
Please let your co-maintainers and other contributors know by raising a pull
request that comments out your `MAINTAINERS` file entry using a `#`.
"""
[Rules."no direct push"]
title = "I'm a maintainer. Should I make pull requests too?"
text = """
Yes. Nobody should ever push to master directly. All changes should be
made through a pull request.
"""
[Rules.meta]
title = "How is this process changed?"
text = "Just like everything else: by making a pull request :)"
# Current project organization
[Org]
bdfl = "shykes"
# The chief architect is responsible for the overall integrity of the technical architecture
# across all subsystems, and the consistency of APIs and UI.
#
# Changes to UI, public APIs and overall architecture (for example a plugin system) must
# be approved by the chief architect.
"Chief Architect" = "shykes"
# The Chief Operator is responsible for the day-to-day operations of the project including:
# - facilitating communications amongst all the contributors;
# - tracking release schedules;
# - managing the relationship with downstream distributions and upstream dependencies;
# - helping new contributors to get involved and become successful contributors and maintainers
#
# The role is also responsible for managing and measuring the success of the overall project
# and ensuring it is governed properly working in concert with the Docker Governance Advisory Board (DGAB).
"Chief Operator" = "spf13"
[Org.Operators]
# The operators make sure the trains run on time. They are responsible for overall operations
# of the project. This includes facilitating communication between all the participants; helping
# newcomers get involved and become successful contributors and maintainers; tracking the schedule
# of releases; managing the relationship with downstream distributions and upstream dependencies;
# define measures of success for the project and measure progress; Devise and implement tools and
# processes which make contributors and maintainers happier and more efficient.
[Org.Operators.security]
people = [
"erw",
"diogomonica",
"nathanmccauley"
]
[Org.Operators."monthly meetings"]
people = [
"sven",
"tianon"
]
[Org.Operators.infrastructure]
people = [
"jfrazelle",
"crosbymichael"
]
[Org.Operators.community]
people = [
"theadactyl"
]
# The chief maintainer is responsible for all aspects of quality for the project including
# code reviews, usability, stability, security, performance, etc.
# The most important function of the chief maintainer is to lead by example. On the first
# day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll
# be fine".
"Chief Maintainer" = "crosbymichael"
# The community manager is responsible for serving the project community, including users,
# contributors and partners. This involves:
# - facilitating communication between maintainers, contributors and users
# - organizing contributor and maintainer events
# - helping new contributors get involved
# - anything the project community needs to be successful
#
# The community manager is a point of contact for any contributor who has questions, concerns
# or feedback about project operations.
"Community Manager" = "theadactyl"
[Org."Core maintainers"]
# The Core maintainers are the ghostbusters of the project: when there's a problem others
# can't solve, they show up and fix it with bizarre devices and weaponry.
# They have final say on technical implementation and coding style.
# They are ultimately responsible for quality in all its forms: usability polish,
# bugfixes, performance, stability, etc. When ownership can cleanly be passed to
# a subsystem, they are responsible for doing so and holding the
# subsystem maintainers accountable. If ownership is unclear, they are the de facto owners.
# For each release (including minor releases), a "release captain" is assigned from the
# pool of core maintainers. Rotation is encouraged across all maintainers, to ensure
# the release process is clear and up-to-date.
#
# It is common for core maintainers to "branch out" to join or start a subsystem.
people = [
"calavera",
"crosbymichael",
"erikh",
"estesp",
"icecrime",
"jfrazelle",
"lk4d4",
"runcom",
"tibor",
"unclejack",
"vbatts",
"vieux",
"vishh"
]
[Org.Subsystems]
# As the project grows, it gets separated into well-defined subsystems. Each subsystem
# has a dedicated group of maintainers, which are dedicated to that subsytem and responsible
# for its quality.
# This "cellular division" is the primary mechanism for scaling maintenance of the project as it grows.
#
# The maintainers of each subsytem are responsible for:
#
# 1. Exposing a clear road map for improving their subsystem.
# 2. Deliver prompt feedback and decisions on pull requests affecting their subsystem.
# 3. Be available to anyone with questions, bug reports, criticism etc.
# on their component. This includes IRC, GitHub requests and the mailing
# list.
# 4. Make sure their subsystem respects the philosophy, design and
# road map of the project.
#
# #### How to review patches to your subsystem
#
# Accepting pull requests:
#
# - If the pull request appears to be ready to merge, give it a `LGTM`, which
# stands for "Looks Good To Me".
# - If the pull request has some small problems that need to be changed, make
# a comment adressing the issues.
# - If the changes needed to a PR are small, you can add a "LGTM once the
# following comments are adressed..." this will reduce needless back and
# forth.
# - If the PR only needs a few changes before being merged, any MAINTAINER can
# make a replacement PR that incorporates the existing commits and fixes the
# problems before a fast track merge.
#
# Closing pull requests:
#
# - If a PR appears to be abandoned, after having attempted to contact the
# original contributor, then a replacement PR may be made. Once the
# replacement PR is made, any contributor may close the original one.
# - If you are not sure if the pull request implements a good feature or you
# do not understand the purpose of the PR, ask the contributor to provide
# more documentation. If the contributor is not able to adequately explain
# the purpose of the PR, the PR may be closed by any MAINTAINER.
# - If a MAINTAINER feels that the pull request is sufficiently architecturally
# flawed, or if the pull request needs significantly more design discussion
# before being considered, the MAINTAINER should close the pull request with
# a short explanation of what discussion still needs to be had. It is
# important not to leave such pull requests open, as this will waste both the
# MAINTAINER's time and the contributor's time. It is not good to string a
# contributor on for weeks or months, having them make many changes to a PR
# that will eventually be rejected.
[Org.Subsystems.Documentation]
people = [
"fredlf",
"james",
"moxiegirl",
"thaJeztah",
"jamtur01",
"spf13",
"sven"
]
[Org.Subsystems.libcontainer]
people = [
"crosbymichael",
"jnagal",
"lk4d4",
"mpatel",
"vmarmol"
]
[Org.Subsystems.registry]
people = [
"dmcg",
"dmp42",
"jlhawn",
"samalba",
"sday",
"vbatts"
]
[Org.Subsystems."build tools"]
people = [
"shykes",
"tianon"
]
[Org.Subsystem."remote api"]
people = [
"vieux"
]
[Org.Subsystem.swarm]
people = [
"aluzzardi",
"vieux"
]
[Org.Subsystem.machine]
people = [
"bfirsh",
"ehazlett"
]
[Org.Subsystem.compose]
people = [
"aanand"
]
[Org.Subsystem.builder]
people = [
"duglin",
"erikh",
"tibor"
]
[Org.Curators]
# The curators help ensure that incoming issues and pull requests are properly triaged and
# that our various contribution and reviewing processes are respected. With their knowledge of
# the repository activity, they can also guide contributors to relevant material or
# discussions.
#
# They are neither code nor docs reviewers, so they are never expected to merge. They can
# however:
# - close an issue or pull request when it's an exact duplicate
# - close an issue or pull request when it's inappropriate or off-topic
people = [
"thajeztah"
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.aanand]
Name = "Aanand Prasad"
Email = "aanand@docker.com"
GitHub = "aanand"
[people.aluzzardi]
Name = "Andrea Luzzardi"
Email = "aluzzardi@docker.com"
GitHub = "aluzzardi"
[people.bfirsh]
Name = "Ben Firshman"
Email = "ben@firshman.co.uk"
GitHub = "bfirsh"
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"
[people.cpuguy83]
Name = "Brian Goff"
Email = "cpuguy83@gmail.com"
Github = "cpuguy83"
[people.crosbymichael]
Name = "Michael Crosby"
Email = "crosbymichael@gmail.com"
GitHub = "crosbymichael"
[people.diogomonica]
Name = "Diogo Monica"
Email = "diogo@docker.com"
GitHub = "diogomonica"
[people.duglin]
Name = "Doug Davis"
Email = "dug@us.ibm.com"
GitHub = "duglin"
[people.dmcg]
Name = "Derek McGowan"
Email = "derek@docker.com"
Github = "dmcgowan"
[people.dmp42]
Name = "Olivier Gambier"
Email = "olivier@docker.com"
Github = "dmp42"
[people.ehazlett]
Name = "Evan Hazlett"
Email = "ejhazlett@gmail.com"
GitHub = "ehazlett"
[people.erikh]
Name = "Erik Hollensbe"
Email = "erik@docker.com"
GitHub = "erikh"
[people.erw]
Name = "Eric Windisch"
Email = "eric@windisch.us"
GitHub = "ewindisch"
[people.estesp]
Name = "Phil Estes"
Email = "estesp@linux.vnet.ibm.com"
GitHub = "estesp"
[people.fredlf]
Name = "Fred Lifton"
Email = "fred.lifton@docker.com"
GitHub = "fredlf"
[people.icecrime]
Name = "Arnaud Porterie"
Email = "arnaud@docker.com"
GitHub = "icecrime"
[people.jfrazelle]
Name = "Jessie Frazelle"
Email = "j@docker.com"
GitHub = "jfrazelle"
[people.jlhawn]
Name = "Josh Hawn"
Email = "josh.hawn@docker.com"
Github = "jlhawn"
[people.lk4d4]
Name = "Alexander Morozov"
Email = "lk4d4@docker.com"
GitHub = "lk4d4"
[people.moxiegirl]
Name = "Mary Anthony"
Email = "mary.anthony@docker.com"
GitHub = "moxiegirl"
[people.nathanmccauley]
Name = "Nathan McCauley"
Email = "nathan.mccauley@docker.com"
GitHub = "nathanmccauley"
[people.runcom]
Name = "Antonio Murdaca"
Email = "me@runcom.ninja"
GitHub = "runcom"
[people.sday]
Name = "Stephen Day"
Email = "stephen.day@docker.com"
Github = "stevvooe"
[people.shykes]
Name = "Solomon Hykes"
Email = "solomon@docker.com"
GitHub = "shykes"
[people.spf13]
Name = "Steve Francia"
Email = "steve.francia@gmail.com"
GitHub = "spf13"
[people.sven]
Name = "Sven Dowideit"
Email = "SvenDowideit@home.org.au"
GitHub = "SvenDowideit"
[people.thajeztah]
Name = "Sebastiaan van Stijn"
Email = "github@gone.nl"
GitHub = "thaJeztah"
[people.theadactyl]
Name = "Thea Lamkin"
Email = "thea@docker.com"
GitHub = "theadactyl"
[people.tianon]
Name = "Tianon Gravi"
Email = "admwiggin@gmail.com"
GitHub = "tianon"
[people.tibor]
Name = "Tibor Vass"
Email = "tibor@docker.com"
GitHub = "tiborvass"
[people.vbatts]
Name = "Vincent Batts"
Email = "vbatts@redhat.com"
GitHub = "vbatts"
[people.vieux]
Name = "Victor Vieux"
Email = "vieux@docker.com"
GitHub = "vieux"
[people.vmarmol]
Name = "Victor Marmol"
Email = "vmarmol@google.com"
GitHub = "vmarmol"
[people.jnagal]
Name = "Rohit Jnagal"
Email = "jnagal@google.com"
GitHub = "rjnagal"
[people.mpatel]
Name = "Mrunal Patel"
Email = "mpatel@redhat.com"
GitHub = "mrunalp"
[people.unclejack]
Name = "Cristian Staretu"
Email = "cristian.staretu@gmail.com"
GitHub = "unclejack"
[people.vishh]
Name = "Vishnu Kannan"
Email = "vishnuk@google.com"
GitHub = "vishh"
Solomon Hykes <solomon@docker.com> (@shykes)
Victor Vieux <vieux@docker.com> (@vieux)
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
.mailmap: Tianon Gravi <admwiggin@gmail.com> (@tianon)
.travis.yml: Tianon Gravi <admwiggin@gmail.com> (@tianon)
AUTHORS: Tianon Gravi <admwiggin@gmail.com> (@tianon)
Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
.dockerignore: Tianon Gravi <admwiggin@gmail.com> (@tianon)
+33 -19
View File
@@ -1,4 +1,4 @@
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration-cli test-docker-py validate
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration test-integration-cli test-docker-py validate
# env vars passed through directly to Docker's build scripts
# to allow things like `make DOCKER_CLIENTONLY=1 binary` easily
@@ -6,26 +6,26 @@
DOCKER_ENVS := \
-e BUILDFLAGS \
-e DOCKER_CLIENTONLY \
-e DOCKER_DEBUG \
-e DOCKER_EXECDRIVER \
-e DOCKER_EXPERIMENTAL \
-e DOCKER_GRAPHDRIVER \
-e DOCKER_STORAGE_OPTS \
-e DOCKER_USERLANDPROXY \
-e TESTDIRS \
-e TESTFLAGS \
-e TIMEOUT
# note: we _cannot_ add "-e DOCKER_BUILDTAGS" here because even if it's unset in the shell, that would shadow the "ENV DOCKER_BUILDTAGS" set in our Dockerfile, which is very important for our official builds
# to allow `make BIND_DIR=. shell` or `make BIND_DIR= test`
# to allow `make BINDDIR=. shell` or `make BINDDIR= test`
# (default to no bind mount if DOCKER_HOST is set)
# note: BINDDIR is supported for backwards-compatibility here
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
BINDDIR := $(if $(DOCKER_HOST),,bundles)
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/docker/docker/$(BINDDIR)")
# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs)
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
# to allow `make DOCSPORT=9000 docs`
DOCSPORT := 8000
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH))
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
DOCKER_RUN_DOCKER := docker run --rm -it --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
@@ -46,18 +46,27 @@ binary: build
cross: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
deb: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-deb
docs: docs-build
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" mkdocs serve
rpm: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-rpm
docs-shell: docs-build
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
docs-release: docs-build
$(DOCKER_RUN_DOCS) -e OPTIONS -e BUILD_ROOT -e DISTRIBUTION_ID "$(DOCKER_DOCS_IMAGE)" ./release.sh
docs-test: docs-build
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh
test: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration test-integration-cli test-docker-py
test-unit: build
$(DOCKER_RUN_DOCKER) hack/make.sh test-unit
test-integration: build
$(DOCKER_RUN_DOCKER) hack/make.sh test-integration
test-integration-cli: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-integration-cli
@@ -65,7 +74,7 @@ test-docker-py: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py
validate: build
$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-pkg validate-lint validate-test validate-toml validate-vet
$(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco
shell: build
$(DOCKER_RUN_DOCKER) bash
@@ -73,8 +82,13 @@ shell: build
build: bundles
docker build -t "$(DOCKER_IMAGE)" .
docs-build:
( git remote | grep -v upstream ) || git diff --name-status upstream/release..upstream/docs docs/ > docs/changed-files
cp ./VERSION docs/VERSION
echo "$(GIT_BRANCH)" > docs/GIT_BRANCH
echo "$(AWS_S3_BUCKET)" > docs/AWS_S3_BUCKET
echo "$(GITCOMMIT)" > docs/GITCOMMIT
docker build -t "$(DOCKER_DOCS_IMAGE)" docs
bundles:
mkdir bundles
docs:
$(MAKE) -C docs docs
+6 -6
View File
@@ -1,7 +1,7 @@
Docker
Copyright 2012-2015 Docker, Inc.
Copyright 2012-2014 Docker, Inc.
This product includes software developed at Docker, Inc. (https://www.docker.com).
This product includes software developed at Docker, Inc. (http://www.docker.com).
This product contains software (https://github.com/kr/pty) developed
by Keith Rarick, licensed under the MIT License.
@@ -10,10 +10,10 @@ The following is courtesy of our legal counsel:
Use and transfer of Docker may be subject to certain restrictions by the
United States and other governments.
United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not
violate applicable laws.
violate applicable laws.
For more information, please see https://www.bis.doc.gov
For more information, please see http://www.bis.doc.gov
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
+40 -101
View File
@@ -2,7 +2,7 @@ Docker: the Linux container engine
==================================
Docker is an open source project to pack, ship and run any application
as a lightweight container.
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
@@ -13,12 +13,12 @@ databases, and backend services without depending on a particular stack
or provider.
Docker began as an open-source implementation of the deployment engine which
powers [dotCloud](https://www.dotcloud.com), a popular Platform-as-a-Service.
powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service.
It benefits directly from the experience accumulated over several years
of large-scale operation and support of hundreds of thousands of
applications and databases.
![Docker L](docs/static_files/docker-logo-compressed.png "Docker")
![Docker L](docs/theme/mkdocs/images/docker-logo-compressed.png "Docker")
## Security Disclosure
@@ -30,7 +30,7 @@ security@docker.com and not by creating a github issue.
A common method for distributing applications and sandboxing their
execution is to use virtual machines, or VMs. Typical VM formats are
VMware's vmdk, Oracle VirtualBox's vdi, and Amazon EC2's ami. In theory
VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory
these formats should allow every developer to automatically package
their application into a "machine" for easy distribution and deployment.
In practice, that almost never happens, for a few reasons:
@@ -56,12 +56,12 @@ By contrast, Docker relies on a different sandboxing method known as
*containerization*. Unlike traditional virtualization, containerization
takes place at the kernel level. Most modern operating system kernels
now support the primitives necessary for containerization, including
Linux with [openvz](https://openvz.org),
Linux with [openvz](http://openvz.org),
[vserver](http://linux-vserver.org) and more recently
[lxc](https://linuxcontainers.org/), Solaris with
[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
[lxc](http://lxc.sourceforge.net), Solaris with
[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
and FreeBSD with
[Jails](https://www.freebsd.org/doc/handbook/jails.html).
[Jails](http://www.freebsd.org/doc/handbook/jails.html).
Docker builds on top of these low-level primitives to offer developers a
portable format and runtime environment that solves all four problems.
@@ -105,7 +105,7 @@ This is usually difficult for several reasons:
these situations with various degrees of ease - but they all
handle them in different and incompatible ways, which again forces
the developer to do extra work.
* *Custom dependencies*. A developer may need to prepare a custom
version of their application's dependency. Some packaging systems
can handle custom versions of a dependency, others can't - and all
@@ -115,7 +115,7 @@ This is usually difficult for several reasons:
Docker solves the problem of dependency hell by giving the developer a simple
way to express *all* their application's dependencies in one place, while
streamlining the process of assembling them. If this makes you think of
[XKCD 927](https://xkcd.com/927/), don't worry. Docker doesn't
[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
*replace* your favorite packaging systems. It simply orchestrates
their use in a simple and repeatable way. How does it do that? With
layers.
@@ -147,19 +147,19 @@ Docker can be installed on your local machine as well as servers - both
bare metal and virtualized. It is available as a binary on most modern
Linux systems, or as a VM on Windows, Mac and other systems.
We also offer an [interactive tutorial](https://www.docker.com/tryit/)
We also offer an [interactive tutorial](http://www.docker.com/tryit/)
for quickly learning the basics of using Docker.
For up-to-date install instructions, see the [Docs](https://docs.docker.com).
For up-to-date install instructions, see the [Docs](http://docs.docker.com).
Usage examples
==============
Docker can be used to run short-lived commands, long-running daemons
(app servers, databases, etc.), interactive shell sessions, etc.
(app servers, databases etc.), interactive shell sessions, etc.
You can find a [list of real-world
examples](https://docs.docker.com/examples/) in the
examples](http://docs.docker.com/examples/) in the
documentation.
Under the hood
@@ -168,104 +168,44 @@ Under the hood
Under the hood, Docker is built on the following components:
* The
[cgroups](https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt)
[cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c)
and
[namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html)
capabilities of the Linux kernel
* The [Go](https://golang.org) programming language
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
* The [Libcontainer Specification](https://github.com/docker/libcontainer/blob/master/SPEC.md)
[namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part)
capabilities of the Linux kernel;
* The [Go](http://golang.org) programming language.
Contributing to Docker
======================
[![GoDoc](https://godoc.org/github.com/docker/docker?status.svg)](https://godoc.org/github.com/docker/docker)
[![Jenkins Build Status](https://jenkins.dockerproject.org/job/Docker%20Master/badge/icon)](https://jenkins.dockerproject.org/job/Docker%20Master/)
[![GoDoc](https://godoc.org/github.com/docker/docker?status.png)](https://godoc.org/github.com/docker/docker)
[![Jenkins Build Status](https://jenkins.dockerproject.com/job/Docker%20Master/badge/icon)](https://jenkins.dockerproject.com/job/Docker%20Master/)
Want to hack on Docker? Awesome! We have [instructions to help you get
started contributing code or documentation](https://docs.docker.com/project/who-written-for/).
started](CONTRIBUTING.md). If you'd like to contribute to the
documentation, please take a look at this [README.md](https://github.com/docker/docker/blob/master/docs/README.md).
These instructions are probably not perfect, please let us know if anything
feels wrong or incomplete. Better yet, submit a PR and improve them yourself.
Getting the development builds
==============================
Want to run Docker from a master build? You can download
master builds at [master.dockerproject.org](https://master.dockerproject.org).
master builds at [master.dockerproject.com](https://master.dockerproject.com).
They are updated with each commit merged into the master branch.
Don't know how to use that super cool new feature in the master build? Check
out the master docs at
[docs.master.dockerproject.org](http://docs.master.dockerproject.org).
How the project is run
======================
Docker is a very, very active project. If you want to learn more about how it is run,
or want to get more involved, the best place to start is [the project directory](https://github.com/docker/docker/tree/master/project).
We are always open to suggestions on process improvements, and are always looking for more maintainers.
### Talking to other Docker users and contributors
<table class="tg">
<col width="45%">
<col width="65%">
<tr>
<td>Internet&nbsp;Relay&nbsp;Chat&nbsp;(IRC)</td>
<td>
<p>
IRC a direct line to our most knowledgeable Docker users; we have
both the <code>#docker</code> and <code>#docker-dev</code> group on
<strong>irc.freenode.net</strong>.
IRC is a rich chat protocol but it can overwhelm new users. You can search
<a href="https://botbot.me/freenode/docker/#" target="_blank">our chat archives</a>.
</p>
Read our <a href="https://docs.docker.com/project/get-help/#irc-quickstart" target="_blank">IRC quickstart guide</a> for an easy way to get started.
</td>
</tr>
<tr>
<td>Google Groups</td>
<td>
There are two groups.
<a href="https://groups.google.com/forum/#!forum/docker-user" target="_blank">Docker-user</a>
is for people using Docker containers.
The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
group is for contributors and other people contributing to the Docker
project.
</td>
</tr>
<tr>
<td>Twitter</td>
<td>
You can follow <a href="https://twitter.com/docker/" target="_blank">Docker's Twitter feed</a>
to get updates on our products. You can also tweet us questions or just
share blogs or stories.
</td>
</tr>
<tr>
<td>Stack Overflow</td>
<td>
Stack Overflow has over 7000K Docker questions listed. We regularly
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
and so do many other knowledgeable Docker users.
</td>
</tr>
</table>
[docs.master.dockerproject.com](http://docs.master.dockerproject.com).
### Legal
*Brought to you courtesy of our legal counsel. For more context,
please see the [NOTICE](https://github.com/docker/docker/blob/master/NOTICE) document in this repo.*
please see the "NOTICE" document in this repo.*
Use and transfer of Docker may be subject to certain restrictions by the
United States and other governments.
United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not
violate applicable laws.
violate applicable laws.
For more information, please see https://www.bis.doc.gov
For more information, please see http://www.bis.doc.gov
Licensing
@@ -278,18 +218,17 @@ Other Docker Related Projects
=============================
There are a number of projects under development that are based on Docker's
core technology. These projects expand the tooling built around the
Docker platform to broaden its application and utility.
Docker platform to broaden its application and utility.
* [Docker Registry](https://github.com/docker/distribution): Registry
server for Docker (hosting/delivery of repositories and images)
* [Docker Machine](https://github.com/docker/machine): Machine management
for a container-centric world
* [Docker Swarm](https://github.com/docker/swarm): A Docker-native clustering
system
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
Define and run multi-container apps
* [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use
Docker on Mac and Windows
If you know of another project underway that should be listed here, please help
If you know of another project underway that should be listed here, please help
us keep this list up-to-date by submitting a PR.
* [Docker Registry](https://github.com/docker/docker-registry): Registry
server for Docker (hosting/delivering of repositories and images)
* [Docker Machine](https://github.com/docker/machine): Machine management
for a container-centric world
* [Docker Swarm](https://github.com/docker/swarm): A Docker-native clustering
system
* [Docker Compose, aka Fig](https://github.com/docker/fig):
Multi-container application management
-183
View File
@@ -1,183 +0,0 @@
Docker Engine Roadmap
=====================
### How should I use this document?
This document provides description of items that the project decided to prioritize. This should
serve as a reference point for Docker contributors to understand where the project is going, and
help determine if a contribution could be conflicting with some longer terms plans.
The fact that a feature isn't listed here doesn't mean that a patch for it will automatically be
refused (except for those mentioned as "frozen features" below)! We are always happy to receive
patches for new cool features we haven't thought about, or didn't judge priority. Please however
understand that such patches might take longer for us to review.
### How can I help?
Short term objectives are listed in the [wiki](https://github.com/docker/docker/wiki) and described
in [Issues](https://github.com/docker/docker/issues?q=is%3Aopen+is%3Aissue+label%3Aroadmap). Our
goal is to split down the workload in such way that anybody can jump in and help. Please comment on
issues if you want to take it to avoid duplicating effort! Similarly, if a maintainer is already
assigned on an issue you'd like to participate in, pinging him on IRC or GitHub to offer your help is
the best way to go.
### How can I add something to the roadmap?
The roadmap process is new to the Docker Engine: we are only beginning to structure and document the
project objectives. Our immediate goal is to be more transparent, and work with our community to
focus our efforts on fewer prioritized topics.
We hope to offer in the near future a process allowing anyone to propose a topic to the roadmap, but
we are not quite there yet. For the time being, the BDFL remains the keeper of the roadmap, and we
won't be accepting pull requests adding or removing items from this file.
# 1. Features and refactoring
## 1.1 Security
Security is a top objective for the Docker Engine. The most notable items we intend to provide in
the near future are:
- Trusted distribution of images: the effort is driven by the [distribution](https://github.com/docker/distribution)
group but will have significant impact on the Engine
- [User namespaces](https://github.com/docker/docker/pull/12648)
- [Seccomp support](https://github.com/docker/libcontainer/pull/613)
## 1.2 Plumbing project
We define a plumbing tool as a standalone piece of software usable and meaningful on its own. In
the current state of the Docker Engine, most subsystems provide independent functionalities (such
the builder, pushing and pulling images, running applications in a containerized environment, etc)
but all are coupled in a single binary. We want to offer the users to flexibility to use only the
pieces they need, and we will also gain in maintainability by splitting the project among multiple
repositories.
As it currently stands, the rough design outlines is to have:
- Low level plumbing tools, each dealing with one responsibility (e.g., [runC](https://runc.io))
- Docker subsystems services, each exposing an elementary concept over an API, and relying on one or
multiple lower level plumbing tools for their implementation (e.g., network management)
- Docker Engine to expose higher level actions (e.g., create a container with volume `V` and network
`N`), while still providing pass-through access to the individual subsystems.
The architectural details are still being worked on, but one thing we know for sure is that we need
to technically decouple the pieces.
### 1.2.1 Runtime
A Runtime tool already exists today in the form of [runC](https://github.com/opencontainers/runc).
We intend to modify the Engine to directly call out to a binary implementing the Open Containers
Specification such as runC rather than relying on libcontainer to set the container runtime up.
This plan will deprecate the existing [`execdriver`](https://github.com/docker/docker/tree/master/daemon/execdriver)
as different runtime backends will be implemented as separated binaries instead of being compiled
into the Engine.
### 1.2.2 Builder
The Builder (i.e., the ability to build an image from a Dockerfile) is already nicely decoupled,
but would benefit from being entirely separated from the Engine, and rely on the standard Engine
API for its operations.
### 1.2.3 Distribution
Distribution already has a [dedicated repository](https://github.com/docker/distribution) which
holds the implementation for Registry v2 and client libraries. We could imagine going further by
having the Engine call out to a binary providing image distribution related functionalities.
There are two short term goals related to image distribution. The first is stabilize and simplify
the push/pull code. Following that is the conversion to the more secure Registry V2 protocol.
### 1.2.4 Networking
Most of networking related code was already decoupled today in [libnetwork](https://github.com/docker/libnetwork).
As with other ingredients, we might want to take it a step further and make it a meaningful utility
that the Engine would call out to instead of a library.
## 1.3 Plugins
An initiative around plugins started with Docker 1.7.0, with the goal of allowing for out of
process extensibility of some Docker functionalities, starting with volumes and networking. The
approach is to provide specific extension points rather than generic hooking facilities. We also
deliberately keep the extensions API the simplest possible, expanding as we discover valid use
cases that cannot be implemented.
At the time of writing:
- Plugin support is merged as an experimental feature: real world use cases and user feedback will
help us refine the UX to make the feature more user friendly.
- There are no immediate plans to expand on the number of pluggable subsystems.
- Golang 1.5 might add language support for [plugins](https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ)
which we consider supporting as an alternative to JSON/HTTP.
## 1.4 Volume management
Volumes are not a first class citizen in the Engine today: we would like better volume management,
similar to the way network are managed in the new [CNM](https://github.com/docker/docker/issues/9983).
## 1.5 Better API implementation
The current Engine API is insufficiently typed, versioned, and ultimately hard to maintain. We
also suffer from the lack of a common implementation with [Swarm](https://github.com/docker/swarm).
## 1.6 Checkpoint/restore
Support for checkpoint/restore was [merged](https://github.com/docker/libcontainer/pull/479) in
[libcontainer](https://github.com/docker/libcontainer) and made available through [runC](https://runc.io):
we intend to take advantage of it in the Engine.
# 2 Frozen features
## 2.1 Docker exec
We won't accept patches expanding the surface of `docker exec`, which we intend to keep as a
*debugging* feature, as well as being strongly dependent on the the Runtime ingredient effort.
## 2.2 Dockerfile syntax
The Dockerfile syntax as we know it is simple, and has proven succesful in supporting all our
[official images](https://github.com/docker-library/official-images). Although this is *not* a
definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
reasons:
- Long term impact of syntax changes is a sensitive matter that require an amount of attention
the volume of Engine codebase and activity today doesn't allow us to provide.
- Allowing the Builder to be implemented as a separate utility consuming the Engine's API will
open the door for many possibilities, such as offering alternate syntaxes or DSL for existing
languages without cluttering the Engine's codebase.
- A standalone Builder will also offer the opportunity for a better dedicated group of maintainers
to own the Dockerfile syntax and decide collectively on the direction to give it.
- Our experience with official images tend to show that no new instruction or syntax expansion is
*strictly* necessary for the majority of use cases, and although we are aware many things are still
lacking for many, we cannot make it a priority yet for the above reasons.
Again, this is not about saying that the Dockerfile syntax is done, it's about making choices about
what we want to do first!
## 2.3 Remote Registry Operations
A large amount of work is ongoing in the area of image distribution and
provenance. This includes moving to the V2 Registry API and heavily
refactoring the code that powers these features. The desired result is more
secure, reliable and easier to use image distribution.
Part of the problem with this part of the code base is the lack of a stable
and flexible interface. If new features are added that access the registry
without solidifying these interfaces, achieving feature parity will continue
to be elusive. While we get a handle on this situation, we are imposing a
moratorium on new code that accesses the Registry API in commands that don't
already make remote calls.
Currently, only the following commands cause interaction with a remote
registry:
- push
- pull
- run
- build
- search
- login
In the interest of stabilizing the registry access model during this ongoing
work, we are not accepting additions to other commands that will cause remote
interaction with the Registry API. This moratorium will lift when the goals of
the distribution project have been met.
+1 -1
View File
@@ -1 +1 @@
1.8.0-rc3
1.5.0
+2
View File
@@ -0,0 +1,2 @@
Victor Vieux <vieux@docker.com> (@vieux)
Jessie Frazelle <jess@docker.com> (@jfrazelle)
-84
View File
@@ -1,84 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"io"
"net/url"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/signal"
)
// CmdAttach attaches to a running container.
//
// Usage: docker attach [OPTIONS] CONTAINER
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
if !c.State.Running {
return fmt.Errorf("You cannot attach to a stopped container, start it first")
}
if err := cli.CheckTtyInput(!*noStdin, c.Config.Tty); err != nil {
return err
}
if c.Config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
logrus.Debugf("Error monitoring TTY size: %s", err)
}
}
var in io.ReadCloser
v := url.Values{}
v.Set("stream", "1")
if !*noStdin && c.Config.OpenStdin {
v.Set("stdin", "1")
in = cli.in
}
v.Set("stdout", "1")
v.Set("stderr", "1")
if *proxy && !c.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
defer signal.StopCatch(sigc)
}
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), c.Config.Tty, in, cli.out, cli.err, nil, nil); err != nil {
return err
}
_, status, err := getExitCode(cli, cmd.Arg(0))
if err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
-634
View File
@@ -1,634 +0,0 @@
package client
import (
"archive/tar"
"bufio"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/docker/docker/api"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/graph/tags"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/jsonmessage"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/progressreader"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/pkg/units"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
const (
tarHeaderSize = 512
)
// CmdBuild builds a new image from the source code at a given path.
//
// If '-' is provided instead of a path or URL, Docker will build an image from either a Dockerfile or tar archive read from STDIN.
//
// Usage: docker build [OPTIONS] PATH | URL | -
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true)
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
flCpuPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
flCPUSetCpus := cmd.String([]string{"-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)")
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
ulimits := make(map[string]*ulimit.Ulimit)
flUlimits := opts.NewUlimitOpt(&ulimits)
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
cmd.Require(flag.Exact, 1)
// For trusted pull on "FROM <image>" instruction.
addTrustedFlags(cmd, true)
cmd.ParseFlags(args, true)
var (
context io.ReadCloser
isRemote bool
err error
)
_, err = exec.LookPath("git")
hasGit := err == nil
specifiedContext := cmd.Arg(0)
var (
contextDir string
tempDir string
relDockerfile string
)
switch {
case specifiedContext == "-":
tempDir, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
case urlutil.IsGitURL(specifiedContext) && hasGit:
tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
case urlutil.IsURL(specifiedContext):
tempDir, relDockerfile, err = getContextFromURL(cli.out, specifiedContext, *dockerfileName)
default:
contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
}
if err != nil {
return fmt.Errorf("unable to prepare context: %s", err)
}
if tempDir != "" {
defer os.RemoveAll(tempDir)
contextDir = tempDir
}
// Resolve the FROM lines in the Dockerfile to trusted digest references
// using Notary. On a successful build, we must tag the resolved digests
// to the original name specified in the Dockerfile.
newDockerfile, resolvedTags, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference)
if err != nil {
return fmt.Errorf("unable to process Dockerfile: %v", err)
}
defer newDockerfile.Close()
// And canonicalize dockerfile name to a platform-independent one
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
if err != nil {
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
}
var includes = []string{"."}
excludes, err := utils.ReadDockerIgnore(path.Join(contextDir, ".dockerignore"))
if err != nil {
return err
}
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
return fmt.Errorf("Error checking context: '%s'.", err)
}
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The deamon will remove them for us, if needed, after it
// parses the Dockerfile. Ignore errors here, as they will have been
// caught by ValidateContextDirectory above.
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
if keepThem1 || keepThem2 {
includes = append(includes, ".dockerignore", relDockerfile)
}
context, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
Compression: archive.Uncompressed,
ExcludePatterns: excludes,
IncludeFiles: includes,
})
if err != nil {
return err
}
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
context = replaceDockerfileTarWrapper(context, newDockerfile, relDockerfile)
// Setup an upload progress bar
// FIXME: ProgressReader shouldn't be this annoying to use
sf := streamformatter.NewStreamFormatter()
var body io.Reader = progressreader.New(progressreader.Config{
In: context,
Out: cli.out,
Formatter: sf,
NewLines: true,
ID: "",
Action: "Sending build context to Docker daemon",
})
var memory int64
if *flMemoryString != "" {
parsedMemory, err := units.RAMInBytes(*flMemoryString)
if err != nil {
return err
}
memory = parsedMemory
}
var memorySwap int64
if *flMemorySwap != "" {
if *flMemorySwap == "-1" {
memorySwap = -1
} else {
parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
if err != nil {
return err
}
memorySwap = parsedMemorySwap
}
}
// Send the build context
v := &url.Values{}
//Check if the given image name can be resolved
if *tag != "" {
repository, tag := parsers.ParseRepositoryTag(*tag)
if err := registry.ValidateRepositoryName(repository); err != nil {
return err
}
if len(tag) > 0 {
if err := tags.ValidateTagName(tag); err != nil {
return err
}
}
}
v.Set("t", *tag)
if *suppressOutput {
v.Set("q", "1")
}
if isRemote {
v.Set("remote", cmd.Arg(0))
}
if *noCache {
v.Set("nocache", "1")
}
if *rm {
v.Set("rm", "1")
} else {
v.Set("rm", "0")
}
if *forceRm {
v.Set("forcerm", "1")
}
if *pull {
v.Set("pull", "1")
}
v.Set("cpusetcpus", *flCPUSetCpus)
v.Set("cpusetmems", *flCPUSetMems)
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10))
v.Set("cpuperiod", strconv.FormatInt(*flCpuPeriod, 10))
v.Set("memory", strconv.FormatInt(memory, 10))
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
v.Set("cgroupparent", *flCgroupParent)
v.Set("dockerfile", relDockerfile)
ulimitsVar := flUlimits.GetList()
ulimitsJson, err := json.Marshal(ulimitsVar)
if err != nil {
return err
}
v.Set("ulimits", string(ulimitsJson))
headers := http.Header(make(map[string][]string))
buf, err := json.Marshal(cli.configFile.AuthConfigs)
if err != nil {
return err
}
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers.Set("Content-Type", "application/tar")
sopts := &streamOpts{
rawTerminal: true,
in: body,
out: cli.out,
headers: headers,
}
serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
// Windows: show error message about modified file permissions.
if runtime.GOOS == "windows" {
h, err := httputils.ParseServerHeader(serverResp.header.Get("Server"))
if err == nil {
if h.OS != "windows" {
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
}
}
}
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
if jerr.Code == 0 {
jerr.Code = 1
}
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
if err != nil {
return err
}
// Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite.
for _, resolved := range resolvedTags {
if err := cli.tagTrusted(resolved.repoInfo, resolved.digestRef, resolved.tagRef); err != nil {
return err
}
}
return nil
}
// getDockerfileRelPath uses the given context directory for a `docker build`
// and returns the absolute path to the context directory, the relative path of
// the dockerfile in that context directory, and a non-nil error on success.
func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
}
// The context dir might be a symbolic link, so follow it to the actual
// target directory.
absContextDir, err = filepath.EvalSymlinks(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
}
stat, err := os.Lstat(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
}
if !stat.IsDir() {
return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
}
absDockerfile := givenDockerfile
if absDockerfile == "" {
// No -f/--file was specified so use the default relative to the
// context directory.
absDockerfile = filepath.Join(absContextDir, api.DefaultDockerfileName)
// Just to be nice ;-) look for 'dockerfile' too but only
// use it if we found it, otherwise ignore this check
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
altPath := filepath.Join(absContextDir, strings.ToLower(api.DefaultDockerfileName))
if _, err = os.Lstat(altPath); err == nil {
absDockerfile = altPath
}
}
}
// If not already an absolute path, the Dockerfile path should be joined to
// the base directory.
if !filepath.IsAbs(absDockerfile) {
absDockerfile = filepath.Join(absContextDir, absDockerfile)
}
// Verify that 'filename' is within the build context
absDockerfile, err = symlink.FollowSymlinkInScope(absDockerfile, absContextDir)
if err != nil {
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
}
if _, err := os.Lstat(absDockerfile); err != nil {
if os.IsNotExist(err) {
return "", "", fmt.Errorf("Cannot locate Dockerfile: absDockerfile: %q", absDockerfile)
}
return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
}
if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
}
return absContextDir, relDockerfile, nil
}
// writeToFile copies from the given reader and writes it to a file with the
// given filename.
func writeToFile(r io.Reader, filename string) error {
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return fmt.Errorf("unable to create file: %v", err)
}
defer file.Close()
if _, err := io.Copy(file, r); err != nil {
return fmt.Errorf("unable to write file: %v", err)
}
return nil
}
// getContextFromReader will read the contents of the given reader as either a
// Dockerfile or tar archive to be extracted to a temporary directory used as
// the context directory. Returns the absolute path to the temporary context
// directory, the relative path of the dockerfile in that context directory,
// and a non-nil error on success.
func getContextFromReader(r io.Reader, dockerfileName string) (absContextDir, relDockerfile string, err error) {
buf := bufio.NewReader(r)
magic, err := buf.Peek(tarHeaderSize)
if err != nil && err != io.EOF {
return "", "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
}
if absContextDir, err = ioutil.TempDir("", "docker-build-context-"); err != nil {
return "", "", fmt.Errorf("unbale to create temporary context directory: %v", err)
}
defer func(d string) {
if err != nil {
os.RemoveAll(d)
}
}(absContextDir)
if !archive.IsArchive(magic) { // Input should be read as a Dockerfile.
// -f option has no meaning when we're reading it from stdin,
// so just use our default Dockerfile name
relDockerfile = api.DefaultDockerfileName
return absContextDir, relDockerfile, writeToFile(buf, filepath.Join(absContextDir, relDockerfile))
}
if err := archive.Untar(buf, absContextDir, nil); err != nil {
return "", "", fmt.Errorf("unable to extract stdin to temporary context direcotry: %v", err)
}
return getDockerfileRelPath(absContextDir, dockerfileName)
}
// getContextFromGitURL uses a Git URL as context for a `docker build`. The
// git repo is cloned into a temporary directory used as the context directory.
// Returns the absolute path to the temporary context directory, the relative
// path of the dockerfile in that context directory, and a non-nil error on
// success.
func getContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
if absContextDir, err = utils.GitClone(gitURL); err != nil {
return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
}
return getDockerfileRelPath(absContextDir, dockerfileName)
}
// getContextFromURL uses a remote URL as context for a `docker build`. The
// remote resource is downloaded as either a Dockerfile or a context tar
// archive and stored in a temporary directory used as the context directory.
// Returns the absolute path to the temporary context directory, the relative
// path of the dockerfile in that context directory, and a non-nil error on
// success.
func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
response, err := httputils.Download(remoteURL)
if err != nil {
return "", "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
}
defer response.Body.Close()
// Pass the response body through a progress reader.
progReader := &progressreader.Config{
In: response.Body,
Out: out,
Formatter: streamformatter.NewStreamFormatter(),
Size: int(response.ContentLength),
NewLines: true,
ID: "",
Action: fmt.Sprintf("Downloading build context from remote url: %s", remoteURL),
}
return getContextFromReader(progReader, dockerfileName)
}
// getContextFromLocalDir uses the given local directory as context for a
// `docker build`. Returns the absolute path to the local context directory,
// the relative path of the dockerfile in that context directory, and a non-nil
// error on success.
func getContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
// When using a local context directory, when the Dockerfile is specified
// with the `-f/--file` option then it is considered relative to the
// current directory and not the context directory.
if dockerfileName != "" {
if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
}
}
return getDockerfileRelPath(localDir, dockerfileName)
}
var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
type trustedDockerfile struct {
*os.File
size int64
}
func (td *trustedDockerfile) Close() error {
td.File.Close()
return os.Remove(td.File.Name())
}
// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
type resolvedTag struct {
repoInfo *registry.RepositoryInfo
digestRef, tagRef registry.Reference
}
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
// "FROM <image>" instructions to a digest reference. `translator` is a
// function that takes a repository name and tag reference and returns a
// trusted digest reference.
func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
dockerfile, err := os.Open(dockerfileName)
if err != nil {
return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err)
}
defer dockerfile.Close()
scanner := bufio.NewScanner(dockerfile)
// Make a tempfile to store the rewritten Dockerfile.
tempFile, err := ioutil.TempFile("", "trusted-dockerfile-")
if err != nil {
return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err)
}
trustedFile := &trustedDockerfile{
File: tempFile,
}
defer func() {
if err != nil {
// Close the tempfile if there was an error during Notary lookups.
// Otherwise the caller should close it.
trustedFile.Close()
}
}()
// Scan the lines of the Dockerfile, looking for a "FROM" line.
for scanner.Scan() {
line := scanner.Text()
matches := dockerfileFromLinePattern.FindStringSubmatch(line)
if matches != nil && matches[1] != "scratch" {
// Replace the line with a resolved "FROM repo@digest"
repo, tag := parsers.ParseRepositoryTag(matches[1])
if tag == "" {
tag = tags.DEFAULTTAG
}
repoInfo, err := registry.ParseRepositoryInfo(repo)
if err != nil {
return nil, nil, fmt.Errorf("unable to parse repository info: %v", err)
}
ref := registry.ParseReference(tag)
if !ref.HasDigest() && isTrusted() {
trustedRef, err := translator(repo, ref)
if err != nil {
return nil, nil, err
}
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo)))
resolvedTags = append(resolvedTags, &resolvedTag{
repoInfo: repoInfo,
digestRef: trustedRef,
tagRef: ref,
})
}
}
n, err := fmt.Fprintln(tempFile, line)
if err != nil {
return nil, nil, err
}
trustedFile.size += int64(n)
}
tempFile.Seek(0, os.SEEK_SET)
return trustedFile, resolvedTags, scanner.Err()
}
// replaceDockerfileTarWrapper wraps the given input tar archive stream and
// replaces the entry with the given Dockerfile name with the contents of the
// new Dockerfile. Returns a new tar archive stream with the replaced
// Dockerfile.
func replaceDockerfileTarWrapper(inputTarStream io.ReadCloser, newDockerfile *trustedDockerfile, dockerfileName string) io.ReadCloser {
pipeReader, pipeWriter := io.Pipe()
go func() {
tarReader := tar.NewReader(inputTarStream)
tarWriter := tar.NewWriter(pipeWriter)
defer inputTarStream.Close()
for {
hdr, err := tarReader.Next()
if err == io.EOF {
// Signals end of archive.
tarWriter.Close()
pipeWriter.Close()
return
}
if err != nil {
pipeWriter.CloseWithError(err)
return
}
var content io.Reader = tarReader
if hdr.Name == dockerfileName {
// This entry is the Dockerfile. Since the tar archive was
// generated from a directory on the local filesystem, the
// Dockerfile will only appear once in the archive.
hdr.Size = newDockerfile.size
content = newDockerfile
}
if err := tarWriter.WriteHeader(hdr); err != nil {
pipeWriter.CloseWithError(err)
return
}
if _, err := io.Copy(tarWriter, content); err != nil {
pipeWriter.CloseWithError(err)
return
}
}
}()
return pipeReader
}
+148 -121
View File
@@ -2,71 +2,116 @@ package client
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"reflect"
"strings"
"text/template"
"time"
"github.com/docker/docker/cli"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/sockets"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/pkg/tlsconfig"
"github.com/docker/docker/registry"
"github.com/docker/libtrust"
)
// DockerCli represents the docker command line client.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
// initializing closure
init func() error
// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
addr string
// basePath holds the path to prepend to the requests
basePath string
// configFile has the client configuration file
configFile *cliconfig.ConfigFile
// in holds the input stream and closer (io.ReadCloser) for the client.
in io.ReadCloser
// out holds the output stream (io.Writer) for the client.
out io.Writer
// err holds the error stream (io.Writer) for the client.
err io.Writer
// keyFile holds the key file as a string.
keyFile string
// tlsConfig holds the TLS configuration for the client, and will
// set the scheme to https in NewDockerCli if present.
tlsConfig *tls.Config
// scheme holds the scheme of the client i.e. https.
scheme string
// inFd holds the file descriptor of the client's STDIN (if valid).
proto string
addr string
configFile *registry.ConfigFile
in io.ReadCloser
out io.Writer
err io.Writer
key libtrust.PrivateKey
tlsConfig *tls.Config
scheme string
// inFd holds file descriptor of the client's STDIN, if it's a valid file
inFd uintptr
// outFd holds file descriptor of the client's STDOUT (if valid).
// outFd holds file descriptor of the client's STDOUT, if it's a valid file
outFd uintptr
// isTerminalIn indicates whether the client's STDIN is a TTY
// isTerminalIn describes if client's STDIN is a TTY
isTerminalIn bool
// isTerminalOut dindicates whether the client's STDOUT is a TTY
// isTerminalOut describes if client's STDOUT is a TTY
isTerminalOut bool
// transport holds the client transport instance.
transport *http.Transport
transport *http.Transport
}
func (cli *DockerCli) Initialize() error {
if cli.init == nil {
return nil
var funcMap = template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}
func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
camelArgs := make([]string, len(args))
for i, s := range args {
if len(s) == 0 {
return nil, false
}
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
}
return cli.init()
methodName := "Cmd" + strings.Join(camelArgs, "")
method := reflect.ValueOf(cli).MethodByName(methodName)
if !method.IsValid() {
return nil, false
}
return method.Interface().(func(...string) error), true
}
// Cmd executes the specified command
func (cli *DockerCli) Cmd(args ...string) error {
if len(args) > 1 {
method, exists := cli.getMethod(args[:2]...)
if exists {
return method(args[2:]...)
}
}
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
if !exists {
fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0])
os.Exit(1)
}
return method(args[1:]...)
}
return cli.CmdHelp()
}
func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet {
var errorHandling flag.ErrorHandling
if exitOnError {
errorHandling = flag.ExitOnError
} else {
errorHandling = flag.ContinueOnError
}
flags := flag.NewFlagSet(name, errorHandling)
flags.Usage = func() {
options := ""
if flags.FlagCountUndeprecated() > 0 {
options = "[OPTIONS] "
}
fmt.Fprintf(cli.out, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description)
flags.SetOutput(cli.out)
flags.PrintDefaults()
os.Exit(0)
}
return flags
}
func (cli *DockerCli) LoadConfigFile() (err error) {
cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
if err != nil {
fmt.Fprintf(cli.err, "WARNING: %s\n", err)
}
return err
}
// CheckTtyInput checks if we are trying to attach to a container tty
// from a non-tty client input stream, and if so, returns an error.
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
// In order to attach to a container tty, input stream for the client must
// be a tty itself: redirecting or piping the client standard input is
@@ -77,86 +122,68 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
return nil
}
func (cli *DockerCli) PsFormat() string {
return cli.configFile.PsFormat
}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli {
var (
inFd uintptr
outFd uintptr
isTerminalIn = false
isTerminalOut = false
scheme = "http"
)
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
// is set the client scheme will be set to https.
// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
cli := &DockerCli{
in: in,
out: out,
err: err,
keyFile: clientFlags.Common.TrustKey,
if tlsConfig != nil {
scheme = "https"
}
cli.init = func() error {
clientFlags.PostParse()
hosts := clientFlags.Common.Hosts
switch len(hosts) {
case 0:
defaultHost := os.Getenv("DOCKER_HOST")
if defaultHost == "" {
defaultHost = opts.DefaultHost
}
defaultHost, err := opts.ValidateHost(defaultHost)
if err != nil {
return err
}
hosts = []string{defaultHost}
case 1:
// only accept one host to talk to
default:
return errors.New("Please specify only one -H")
if in != nil {
if file, ok := in.(*os.File); ok {
inFd = file.Fd()
isTerminalIn = term.IsTerminal(inFd)
}
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
if cli.proto == "tcp" {
// error is checked in pkg/parsers already
parsed, _ := url.Parse("tcp://" + cli.addr)
cli.addr = parsed.Host
cli.basePath = parsed.Path
}
if clientFlags.Common.TLSOptions != nil {
cli.scheme = "https"
var e error
cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions)
if e != nil {
return e
}
} else {
cli.scheme = "http"
}
if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
}
if cli.out != nil {
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}
// The transport is created here for reuse during the client session.
cli.transport = &http.Transport{
TLSClientConfig: cli.tlsConfig,
}
sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr)
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
}
cli.configFile = configFile
return nil
}
return cli
if out != nil {
if file, ok := out.(*os.File); ok {
outFd = file.Fd()
isTerminalOut = term.IsTerminal(outFd)
}
}
if err == nil {
err = out
}
// The transport is created here for reuse during the client session
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: tlsConfig,
}
// Why 32? See issue 8035
timeout := 32 * time.Second
if proto == "unix" {
// no need in compressing for local communications
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, timeout)
}
} else {
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
}
return &DockerCli{
proto: proto,
addr: addr,
in: in,
out: out,
err: err,
key: key,
inFd: inFd,
outFd: outFd,
isTerminalIn: isTerminalIn,
isTerminalOut: isTerminalOut,
tlsConfig: tlsConfig,
scheme: scheme,
transport: tr,
}
}
-5
View File
@@ -1,5 +0,0 @@
// Package client provides a command-line interface for Docker.
//
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
// See https://docs.docker.com/installation/ for instructions on installing Docker.
package client
File diff suppressed because it is too large Load Diff
-84
View File
@@ -1,84 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
)
// CmdCommit creates a new image from a container's changes.
//
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true)
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
flChanges := opts.NewListOpts(nil)
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
// FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands.
flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
cmd.Require(flag.Max, 2)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
name = cmd.Arg(0)
repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1))
)
//Check if the given image name can be resolved
if repository != "" {
if err := registry.ValidateRepositoryName(repository); err != nil {
return err
}
}
v := url.Values{}
v.Set("container", name)
v.Set("repo", repository)
v.Set("tag", tag)
v.Set("comment", *flComment)
v.Set("author", *flAuthor)
for _, change := range flChanges.GetAll() {
v.Add("changes", change)
}
if *flPause != true {
v.Set("pause", "0")
}
var (
config *runconfig.Config
response types.ContainerCommitResponse
)
if *flConfig != "" {
config = &runconfig.Config{}
if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
return err
}
}
serverResp, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
return err
}
fmt.Fprintln(cli.out, response.ID)
return nil
}
-324
View File
@@ -1,324 +0,0 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/archive"
flag "github.com/docker/docker/pkg/mflag"
)
type copyDirection int
const (
fromContainer copyDirection = (1 << iota)
toContainer
acrossContainers = fromContainer | toContainer
)
// CmdCp copies files/folders to or from a path in a container.
//
// When copying from a container, if LOCALPATH is '-' the data is written as a
// tar archive file to STDOUT.
//
// When copying to a container, if LOCALPATH is '-' the data is read as a tar
// archive file from STDIN, and the destination CONTAINER:PATH, must specify
// a directory.
//
// Usage:
// docker cp CONTAINER:PATH LOCALPATH|-
// docker cp LOCALPATH|- CONTAINER:PATH
func (cli *DockerCli) CmdCp(args ...string) error {
cmd := Cli.Subcmd(
"cp",
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
strings.Join([]string{
"Copy files/folders between a container and your host.\n",
"Use '-' as the source to read a tar archive from stdin\n",
"and extract it to a directory destination in a container.\n",
"Use '-' as the destination to stream a tar archive of a\n",
"container source to stdout.",
}, ""),
true,
)
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
if cmd.Arg(0) == "" {
return fmt.Errorf("source can not be empty")
}
if cmd.Arg(1) == "" {
return fmt.Errorf("destination can not be empty")
}
srcContainer, srcPath := splitCpArg(cmd.Arg(0))
dstContainer, dstPath := splitCpArg(cmd.Arg(1))
var direction copyDirection
if srcContainer != "" {
direction |= fromContainer
}
if dstContainer != "" {
direction |= toContainer
}
switch direction {
case fromContainer:
return cli.copyFromContainer(srcContainer, srcPath, dstPath)
case toContainer:
return cli.copyToContainer(srcPath, dstContainer, dstPath)
case acrossContainers:
// Copying between containers isn't supported.
return fmt.Errorf("copying between containers is not supported")
default:
// User didn't specify any container.
return fmt.Errorf("must specify at least one container source")
}
}
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
// requiring a LOCALPATH with a `:` to be made explicit with a relative or
// absolute path:
// `/path/to/file:name.txt` or `./file:name.txt`
//
// This is apparently how `scp` handles this as well:
// http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
//
// We can't simply check for a filepath separator because container names may
// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
// client, a `:` could be part of an absolute Windows path, in which case it
// is immediately proceeded by a backslash.
func splitCpArg(arg string) (container, path string) {
if filepath.IsAbs(arg) {
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
return "", arg
}
parts := strings.SplitN(arg, ":", 2)
if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
// Either there's no `:` in the arg
// OR it's an explicit local relative path like `./file:name.txt`.
return "", arg
}
return parts[0], parts[1]
}
func (cli *DockerCli) statContainerPath(containerName, path string) (types.ContainerPathStat, error) {
var stat types.ContainerPathStat
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
urlStr := fmt.Sprintf("/containers/%s/archive?%s", containerName, query.Encode())
response, err := cli.call("HEAD", urlStr, nil, nil)
if err != nil {
return stat, err
}
defer response.body.Close()
if response.statusCode != http.StatusOK {
return stat, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
return getContainerPathStatFromHeader(response.header)
}
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
var stat types.ContainerPathStat
encodedStat := header.Get("X-Docker-Container-Path-Stat")
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
err := json.NewDecoder(statDecoder).Decode(&stat)
if err != nil {
err = fmt.Errorf("unable to decode container path stat header: %s", err)
}
return stat, err
}
func resolveLocalPath(localPath string) (absPath string, err error) {
if absPath, err = filepath.Abs(localPath); err != nil {
return
}
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
}
func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string) (err error) {
if dstPath != "-" {
// Get an absolute destination path.
dstPath, err = resolveLocalPath(dstPath)
if err != nil {
return err
}
}
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
urlStr := fmt.Sprintf("/containers/%s/archive?%s", srcContainer, query.Encode())
response, err := cli.call("GET", urlStr, nil, nil)
if err != nil {
return err
}
defer response.body.Close()
if response.statusCode != http.StatusOK {
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
if dstPath == "-" {
// Send the response to STDOUT.
_, err = io.Copy(os.Stdout, response.body)
return err
}
// In order to get the copy behavior right, we need to know information
// about both the source and the destination. The response headers include
// stat info about the source that we can use in deciding exactly how to
// copy it locally. Along with the stat info about the local destination,
// we have everything we need to handle the multiple possibilities there
// can be when copying a file/dir from one location to another file/dir.
stat, err := getContainerPathStatFromHeader(response.header)
if err != nil {
return fmt.Errorf("unable to get resource stat from response: %s", err)
}
// Prepare source copy info.
srcInfo := archive.CopyInfo{
Path: srcPath,
Exists: true,
IsDir: stat.Mode.IsDir(),
}
// See comments in the implementation of `archive.CopyTo` for exactly what
// goes into deciding how and whether the source archive needs to be
// altered for the correct copy behavior.
return archive.CopyTo(response.body, srcInfo, dstPath)
}
func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (err error) {
if srcPath != "-" {
// Get an absolute source path.
srcPath, err = resolveLocalPath(srcPath)
if err != nil {
return err
}
}
// In order to get the copy behavior right, we need to know information
// about both the source and destination. The API is a simple tar
// archive/extract API but we can use the stat info header about the
// destination to be more informed about exactly what the destination is.
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
dstStat, err := cli.statContainerPath(dstContainer, dstPath)
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
linkTarget := dstStat.LinkTarget
if !filepath.IsAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath)
linkTarget = filepath.Join(dstParent, linkTarget)
}
dstInfo.Path = linkTarget
dstStat, err = cli.statContainerPath(dstContainer, linkTarget)
}
// Ignore any error and assume that the parent directory of the destination
// path exists, in which case the copy may still succeed. If there is any
// type of conflict (e.g., non-directory overwriting an existing directory
// or vice versia) the extraction will fail. If the destination simply did
// not exist, but the parent directory does, the extraction will still
// succeed.
if err == nil {
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
}
var (
content io.Reader
resolvedDstPath string
)
if srcPath == "-" {
// Use STDIN.
content = os.Stdin
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir {
return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
}
} else {
// Prepare source copy info.
srcInfo, err := archive.CopyInfoSourcePath(srcPath)
if err != nil {
return err
}
srcArchive, err := archive.TarResource(srcInfo)
if err != nil {
return err
}
defer srcArchive.Close()
// With the stat info about the local source as well as the
// destination, we have enough information to know whether we need to
// alter the archive that we upload so that when the server extracts
// it to the specified directory in the container we get the disired
// copy behavior.
// See comments in the implementation of `archive.PrepareArchiveCopy`
// for exactly what goes into deciding how and whether the source
// archive needs to be altered for the correct copy behavior when it is
// extracted. This function also infers from the source and destination
// info which directory to extract to, which may be the parent of the
// destination that the user specified.
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
if err != nil {
return err
}
defer preparedArchive.Close()
resolvedDstPath = dstDir
content = preparedArchive
}
query := make(url.Values, 2)
query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
query.Set("noOverwriteDirNonDir", "true")
urlStr := fmt.Sprintf("/containers/%s/archive?%s", dstContainer, query.Encode())
response, err := cli.stream("PUT", urlStr, &streamOpts{in: content})
if err != nil {
return err
}
defer response.body.Close()
if response.statusCode != http.StatusOK {
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
return nil
}
-185
View File
@@ -1,185 +0,0 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"strings"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/graph/tags"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
)
func (cli *DockerCli) pullImage(image string) error {
return cli.pullImageCustomOut(image, cli.out)
}
func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
v := url.Values{}
repos, tag := parsers.ParseRepositoryTag(image)
// pull only the image tagged 'latest' if no tag was specified
if tag == "" {
tag = tags.DEFAULTTAG
}
v.Set("fromImage", repos)
v.Set("tag", tag)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(repos)
if err != nil {
return err
}
// Resolve the Auth config relevant for this server
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
buf, err := json.Marshal(authConfig)
if err != nil {
return err
}
registryAuthHeader := []string{
base64.URLEncoding.EncodeToString(buf),
}
sopts := &streamOpts{
rawTerminal: true,
out: out,
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
}
if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
return err
}
return nil
}
type cidFile struct {
path string
file *os.File
written bool
}
func newCIDFile(path string) (*cidFile, error) {
if _, err := os.Stat(path); err == nil {
return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
}
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
}
return &cidFile{path: path, file: f}, nil
}
func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
containerValues := url.Values{}
if name != "" {
containerValues.Set("name", name)
}
mergedConfig := runconfig.MergeConfigs(config, hostConfig)
var containerIDFile *cidFile
if cidfile != "" {
var err error
if containerIDFile, err = newCIDFile(cidfile); err != nil {
return nil, err
}
defer containerIDFile.Close()
}
repo, tag := parsers.ParseRepositoryTag(config.Image)
if tag == "" {
tag = tags.DEFAULTTAG
}
ref := registry.ParseReference(tag)
var trustedRef registry.Reference
if isTrusted() && !ref.HasDigest() {
var err error
trustedRef, err = cli.trustedReference(repo, ref)
if err != nil {
return nil, err
}
config.Image = trustedRef.ImageName(repo)
}
//create the container
serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
//if image not found try to pull it
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.ImageName(repo))
// we don't want to write to stdout anything apart from container.ID
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
return nil, err
}
if trustedRef != nil && !ref.HasDigest() {
repoInfo, err := registry.ParseRepositoryInfo(repo)
if err != nil {
return nil, err
}
if err := cli.tagTrusted(repoInfo, trustedRef, ref); err != nil {
return nil, err
}
}
// Retry
if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
defer serverResp.body.Close()
var response types.ContainerCreateResponse
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
return nil, err
}
for _, warning := range response.Warnings {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}
if containerIDFile != nil {
if err = containerIDFile.Write(response.ID); err != nil {
return nil, err
}
}
return &response, nil
}
// CmdCreate creates a new container from a given image.
//
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
func (cli *DockerCli) CmdCreate(args ...string) error {
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new container", true)
addTrustedFlags(cmd, true)
// These are flags not stored in Config/HostConfig
var (
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
)
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
if err != nil {
cmd.ReportError(err.Error(), true)
os.Exit(1)
}
if config.Image == "" {
cmd.Usage()
return nil
}
response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", response.ID)
return nil
}
-56
View File
@@ -1,56 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/archive"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdDiff shows changes on a container's filesystem.
//
// Each changed file is printed on a separate line, prefixed with a single
// character that indicates the status of the file: C (modified), A (added),
// or D (deleted).
//
// Usage: docker diff CONTAINER
func (cli *DockerCli) CmdDiff(args ...string) error {
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, "Inspect changes on a container's filesystem", true)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
if cmd.Arg(0) == "" {
return fmt.Errorf("Container name cannot be empty")
}
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
changes := []types.ContainerChange{}
if err := json.NewDecoder(serverResp.body).Decode(&changes); err != nil {
return err
}
for _, change := range changes {
var kind string
switch change.Kind {
case archive.ChangeModify:
kind = "C"
case archive.ChangeAdd:
kind = "A"
case archive.ChangeDelete:
kind = "D"
}
fmt.Fprintf(cli.out, "%s %s\n", kind, change.Path)
}
return nil
}
-63
View File
@@ -1,63 +0,0 @@
package client
import (
"net/url"
"time"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/pkg/timeutils"
)
// CmdEvents prints a live stream of real time events from the server.
//
// Usage: docker events [OPTIONS]
func (cli *DockerCli) CmdEvents(args ...string) error {
cmd := Cli.Subcmd("events", nil, "Get real time events from the server", true)
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
var (
v = url.Values{}
eventFilterArgs = filters.Args{}
)
// Consolidate all filter flags, and sanity check them early.
// They'll get process in the daemon/server.
for _, f := range flFilter.GetAll() {
var err error
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
if err != nil {
return err
}
}
ref := time.Now()
if *since != "" {
v.Set("since", timeutils.GetTimestamp(*since, ref))
}
if *until != "" {
v.Set("until", timeutils.GetTimestamp(*until, ref))
}
if len(eventFilterArgs) > 0 {
filterJSON, err := filters.ToParam(eventFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJSON)
}
sopts := &streamOpts{
rawTerminal: true,
out: cli.out,
}
if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
return err
}
return nil
}
-134
View File
@@ -1,134 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"io"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/runconfig"
)
// CmdExec runs a command in a running container.
//
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
func (cli *DockerCli) CmdExec(args ...string) error {
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, "Run a command in a running container", true)
execConfig, err := runconfig.ParseExec(cmd, args)
// just in case the ParseExec does not exit
if execConfig.Container == "" || err != nil {
return Cli.StatusError{StatusCode: 1}
}
serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
var response types.ContainerExecCreateResponse
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
return err
}
execID := response.ID
if execID == "" {
fmt.Fprintf(cli.out, "exec ID empty")
return nil
}
//Temp struct for execStart so that we don't need to transfer all the execConfig
execStartCheck := &types.ExecStartCheck{
Detach: execConfig.Detach,
Tty: execConfig.Tty,
}
if !execConfig.Detach {
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
return err
}
} else {
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil {
return err
}
// For now don't print this - wait for when we support exec wait()
// fmt.Fprintf(cli.out, "%s\n", execID)
return nil
}
// Interactive exec requested.
var (
out, stderr io.Writer
in io.ReadCloser
hijacked = make(chan io.Closer)
errCh chan error
)
// Block the return until the chan gets closed
defer func() {
logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.")
if _, ok := <-hijacked; ok {
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
}
}()
if execConfig.AttachStdin {
in = cli.in
}
if execConfig.AttachStdout {
out = cli.out
}
if execConfig.AttachStderr {
if execConfig.Tty {
stderr = cli.out
} else {
stderr = cli.err
}
}
errCh = promise.Go(func() error {
return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig)
})
// Acknowledge the hijack before starting
select {
case closer := <-hijacked:
// Make sure that hijack gets closed when returning. (result
// in closing hijack chan and freeing server's goroutines.
if closer != nil {
defer closer.Close()
}
case err := <-errCh:
if err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
}
if execConfig.Tty && cli.isTerminalIn {
if err := cli.monitorTtySize(execID, true); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if err := <-errCh; err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
var status int
if _, status, err = getExecExitCode(cli, execID); err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
-47
View File
@@ -1,47 +0,0 @@
package client
import (
"errors"
"io"
"os"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdExport exports a filesystem as a tar archive.
//
// The tar archive is streamed to STDOUT by default or written to a file.
//
// Usage: docker export [OPTIONS] CONTAINER
func (cli *DockerCli) CmdExport(args ...string) error {
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, "Export the contents of a container's filesystem as a tar archive", true)
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
var (
output io.Writer = cli.out
err error
)
if *outfile != "" {
output, err = os.Create(*outfile)
if err != nil {
return err
}
} else if cli.isTerminalOut {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
image := cmd.Arg(0)
sopts := &streamOpts{
rawTerminal: true,
out: output,
}
if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
return err
}
return nil
}
+9 -34
View File
@@ -13,9 +13,9 @@ import (
"strings"
"time"
"github.com/Sirupsen/logrus"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
@@ -72,15 +72,6 @@ func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Con
if err != nil {
return nil, err
}
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
@@ -138,33 +129,17 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
if err != nil {
return err
}
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), params)
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
if err != nil {
return err
}
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k, v := range cli.configFile.HTTPHeaders {
req.Header.Set(k, v)
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
req.Header.Set("Content-Type", "text/plain")
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "tcp")
req.Host = cli.addr
dial, err := cli.dial()
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := dial.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
@@ -218,7 +193,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
} else {
_, err = stdcopy.StdCopy(stdout, stderr, br)
}
logrus.Debugf("[hijack] End of stdout")
log.Debugf("[hijack] End of stdout")
return err
})
}
@@ -226,14 +201,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
sendStdin := promise.Go(func() error {
if in != nil {
io.Copy(rwc, in)
logrus.Debugf("[hijack] End of stdin")
log.Debugf("[hijack] End of stdin")
}
if conn, ok := rwc.(interface {
CloseWrite() error
}); ok {
if err := conn.CloseWrite(); err != nil {
logrus.Debugf("Couldn't send EOF: %s", err)
log.Debugf("Couldn't send EOF: %s", err)
}
}
// Discard errors due to pipe interruption
@@ -242,14 +217,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
if stdout != nil || stderr != nil {
if err := <-receiveStdout; err != nil {
logrus.Debugf("Error receiveStdout: %s", err)
log.Debugf("Error receiveStdout: %s", err)
return err
}
}
if !cli.isTerminalIn {
if err := <-sendStdin; err != nil {
logrus.Debugf("Error sendStdin: %s", err)
log.Debugf("Error sendStdin: %s", err)
return err
}
}
-77
View File
@@ -1,77 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"text/tabwriter"
"time"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/units"
)
// CmdHistory shows the history of an image.
//
// Usage: docker history [OPTIONS] IMAGE
func (cli *DockerCli) CmdHistory(args ...string) error {
cmd := Cli.Subcmd("history", []string{"IMAGE"}, "Show the history of an image", true)
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
history := []types.ImageHistory{}
if err := json.NewDecoder(serverResp.body).Decode(&history); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\tCOMMENT")
}
for _, entry := range history {
if *noTrunc {
fmt.Fprintf(w, entry.ID)
} else {
fmt.Fprintf(w, stringid.TruncateID(entry.ID))
}
if !*quiet {
if *human {
fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0))))
} else {
fmt.Fprintf(w, "\t%s\t", time.Unix(entry.Created, 0).Format(time.RFC3339))
}
if *noTrunc {
fmt.Fprintf(w, "%s\t", entry.CreatedBy)
} else {
fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45))
}
if *human {
fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size)))
} else {
fmt.Fprintf(w, "%d\t", entry.Size)
}
fmt.Fprintf(w, "%s", entry.Comment)
}
fmt.Fprintf(w, "\n")
}
w.Flush()
return nil
}
-130
View File
@@ -1,130 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"text/tabwriter"
"time"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/units"
"github.com/docker/docker/utils"
)
// CmdImages lists the images in a specified repository, or all top-level images if no repository is specified.
//
// Usage: docker images [OPTIONS] [REPOSITORY]
func (cli *DockerCli) CmdImages(args ...string) error {
cmd := Cli.Subcmd("images", []string{"[REPOSITORY]"}, "List images", true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Max, 1)
cmd.ParseFlags(args, true)
// Consolidate all filter flags, and sanity check them early.
// They'll get process in the daemon/server.
imageFilterArgs := filters.Args{}
for _, f := range flFilter.GetAll() {
var err error
imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs)
if err != nil {
return err
}
}
matchName := cmd.Arg(0)
v := url.Values{}
if len(imageFilterArgs) > 0 {
filterJSON, err := filters.ToParam(imageFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJSON)
}
if cmd.NArg() == 1 {
// FIXME rename this parameter, to not be confused with the filters flag
v.Set("filter", matchName)
}
if *all {
v.Set("all", "1")
}
serverResp, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
images := []types.Image{}
if err := json.NewDecoder(serverResp.body).Decode(&images); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
if *showDigests {
fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE")
} else {
fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE")
}
}
for _, image := range images {
ID := image.ID
if !*noTrunc {
ID = stringid.TruncateID(ID)
}
repoTags := image.RepoTags
repoDigests := image.RepoDigests
if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
// dangling image - clear out either repoTags or repoDigsts so we only show it once below
repoDigests = []string{}
}
// combine the tags and digests lists
tagsAndDigests := append(repoTags, repoDigests...)
for _, repoAndRef := range tagsAndDigests {
repo, ref := parsers.ParseRepositoryTag(repoAndRef)
// default tag and digest to none - if there's a value, it'll be set below
tag := "<none>"
digest := "<none>"
if utils.DigestReference(ref) {
digest = ref
} else {
tag = ref
}
if !*quiet {
if *showDigests {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize)))
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize)))
}
} else {
fmt.Fprintln(w, ID)
}
}
}
if !*quiet {
w.Flush()
}
return nil
}
-77
View File
@@ -1,77 +0,0 @@
package client
import (
"fmt"
"io"
"net/url"
"os"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry"
)
// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image.
//
// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file or a path to local file relative to docker client. If the URL is '-', then the tar file is read from STDIN.
//
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
func (cli *DockerCli) CmdImport(args ...string) error {
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
flChanges := opts.NewListOpts(nil)
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
v = url.Values{}
src = cmd.Arg(0)
repository = cmd.Arg(1)
)
v.Set("fromSrc", src)
v.Set("repo", repository)
for _, change := range flChanges.GetAll() {
v.Add("changes", change)
}
if cmd.NArg() == 3 {
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'file|URL|- [REPOSITORY [TAG]]' has been deprecated. Please use file|URL|- [REPOSITORY[:TAG]]\n")
v.Set("tag", cmd.Arg(2))
}
if repository != "" {
//Check if the given image name can be resolved
repo, _ := parsers.ParseRepositoryTag(repository)
if err := registry.ValidateRepositoryName(repo); err != nil {
return err
}
}
var in io.Reader
if src == "-" {
in = cli.in
} else if !urlutil.IsURL(src) {
v.Set("fromSrc", "-")
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
in = file
}
sopts := &streamOpts{
rawTerminal: true,
in: in,
out: cli.out,
}
_, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts)
return err
}
-108
View File
@@ -1,108 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/units"
)
// CmdInfo displays system-wide information.
//
// Usage: docker info
func (cli *DockerCli) CmdInfo(args ...string) error {
cmd := Cli.Subcmd("info", nil, "Display system-wide information", true)
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/info", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
info := &types.Info{}
if err := json.NewDecoder(serverResp.body).Decode(info); err != nil {
return fmt.Errorf("Error reading remote info: %v", err)
}
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
if info.DriverStatus != nil {
for _, pair := range info.DriverStatus {
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
}
}
ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU)
fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
ioutils.FprintfIfNotEmpty(cli.out, "Name: %s\n", info.Name)
ioutils.FprintfIfNotEmpty(cli.out, "ID: %s\n", info.ID)
if info.Debug {
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
fmt.Fprintf(cli.out, "File Descriptors: %d\n", info.NFd)
fmt.Fprintf(cli.out, "Goroutines: %d\n", info.NGoroutines)
fmt.Fprintf(cli.out, "System Time: %s\n", info.SystemTime)
fmt.Fprintf(cli.out, "EventsListeners: %d\n", info.NEventsListener)
fmt.Fprintf(cli.out, "Init SHA1: %s\n", info.InitSha1)
fmt.Fprintf(cli.out, "Init Path: %s\n", info.InitPath)
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
}
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HttpProxy)
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HttpsProxy)
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
if info.IndexServerAddress != "" {
u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username
if len(u) > 0 {
fmt.Fprintf(cli.out, "Username: %v\n", u)
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
}
}
// Only output these warnings if the server supports these features
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
if h.OS != "windows" {
if !info.MemoryLimit {
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
}
if !info.SwapLimit {
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
}
if !info.IPv4Forwarding {
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
}
if !info.BridgeNfIptables {
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-iptables is disabled\n")
}
if !info.BridgeNfIp6tables {
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled\n")
}
}
}
if info.Labels != nil {
fmt.Fprintln(cli.out, "Labels:")
for _, attribute := range info.Labels {
fmt.Fprintf(cli.out, " %s\n", attribute)
}
}
if info.ExperimentalBuild {
fmt.Fprintf(cli.out, "Experimental: true\n")
}
return nil
}
-157
View File
@@ -1,157 +0,0 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"text/template"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
var funcMap = template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}
// CmdInspect displays low-level information on one or more containers or images.
//
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
func (cli *DockerCli) CmdInspect(args ...string) error {
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", true)
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var tmpl *template.Template
var err error
var obj []byte
if *tmplStr != "" {
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
return Cli.StatusError{StatusCode: 64,
Status: "Template parsing error: " + err.Error()}
}
}
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
}
indented := new(bytes.Buffer)
indented.WriteString("[\n")
status := 0
isImage := false
for _, name := range cmd.Args() {
if *inspectType == "" || *inspectType == "container" {
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
if err != nil && *inspectType == "container" {
if strings.Contains(err.Error(), "No such") {
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
} else {
fmt.Fprintf(cli.err, "%s", err)
}
status = 1
continue
}
}
if obj == nil && (*inspectType == "" || *inspectType == "image") {
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
isImage = true
if err != nil {
if strings.Contains(err.Error(), "No such") {
if *inspectType == "" {
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
} else {
fmt.Fprintf(cli.err, "Error: No such image: %s\n", name)
}
} else {
fmt.Fprintf(cli.err, "%s", err)
}
status = 1
continue
}
}
if tmpl == nil {
if err := json.Indent(indented, obj, "", " "); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
} else {
rdr := bytes.NewReader(obj)
dec := json.NewDecoder(rdr)
if isImage {
inspPtr := types.ImageInspect{}
if err := dec.Decode(&inspPtr); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
if err := tmpl.Execute(cli.out, inspPtr); err != nil {
rdr.Seek(0, 0)
var raw interface{}
if err := dec.Decode(&raw); err != nil {
return err
}
if err = tmpl.Execute(cli.out, raw); err != nil {
return err
}
}
} else {
inspPtr := types.ContainerJSON{}
if err := dec.Decode(&inspPtr); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
if err := tmpl.Execute(cli.out, inspPtr); err != nil {
rdr.Seek(0, 0)
var raw interface{}
if err := dec.Decode(&raw); err != nil {
return err
}
if err = tmpl.Execute(cli.out, raw); err != nil {
return err
}
}
}
cli.out.Write([]byte{'\n'})
}
indented.WriteString(",")
}
if indented.Len() > 1 {
// Remove trailing ','
indented.Truncate(indented.Len() - 1)
}
indented.WriteString("]\n")
if tmpl == nil {
// Note that we will always write "[]" when "-f" isn't specified,
// to make sure the output would always be array, see
// https://github.com/docker/docker/pull/9500#issuecomment-65846734
if _, err := io.Copy(cli.out, indented); err != nil {
return err
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
-33
View File
@@ -1,33 +0,0 @@
package client
import (
"fmt"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdKill kills one or more running container using SIGKILL or a specified signal.
//
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdKill(args ...string) error {
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, "Kill a running container using SIGKILL or a specified signal", true)
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var errNames []string
for _, name := range cmd.Args() {
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, nil)); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to kill containers: %v", errNames)
}
return nil
}
-42
View File
@@ -1,42 +0,0 @@
package client
import (
"io"
"os"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdLoad loads an image from a tar archive.
//
// The tar archive is read from STDIN by default, or from a tar archive file.
//
// Usage: docker load [OPTIONS]
func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := Cli.Subcmd("load", nil, "Load an image from a tar archive or STDIN", true)
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
var (
input io.Reader = cli.in
err error
)
if *infile != "" {
input, err = os.Open(*infile)
if err != nil {
return err
}
}
sopts := &streamOpts{
rawTerminal: true,
in: input,
out: cli.out,
}
if _, err := cli.stream("POST", "/images/load", sopts); err != nil {
return err
}
return nil
}
-147
View File
@@ -1,147 +0,0 @@
package client
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/cliconfig"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
)
// CmdLogin logs in or registers a user to a Docker registry service.
//
// If no server is specified, the user will be logged into or registered to the registry's index server.
//
// Usage: docker login SERVER
func (cli *DockerCli) CmdLogin(args ...string) error {
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
cmd.Require(flag.Max, 1)
var username, password, email string
cmd.StringVar(&username, []string{"u", "-username"}, "", "Username")
cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
cmd.ParseFlags(args, true)
serverAddress := registry.IndexServer
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
}
promptDefault := func(prompt string, configDefault string) {
if configDefault == "" {
fmt.Fprintf(cli.out, "%s: ", prompt)
} else {
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
}
}
readInput := func(in io.Reader, out io.Writer) string {
reader := bufio.NewReader(in)
line, _, err := reader.ReadLine()
if err != nil {
fmt.Fprintln(out, err.Error())
os.Exit(1)
}
return string(line)
}
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
if !ok {
authconfig = cliconfig.AuthConfig{}
}
if username == "" {
promptDefault("Username", authconfig.Username)
username = readInput(cli.in, cli.out)
username = strings.Trim(username, " ")
if username == "" {
username = authconfig.Username
}
}
// Assume that a different username means they may not want to use
// the password or email from the config file, so prompt them
if username != authconfig.Username {
if password == "" {
oldState, err := term.SaveState(cli.inFd)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Password: ")
term.DisableEcho(cli.inFd, oldState)
password = readInput(cli.in, cli.out)
fmt.Fprint(cli.out, "\n")
term.RestoreTerminal(cli.inFd, oldState)
if password == "" {
return fmt.Errorf("Error : Password Required")
}
}
if email == "" {
promptDefault("Email", authconfig.Email)
email = readInput(cli.in, cli.out)
if email == "" {
email = authconfig.Email
}
}
} else {
// However, if they don't override the username use the
// password or email from the cmd line if specified. IOW, allow
// then to change/override them. And if not specified, just
// use what's in the config file
if password == "" {
password = authconfig.Password
}
if email == "" {
email = authconfig.Email
}
}
authconfig.Username = username
authconfig.Password = password
authconfig.Email = email
authconfig.ServerAddress = serverAddress
cli.configFile.AuthConfigs[serverAddress] = authconfig
serverResp, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
if serverResp.statusCode == 401 {
delete(cli.configFile.AuthConfigs, serverAddress)
if err2 := cli.configFile.Save(); err2 != nil {
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
}
return err
}
if err != nil {
return err
}
defer serverResp.body.Close()
var response types.AuthResponse
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
// Upon error, remove entry
delete(cli.configFile.AuthConfigs, serverAddress)
return err
}
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Error saving config file: %v", err)
}
fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename())
if response.Status != "" {
fmt.Fprintf(cli.out, "%s\n", response.Status)
}
return nil
}
-38
View File
@@ -1,38 +0,0 @@
package client
import (
"fmt"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/registry"
)
// CmdLogout logs a user out from a Docker registry.
//
// If no server is specified, the user will be logged out from the registry's index server.
//
// Usage: docker logout [SERVER]
func (cli *DockerCli) CmdLogout(args ...string) error {
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
cmd.Require(flag.Max, 1)
cmd.ParseFlags(args, true)
serverAddress := registry.IndexServer
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
}
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
} else {
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
delete(cli.configFile.AuthConfigs, serverAddress)
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Failed to save docker config: %v", err)
}
}
return nil
}
-69
View File
@@ -1,69 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/timeutils"
)
// CmdLogs fetches the logs of a given container.
//
// docker logs [OPTIONS] CONTAINER
func (cli *DockerCli) CmdLogs(args ...string) error {
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, "Fetch the logs of a container", true)
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
name := cmd.Arg(0)
serverResp, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
if err != nil {
return err
}
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
if logType := c.HostConfig.LogConfig.Type; logType != "json-file" {
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver (got: %s)", logType)
}
v := url.Values{}
v.Set("stdout", "1")
v.Set("stderr", "1")
if *since != "" {
v.Set("since", timeutils.GetTimestamp(*since, time.Now()))
}
if *times {
v.Set("timestamps", "1")
}
if *follow {
v.Set("follow", "1")
}
v.Set("tail", *tail)
sopts := &streamOpts{
rawTerminal: c.Config.Tty,
out: cli.out,
err: cli.err,
}
_, err = cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
return err
}
-15
View File
@@ -1,15 +0,0 @@
// +build experimental
package client
import (
"os"
nwclient "github.com/docker/libnetwork/client"
)
func (cli *DockerCli) CmdNetwork(args ...string) error {
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
args = append([]string{"network"}, args...)
return nCli.Cmd(os.Args[0], args...)
}
-32
View File
@@ -1,32 +0,0 @@
package client
import (
"fmt"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdPause pauses all processes within one or more containers.
//
// Usage: docker pause CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdPause(args ...string) error {
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, "Pause all processes within a container", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var errNames []string
for _, name := range cmd.Args() {
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, nil)); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to pause containers: %v", errNames)
}
return nil
}
-72
View File
@@ -1,72 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"strings"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/nat"
)
// CmdPort lists port mappings for a container.
// If a private port is specified, it also shows the public-facing port that is NATed to the private port.
//
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
func (cli *DockerCli) CmdPort(args ...string) error {
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
var c struct {
NetworkSettings struct {
Ports nat.PortMap
}
}
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
if cmd.NArg() == 2 {
var (
port = cmd.Arg(1)
proto = "tcp"
parts = strings.SplitN(port, "/", 2)
)
if len(parts) == 2 && len(parts[1]) != 0 {
port = parts[0]
proto = parts[1]
}
natPort := port + "/" + proto
newP, err := nat.NewPort(proto, port)
if err != nil {
return err
}
if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIP, frontend.HostPort)
}
return nil
}
return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0))
}
for from, frontends := range c.NetworkSettings.Ports {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIP, frontend.HostPort)
}
}
return nil
}
-116
View File
@@ -1,116 +0,0 @@
package client
import (
"encoding/json"
"net/url"
"strconv"
"github.com/docker/docker/api/client/ps"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers/filters"
)
// CmdPs outputs a list of Docker containers.
//
// Usage: docker ps [OPTIONS]
func (cli *DockerCli) CmdPs(args ...string) error {
var (
err error
psFilterArgs = filters.Args{}
v = url.Values{}
cmd = Cli.Subcmd("ps", nil, "List containers", true)
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container, include non-running")
since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
format = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
flFilter = opts.NewListOpts(nil)
)
cmd.Require(flag.Exact, 0)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.ParseFlags(args, true)
if *last == -1 && *nLatest {
*last = 1
}
if *all {
v.Set("all", "1")
}
if *last != -1 {
v.Set("limit", strconv.Itoa(*last))
}
if *since != "" {
v.Set("since", *since)
}
if *before != "" {
v.Set("before", *before)
}
if *size {
v.Set("size", "1")
}
// Consolidate all filter flags, and sanity check them.
// They'll get processed in the daemon/server.
for _, f := range flFilter.GetAll() {
if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil {
return err
}
}
if len(psFilterArgs) > 0 {
filterJSON, err := filters.ToParam(psFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJSON)
}
serverResp, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
containers := []types.Container{}
if err := json.NewDecoder(serverResp.body).Decode(&containers); err != nil {
return err
}
f := *format
if len(f) == 0 {
if len(cli.PsFormat()) > 0 {
f = cli.PsFormat()
} else {
f = "table"
}
}
psCtx := ps.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
Size: *size,
Trunc: !*noTrunc,
}
ps.Format(psCtx, containers)
return nil
}
-220
View File
@@ -1,220 +0,0 @@
package ps
import (
"bytes"
"fmt"
"strconv"
"strings"
"text/tabwriter"
"text/template"
"time"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/units"
)
const (
tableKey = "table"
idHeader = "CONTAINER ID"
imageHeader = "IMAGE"
namesHeader = "NAMES"
commandHeader = "COMMAND"
createdAtHeader = "CREATED AT"
runningForHeader = "CREATED"
statusHeader = "STATUS"
portsHeader = "PORTS"
sizeHeader = "SIZE"
labelsHeader = "LABELS"
)
type containerContext struct {
trunc bool
header []string
c types.Container
}
func (c *containerContext) ID() string {
c.addHeader(idHeader)
if c.trunc {
return stringid.TruncateID(c.c.ID)
}
return c.c.ID
}
func (c *containerContext) Names() string {
c.addHeader(namesHeader)
names := stripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
names = []string{name}
break
}
}
}
return strings.Join(names, ",")
}
func (c *containerContext) Image() string {
c.addHeader(imageHeader)
if c.c.Image == "" {
return "<no image>"
}
return c.c.Image
}
func (c *containerContext) Command() string {
c.addHeader(commandHeader)
command := c.c.Command
if c.trunc {
command = stringutils.Truncate(command, 20)
}
return strconv.Quote(command)
}
func (c *containerContext) CreatedAt() string {
c.addHeader(createdAtHeader)
return time.Unix(int64(c.c.Created), 0).String()
}
func (c *containerContext) RunningFor() string {
c.addHeader(runningForHeader)
createdAt := time.Unix(int64(c.c.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
}
func (c *containerContext) Ports() string {
c.addHeader(portsHeader)
return api.DisplayablePorts(c.c.Ports)
}
func (c *containerContext) Status() string {
c.addHeader(statusHeader)
return c.c.Status
}
func (c *containerContext) Size() string {
c.addHeader(sizeHeader)
srw := units.HumanSize(float64(c.c.SizeRw))
sv := units.HumanSize(float64(c.c.SizeRootFs))
sf := srw
if c.c.SizeRootFs > 0 {
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
}
return sf
}
func (c *containerContext) Labels() string {
c.addHeader(labelsHeader)
if c.c.Labels == nil {
return ""
}
var joinLabels []string
for k, v := range c.c.Labels {
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(joinLabels, ",")
}
func (c *containerContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
c.addHeader(h)
if c.c.Labels == nil {
return ""
}
return c.c.Labels[name]
}
func (c *containerContext) fullHeader() string {
if c.header == nil {
return ""
}
return strings.Join(c.header, "\t")
}
func (c *containerContext) addHeader(header string) {
if c.header == nil {
c.header = []string{}
}
c.header = append(c.header, strings.ToUpper(header))
}
func customFormat(ctx Context, containers []types.Container) {
var (
table bool
header string
format = ctx.Format
buffer = bytes.NewBufferString("")
)
if strings.HasPrefix(ctx.Format, tableKey) {
table = true
format = format[len(tableKey):]
}
format = strings.Trim(format, " ")
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
format = r.Replace(format)
if table && ctx.Size {
format += "\t{{.Size}}"
}
tmpl, err := template.New("").Parse(format)
if err != nil {
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
buffer.WriteTo(ctx.Output)
return
}
for _, container := range containers {
containerCtx := &containerContext{
trunc: ctx.Trunc,
c: container,
}
if err := tmpl.Execute(buffer, containerCtx); err != nil {
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
buffer.WriteTo(ctx.Output)
return
}
if table && len(header) == 0 {
header = containerCtx.fullHeader()
}
buffer.WriteString("\n")
}
if table {
if len(header) == 0 {
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
containerCtx := &containerContext{}
tmpl.Execute(bytes.NewBufferString(""), containerCtx)
header = containerCtx.fullHeader()
}
t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0)
t.Write([]byte(header))
t.Write([]byte("\n"))
buffer.WriteTo(t)
t.Flush()
} else {
buffer.WriteTo(ctx.Output)
}
}
func stripNamePrefix(ss []string) []string {
for i, s := range ss {
ss[i] = s[1:]
}
return ss
}
-102
View File
@@ -1,102 +0,0 @@
package ps
import (
"bytes"
"reflect"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
)
func TestContainerPsContext(t *testing.T) {
containerId := stringid.GenerateRandomID()
unix := time.Now().Unix()
var ctx containerContext
cases := []struct {
container types.Container
trunc bool
expValue string
expHeader string
call func() string
}{
{types.Container{ID: containerId}, true, stringid.TruncateID(containerId), idHeader, ctx.ID},
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
{types.Container{Created: int(unix)}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
}
for _, c := range cases {
ctx = containerContext{c: c.container, trunc: c.trunc}
v := c.call()
if strings.Contains(v, ",") {
// comma-separated values means probably a map input, which won't
// be guaranteed to have the same order as our expected value
// We'll create maps and use reflect.DeepEquals to check instead:
entriesMap := make(map[string]string)
expMap := make(map[string]string)
entries := strings.Split(v, ",")
expectedEntries := strings.Split(c.expValue, ",")
for _, entry := range entries {
keyval := strings.Split(entry, "=")
entriesMap[keyval[0]] = keyval[1]
}
for _, expected := range expectedEntries {
keyval := strings.Split(expected, "=")
expMap[keyval[0]] = keyval[1]
}
if !reflect.DeepEqual(expMap, entriesMap) {
t.Fatalf("Expected entries: %v, got: %v", c.expValue, v)
}
} else if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
h := ctx.fullHeader()
if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
}
}
c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
ctx = containerContext{c: c, trunc: true}
sid := ctx.Label("com.docker.swarm.swarm-id")
node := ctx.Label("com.docker.swarm.node_name")
if sid != "33" {
t.Fatalf("Expected 33, was %s\n", sid)
}
if node != "ubuntu" {
t.Fatalf("Expected ubuntu, was %s\n", node)
}
h := ctx.fullHeader()
if h != "SWARM ID\tNODE NAME" {
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
}
}
func TestContainerPsFormatError(t *testing.T) {
out := bytes.NewBufferString("")
ctx := Context{
Format: "{{InvalidFunction}}",
Output: out,
}
customFormat(ctx, make([]types.Container, 0))
if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" {
t.Fatalf("Expected format error, got `%v`\n", out.String())
}
}
-65
View File
@@ -1,65 +0,0 @@
package ps
import (
"io"
"github.com/docker/docker/api/types"
)
const (
tableFormatKey = "table"
rawFormatKey = "raw"
defaultTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
defaultQuietFormat = "{{.ID}}"
)
type Context struct {
Output io.Writer
Format string
Size bool
Quiet bool
Trunc bool
}
func Format(ctx Context, containers []types.Container) {
switch ctx.Format {
case tableFormatKey:
tableFormat(ctx, containers)
case rawFormatKey:
rawFormat(ctx, containers)
default:
customFormat(ctx, containers)
}
}
func rawFormat(ctx Context, containers []types.Container) {
if ctx.Quiet {
ctx.Format = `container_id: {{.ID}}`
} else {
ctx.Format = `container_id: {{.ID}}
image: {{.Image}}
command: {{.Command}}
created_at: {{.CreatedAt}}
status: {{.Status}}
names: {{.Names}}
labels: {{.Labels}}
ports: {{.Ports}}
`
if ctx.Size {
ctx.Format += `size: {{.Size}}
`
}
}
customFormat(ctx, containers)
}
func tableFormat(ctx Context, containers []types.Container) {
ctx.Format = defaultTableFormat
if ctx.Quiet {
ctx.Format = defaultQuietFormat
}
customFormat(ctx, containers)
}
-53
View File
@@ -1,53 +0,0 @@
package client
import (
"fmt"
"net/url"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/graph/tags"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
)
// CmdPull pulls an image or a repository from the registry.
//
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
func (cli *DockerCli) CmdPull(args ...string) error {
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true)
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
addTrustedFlags(cmd, true)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
remote := cmd.Arg(0)
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
if tag == "" && !*allTags {
tag = tags.DEFAULTTAG
fmt.Fprintf(cli.out, "Using default tag: %s\n", tag)
} else if tag != "" && *allTags {
return fmt.Errorf("tag can't be used with --all-tags/-a")
}
ref := registry.ParseReference(tag)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
if err != nil {
return err
}
if isTrusted() && !ref.HasDigest() {
// Check if tag is digest
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
return cli.trustedPull(repoInfo, ref, authConfig)
}
v := url.Values{}
v.Set("fromImage", ref.ImageName(taglessRemote))
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
return err
}
-53
View File
@@ -1,53 +0,0 @@
package client
import (
"fmt"
"net/url"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
)
// CmdPush pushes an image or repository to the registry.
//
// Usage: docker push NAME[:TAG]
func (cli *DockerCli) CmdPush(args ...string) error {
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, "Push an image or a repository to a registry", true)
addTrustedFlags(cmd, false)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
remote, tag := parsers.ParseRepositoryTag(cmd.Arg(0))
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(remote)
if err != nil {
return err
}
// Resolve the Auth config relevant for this server
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
// If we're not using a custom registry, we know the restrictions
// applied to repository names and can warn the user in advance.
// Custom repositories can have different rules, and we must also
// allow pushing by image ID.
if repoInfo.Official {
username := authConfig.Username
if username == "" {
username = "<user>"
}
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
}
if isTrusted() {
return cli.trustedPush(repoInfo, tag, authConfig)
}
v := url.Values{}
v.Set("tag", tag)
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
return err
}
-27
View File
@@ -1,27 +0,0 @@
package client
import (
"fmt"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdRename renames a container.
//
// Usage: docker rename OLD_NAME NEW_NAME
func (cli *DockerCli) CmdRename(args ...string) error {
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, "Rename a container", true)
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
oldName := cmd.Arg(0)
newName := cmd.Arg(1)
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
return fmt.Errorf("Error: failed to rename container named %s", oldName)
}
return nil
}
-39
View File
@@ -1,39 +0,0 @@
package client
import (
"fmt"
"net/url"
"strconv"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdRestart restarts one or more running containers.
//
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdRestart(args ...string) error {
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, "Restart a running container", true)
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
v := url.Values{}
v.Set("t", strconv.Itoa(*nSeconds))
var errNames []string
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to restart containers: %v", errNames)
}
return nil
}
-55
View File
@@ -1,55 +0,0 @@
package client
import (
"fmt"
"net/url"
"strings"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdRm removes one or more containers.
//
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdRm(args ...string) error {
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, "Remove one or more containers", true)
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
val := url.Values{}
if *v {
val.Set("v", "1")
}
if *link {
val.Set("link", "1")
}
if *force {
val.Set("force", "1")
}
var errNames []string
for _, name := range cmd.Args() {
if name == "" {
return fmt.Errorf("Container name cannot be empty")
}
name = strings.Trim(name, "/")
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to remove containers: %v", errNames)
}
return nil
}
-61
View File
@@ -1,61 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdRmi removes all images with the specified name(s).
//
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
func (cli *DockerCli) CmdRmi(args ...string) error {
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, "Remove one or more images", true)
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
v := url.Values{}
if *force {
v.Set("force", "1")
}
if *noprune {
v.Set("noprune", "1")
}
var errNames []string
for _, name := range cmd.Args() {
serverResp, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
defer serverResp.body.Close()
dels := []types.ImageDelete{}
if err := json.NewDecoder(serverResp.body).Decode(&dels); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
continue
}
for _, del := range dels {
if del.Deleted != "" {
fmt.Fprintf(cli.out, "Deleted: %s\n", del.Deleted)
} else {
fmt.Fprintf(cli.out, "Untagged: %s\n", del.Untagged)
}
}
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to remove images: %v", errNames)
}
return nil
}
-257
View File
@@ -1,257 +0,0 @@
package client
import (
"fmt"
"io"
"net/url"
"os"
"runtime"
"github.com/Sirupsen/logrus"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork/resolvconf/dns"
)
func (cid *cidFile) Close() error {
cid.file.Close()
if !cid.written {
if err := os.Remove(cid.path); err != nil {
return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
}
}
return nil
}
func (cid *cidFile) Write(id string) error {
if _, err := cid.file.Write([]byte(id)); err != nil {
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
}
cid.written = true
return nil
}
// CmdRun runs a command in a new container.
//
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
func (cli *DockerCli) CmdRun(args ...string) error {
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true)
addTrustedFlags(cmd, true)
// These are flags not stored in Config/HostConfig
var (
flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
flAttach *opts.ListOpts
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
)
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
// just in case the Parse does not exit
if err != nil {
cmd.ReportError(err.Error(), true)
os.Exit(1)
}
if len(hostConfig.Dns) > 0 {
// check the DNS settings passed via --dns against
// localhost regexp to warn if they are trying to
// set a DNS to a localhost address
for _, dnsIP := range hostConfig.Dns {
if dns.IsLocalhost(dnsIP) {
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
break
}
}
}
if config.Image == "" {
cmd.Usage()
return nil
}
if !*flDetach {
if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
return err
}
} else {
if fl := cmd.Lookup("-attach"); fl != nil {
flAttach = fl.Value.(*opts.ListOpts)
if flAttach.Len() != 0 {
return ErrConflictAttachDetach
}
}
if *flAutoRemove {
return ErrConflictDetachAutoRemove
}
config.AttachStdin = false
config.AttachStdout = false
config.AttachStderr = false
config.StdinOnce = false
}
// Disable flSigProxy when in TTY mode
sigProxy := *flSigProxy
if config.Tty {
sigProxy = false
}
// Telling the Windows daemon the initial size of the tty during start makes
// a far better user experience rather than relying on subsequent resizes
// to cause things to catch up.
if runtime.GOOS == "windows" {
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
}
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
return err
}
if sigProxy {
sigc := cli.forwardAllSignals(createResponse.ID)
defer signal.StopCatch(sigc)
}
var (
waitDisplayID chan struct{}
errCh chan error
)
if !config.AttachStdout && !config.AttachStderr {
// Make this asynchronous to allow the client to write to stdin before having to read the ID
waitDisplayID = make(chan struct{})
go func() {
defer close(waitDisplayID)
fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
}()
}
if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
return ErrConflictRestartPolicyAndAutoRemove
}
// We need to instantiate the chan because the select needs it. It can
// be closed but can't be uninitialized.
hijacked := make(chan io.Closer)
// Block the return until the chan gets closed
defer func() {
logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.")
if _, ok := <-hijacked; ok {
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
}
}()
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
var (
out, stderr io.Writer
in io.ReadCloser
v = url.Values{}
)
v.Set("stream", "1")
if config.AttachStdin {
v.Set("stdin", "1")
in = cli.in
}
if config.AttachStdout {
v.Set("stdout", "1")
out = cli.out
}
if config.AttachStderr {
v.Set("stderr", "1")
if config.Tty {
stderr = cli.out
} else {
stderr = cli.err
}
}
errCh = promise.Go(func() error {
return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
})
} else {
close(hijacked)
}
// Acknowledge the hijack before starting
select {
case closer := <-hijacked:
// Make sure that the hijack gets closed when returning (results
// in closing the hijack chan and freeing server's goroutines)
if closer != nil {
defer closer.Close()
}
case err := <-errCh:
if err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
}
defer func() {
if *flAutoRemove {
if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
}
}
}()
//start the container
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
return err
}
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if errCh != nil {
if err := <-errCh; err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
}
// Detached mode: wait for the id to be displayed and return.
if !config.AttachStdout && !config.AttachStderr {
// Detached mode
<-waitDisplayID
return nil
}
var status int
// Attached mode
if *flAutoRemove {
// Autoremove: wait for the container to finish, retrieve
// the exit code and remove the container
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
return err
}
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
return err
}
} else {
// No Autoremove: Simply retrieve the exit code
if !config.Tty {
// In non-TTY mode, we can't detach, so we must wait for container exit
if status, err = waitForExit(cli, createResponse.ID); err != nil {
return err
}
} else {
// In TTY mode, there is a race: if the process dies too slowly, the state could
// be updated after the getExitCode call and result in the wrong exit code being reported
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
return err
}
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
-58
View File
@@ -1,58 +0,0 @@
package client
import (
"errors"
"io"
"net/url"
"os"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdSave saves one or more images to a tar archive.
//
// The tar archive is written to STDOUT by default, or written to a file.
//
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
func (cli *DockerCli) CmdSave(args ...string) error {
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
output io.Writer = cli.out
err error
)
if *outfile != "" {
output, err = os.Create(*outfile)
if err != nil {
return err
}
} else if cli.isTerminalOut {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
sopts := &streamOpts{
rawTerminal: true,
out: output,
}
if len(cmd.Args()) == 1 {
image := cmd.Arg(0)
if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
return err
}
} else {
v := url.Values{}
for _, arg := range cmd.Args() {
v.Add("names", arg)
}
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
return err
}
}
return nil
}
-87
View File
@@ -1,87 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"sort"
"strings"
"text/tabwriter"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/registry"
)
// ByStars sorts search results in ascending order by number of stars.
type ByStars []registry.SearchResult
func (r ByStars) Len() int { return len(r) }
func (r ByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount }
// CmdSearch searches the Docker Hub for images.
//
// Usage: docker search [OPTIONS] TERM
func (cli *DockerCli) CmdSearch(args ...string) error {
cmd := Cli.Subcmd("search", []string{"TERM"}, "Search the Docker Hub for images", true)
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
stars := cmd.Uint([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
name := cmd.Arg(0)
v := url.Values{}
v.Set("term", name)
// Resolve the Repository name from fqn to hostname + name
taglessRemote, _ := parsers.ParseRepositoryTag(name)
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
if err != nil {
return err
}
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search")
if err != nil {
return err
}
defer rdr.Close()
results := ByStars{}
if err := json.NewDecoder(rdr).Decode(&results); err != nil {
return err
}
sort.Sort(sort.Reverse(results))
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
for _, res := range results {
if ((*automated || *trusted) && (!res.IsTrusted && !res.IsAutomated)) || (int(*stars) > res.StarCount) {
continue
}
desc := strings.Replace(res.Description, "\n", " ", -1)
desc = strings.Replace(desc, "\r", " ", -1)
if !*noTrunc && len(desc) > 45 {
desc = stringutils.Truncate(desc, 42) + "..."
}
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
if res.IsOfficial {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\t")
if res.IsAutomated || res.IsTrusted {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\n")
}
w.Flush()
return nil
}
-15
View File
@@ -1,15 +0,0 @@
// +build experimental
package client
import (
"os"
nwclient "github.com/docker/libnetwork/client"
)
func (cli *DockerCli) CmdService(args ...string) error {
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
args = append([]string{"service"}, args...)
return nCli.Cmd(os.Args[0], args...)
}
-170
View File
@@ -1,170 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
)
func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.CatchAll(sigc)
go func() {
for s := range sigc {
if s == signal.SIGCHLD {
continue
}
var sig string
for sigStr, sigN := range signal.SignalMap {
if sigN == s {
sig = sigStr
break
}
}
if sig == "" {
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
}
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
logrus.Debugf("Error sending signal: %s", err)
}
}
}()
return sigc
}
// CmdStart starts one or more stopped containers.
//
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdStart(args ...string) error {
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, "Start one or more stopped containers", true)
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
cErr chan error
tty bool
)
if *attach || *openStdin {
if cmd.NArg() > 1 {
return fmt.Errorf("You cannot start and attach multiple containers at once.")
}
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
return err
}
tty = c.Config.Tty
if !tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
defer signal.StopCatch(sigc)
}
var in io.ReadCloser
v := url.Values{}
v.Set("stream", "1")
if *openStdin && c.Config.OpenStdin {
v.Set("stdin", "1")
in = cli.in
}
v.Set("stdout", "1")
v.Set("stderr", "1")
hijacked := make(chan io.Closer)
// Block the return until the chan gets closed
defer func() {
logrus.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
if _, ok := <-hijacked; ok {
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
}
cli.in.Close()
}()
cErr = promise.Go(func() error {
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
})
// Acknowledge the hijack before starting
select {
case closer := <-hijacked:
// Make sure that the hijack gets closed when returning (results
// in closing the hijack chan and freeing server's goroutines)
if closer != nil {
defer closer.Close()
}
case err := <-cErr:
if err != nil {
return err
}
}
}
var encounteredError error
var errNames []string
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil))
if err != nil {
if !*attach && !*openStdin {
// attach and openStdin is false means it could be starting multiple containers
// when a container start failed, show the error message and start next
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
encounteredError = err
}
} else {
if !*attach && !*openStdin {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
}
if len(errNames) > 0 {
encounteredError = fmt.Errorf("Error: failed to start containers: %v", errNames)
}
if encounteredError != nil {
return encounteredError
}
if *openStdin || *attach {
if tty && cli.isTerminalOut {
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if attchErr := <-cErr; attchErr != nil {
return attchErr
}
_, status, err := getExitCode(cli, cmd.Arg(0))
if err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
}
return nil
}
-202
View File
@@ -1,202 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"io"
"net/url"
"sort"
"strings"
"sync"
"text/tabwriter"
"time"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/units"
)
type containerStats struct {
Name string
CPUPercentage float64
Memory float64
MemoryLimit float64
MemoryPercentage float64
NetworkRx float64
NetworkTx float64
mu sync.RWMutex
err error
}
func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
v := url.Values{}
if streamStats {
v.Set("stream", "1")
} else {
v.Set("stream", "0")
}
serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
if err != nil {
s.mu.Lock()
s.err = err
s.mu.Unlock()
return
}
defer serverResp.body.Close()
var (
previousCPU uint64
previousSystem uint64
dec = json.NewDecoder(serverResp.body)
u = make(chan error, 1)
)
go func() {
for {
var v *types.Stats
if err := dec.Decode(&v); err != nil {
u <- err
return
}
var (
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
cpuPercent = 0.0
)
previousCPU = v.PreCpuStats.CpuUsage.TotalUsage
previousSystem = v.PreCpuStats.SystemUsage
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
s.mu.Lock()
s.CPUPercentage = cpuPercent
s.Memory = float64(v.MemoryStats.Usage)
s.MemoryLimit = float64(v.MemoryStats.Limit)
s.MemoryPercentage = memPercent
s.NetworkRx = float64(v.Network.RxBytes)
s.NetworkTx = float64(v.Network.TxBytes)
s.mu.Unlock()
u <- nil
if !streamStats {
return
}
}
}()
for {
select {
case <-time.After(2 * time.Second):
// zero out the values if we have not received an update within
// the specified duration.
s.mu.Lock()
s.CPUPercentage = 0
s.Memory = 0
s.MemoryPercentage = 0
s.mu.Unlock()
case err := <-u:
if err != nil {
s.mu.Lock()
s.err = err
s.mu.Unlock()
return
}
}
if !streamStats {
return
}
}
}
func (s *containerStats) Display(w io.Writer) error {
s.mu.RLock()
defer s.mu.RUnlock()
if s.err != nil {
return s.err
}
fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n",
s.Name,
s.CPUPercentage,
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
s.MemoryPercentage,
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx))
return nil
}
// CmdStats displays a live stream of resource usage statistics for one or more containers.
//
// This shows real-time information on CPU usage, memory usage, and network I/O.
//
// Usage: docker stats CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdStats(args ...string) error {
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", true)
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
names := cmd.Args()
sort.Strings(names)
var (
cStats []*containerStats
w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
)
printHeader := func() {
if !*noStream {
fmt.Fprint(cli.out, "\033[2J")
fmt.Fprint(cli.out, "\033[H")
}
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n")
}
for _, n := range names {
s := &containerStats{Name: n}
cStats = append(cStats, s)
go s.Collect(cli, !*noStream)
}
// do a quick pause so that any failed connections for containers that do not exist are able to be
// evicted before we display the initial or default values.
time.Sleep(1500 * time.Millisecond)
var errs []string
for _, c := range cStats {
c.mu.Lock()
if c.err != nil {
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
}
c.mu.Unlock()
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, ", "))
}
for range time.Tick(500 * time.Millisecond) {
printHeader()
toRemove := []int{}
for i, s := range cStats {
if err := s.Display(w); err != nil && !*noStream {
toRemove = append(toRemove, i)
}
}
for j := len(toRemove) - 1; j >= 0; j-- {
i := toRemove[j]
cStats = append(cStats[:i], cStats[i+1:]...)
}
if len(cStats) == 0 {
return nil
}
w.Flush()
if *noStream {
break
}
}
return nil
}
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
var (
cpuPercent = 0.0
// calculate the change for the cpu usage of the container in between readings
cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU)
// calculate the change for the entire system between readings
systemDelta = float64(v.CpuStats.SystemUsage - previousSystem)
)
if systemDelta > 0.0 && cpuDelta > 0.0 {
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0
}
return cpuPercent
}
-29
View File
@@ -1,29 +0,0 @@
package client
import (
"bytes"
"sync"
"testing"
)
func TestDisplay(t *testing.T) {
c := &containerStats{
Name: "app",
CPUPercentage: 30.0,
Memory: 100 * 1024 * 1024.0,
MemoryLimit: 2048 * 1024 * 1024.0,
MemoryPercentage: 100.0 / 2048.0 * 100.0,
NetworkRx: 100 * 1024 * 1024,
NetworkTx: 800 * 1024 * 1024,
mu: sync.RWMutex{},
}
var b bytes.Buffer
if err := c.Display(&b); err != nil {
t.Fatalf("c.Display() gave error: %s", err)
}
got := b.String()
want := "app\t30.00%\t104.9 MB/2.147 GB\t4.88%\t104.9 MB/838.9 MB\n"
if got != want {
t.Fatalf("c.Display() = %q, want %q", got, want)
}
}
-41
View File
@@ -1,41 +0,0 @@
package client
import (
"fmt"
"net/url"
"strconv"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdStop stops one or more running containers.
//
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
//
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdStop(args ...string) error {
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
v := url.Values{}
v.Set("t", strconv.Itoa(*nSeconds))
var errNames []string
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to stop containers: %v", errNames)
}
return nil
}
-42
View File
@@ -1,42 +0,0 @@
package client
import (
"net/url"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
)
// CmdTag tags an image into a repository.
//
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
func (cli *DockerCli) CmdTag(args ...string) error {
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, "Tag an image into a repository", true)
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
var (
repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1))
v = url.Values{}
)
//Check if the given image name can be resolved
if err := registry.ValidateRepositoryName(repository); err != nil {
return err
}
v.Set("repo", repository)
v.Set("tag", tag)
if *force {
v.Set("force", "1")
}
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, nil)); err != nil {
return err
}
return nil
}
-49
View File
@@ -1,49 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"text/tabwriter"
"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdTop displays the running processes of a container.
//
// Usage: docker top CONTAINER
func (cli *DockerCli) CmdTop(args ...string) error {
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, "Display the running processes of a container", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
val := url.Values{}
if cmd.NArg() > 1 {
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
}
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
procList := types.ContainerProcessList{}
if err := json.NewDecoder(serverResp.body).Decode(&procList); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
for _, proc := range procList.Processes {
fmt.Fprintln(w, strings.Join(proc, "\t"))
}
w.Flush()
return nil
}
-454
View File
@@ -1,454 +0,0 @@
package client
import (
"bufio"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/ansiescape"
"github.com/docker/docker/pkg/ioutils"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/tlsconfig"
"github.com/docker/docker/registry"
"github.com/docker/notary/client"
"github.com/docker/notary/pkg/passphrase"
"github.com/docker/notary/trustmanager"
"github.com/endophage/gotuf/data"
)
var untrusted bool
func addTrustedFlags(fs *flag.FlagSet, verify bool) {
var trusted bool
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
if t, err := strconv.ParseBool(e); t || err != nil {
// treat any other value as true
trusted = true
}
}
message := "Skip image signing"
if verify {
message = "Skip image verification"
}
fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
}
func isTrusted() bool {
return !untrusted
}
var targetRegexp = regexp.MustCompile(`([\S]+): digest: ([\S]+) size: ([\d]+)`)
type target struct {
reference registry.Reference
digest digest.Digest
size int64
}
func (cli *DockerCli) trustDirectory() string {
return filepath.Join(cliconfig.ConfigDir(), "trust")
}
// certificateDirectory returns the directory containing
// TLS certificates for the given server. An error is
// returned if there was an error parsing the server string.
func (cli *DockerCli) certificateDirectory(server string) (string, error) {
u, err := url.Parse(server)
if err != nil {
return "", err
}
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
}
func trustServer(index *registry.IndexInfo) string {
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
if !strings.HasPrefix(s, "https://") {
return "https://" + s
}
return s
}
if index.Official {
return registry.NotaryServer
}
return "https://" + index.Name
}
type simpleCredentialStore struct {
auth cliconfig.AuthConfig
}
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
return scs.auth.Username, scs.auth.Password
}
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {
server := trustServer(repoInfo.Index)
if !strings.HasPrefix(server, "https://") {
return nil, errors.New("unsupported scheme: https required for trust server")
}
var cfg = tlsconfig.ClientDefault
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
// Get certificate base directory
certDir, err := cli.certificateDirectory(server)
if err != nil {
return nil, err
}
logrus.Debugf("reading certificate directory: %s", certDir)
if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil {
return nil, err
}
base := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &cfg,
DisableKeepAlives: true,
}
// Skip configuration headers since request is not going to Docker daemon
modifiers := registry.DockerHeaders(http.Header{})
authTransport := transport.NewTransport(base, modifiers...)
pingClient := &http.Client{
Transport: authTransport,
Timeout: 5 * time.Second,
}
endpointStr := server + "/v2/"
req, err := http.NewRequest("GET", endpointStr, nil)
if err != nil {
return nil, err
}
resp, err := pingClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
challengeManager := auth.NewSimpleChallengeManager()
if err := challengeManager.AddResponse(resp); err != nil {
return nil, err
}
creds := simpleCredentialStore{auth: authConfig}
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName, "push", "pull")
basicHandler := auth.NewBasicHandler(creds)
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
tr := transport.NewTransport(base, modifiers...)
return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName, server, tr, cli.getPassphraseRetriever())
}
func convertTarget(t client.Target) (target, error) {
h, ok := t.Hashes["sha256"]
if !ok {
return target{}, errors.New("no valid hash, expecting sha256")
}
return target{
reference: registry.ParseReference(t.Name),
digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)),
size: t.Length,
}, nil
}
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
aliasMap := map[string]string{
"root": "offline",
"snapshot": "tagging",
"targets": "tagging",
}
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
env := map[string]string{
"root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"),
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
"targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
}
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
if v := env[alias]; v != "" {
return v, numAttempts > 1, nil
}
return baseRetriever(keyName, alias, createNew, numAttempts)
}
}
func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (registry.Reference, error) {
repoInfo, err := registry.ParseRepositoryInfo(repo)
if err != nil {
return nil, err
}
// Resolve the Auth config relevant for this server
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
return nil, err
}
t, err := notaryRepo.GetTargetByName(ref.String())
if err != nil {
return nil, err
}
r, err := convertTarget(*t)
if err != nil {
return nil, err
}
return registry.DigestReference(r.digest), nil
}
func (cli *DockerCli) tagTrusted(repoInfo *registry.RepositoryInfo, trustedRef, ref registry.Reference) error {
fullName := trustedRef.ImageName(repoInfo.LocalName)
fmt.Fprintf(cli.out, "Tagging %s as %s\n", fullName, ref.ImageName(repoInfo.LocalName))
tv := url.Values{}
tv.Set("repo", repoInfo.LocalName)
tv.Set("tag", ref.String())
tv.Set("force", "1")
if _, _, err := readBody(cli.call("POST", "/images/"+fullName+"/tag?"+tv.Encode(), nil, nil)); err != nil {
return err
}
return nil
}
func notaryError(err error) error {
switch err.(type) {
case *json.SyntaxError:
logrus.Debugf("Notary syntax error: %s", err)
return errors.New("no trust data available for remote repository")
case client.ErrExpired:
return fmt.Errorf("remote repository out-of-date: %v", err)
case trustmanager.ErrKeyNotFound:
return fmt.Errorf("signing keys not found: %v", err)
}
return err
}
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error {
var (
v = url.Values{}
refs = []target{}
)
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
return err
}
if ref.String() == "" {
// List all targets
targets, err := notaryRepo.ListTargets()
if err != nil {
return notaryError(err)
}
for _, tgt := range targets {
t, err := convertTarget(*tgt)
if err != nil {
fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.LocalName)
continue
}
refs = append(refs, t)
}
} else {
t, err := notaryRepo.GetTargetByName(ref.String())
if err != nil {
return notaryError(err)
}
r, err := convertTarget(*t)
if err != nil {
return err
}
refs = append(refs, r)
}
v.Set("fromImage", repoInfo.LocalName)
for i, r := range refs {
displayTag := r.reference.String()
if displayTag != "" {
displayTag = ":" + displayTag
}
fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest)
v.Set("tag", r.digest.String())
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
if err != nil {
return err
}
// If reference is not trusted, tag by trusted reference
if !r.reference.HasDigest() {
if err := cli.tagTrusted(repoInfo, registry.DigestReference(r.digest), r.reference); err != nil {
return err
}
}
}
return nil
}
func selectKey(keys map[string]string) string {
if len(keys) == 0 {
return ""
}
keyIDs := []string{}
for k := range keys {
keyIDs = append(keyIDs, k)
}
// TODO(dmcgowan): let user choose if multiple keys, now pick consistently
sort.Strings(keyIDs)
return keyIDs[0]
}
func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
r, w := io.Pipe()
out := io.MultiWriter(in, w)
targetChan := make(chan []target)
go func() {
targets := []target{}
scanner := bufio.NewScanner(r)
scanner.Split(ansiescape.ScanANSILines)
for scanner.Scan() {
line := scanner.Bytes()
if matches := targetRegexp.FindSubmatch(line); len(matches) == 4 {
dgst, err := digest.ParseDigest(string(matches[2]))
if err != nil {
// Line does match what is expected, continue looking for valid lines
logrus.Debugf("Bad digest value %q in matched line, ignoring\n", string(matches[2]))
continue
}
s, err := strconv.ParseInt(string(matches[3]), 10, 64)
if err != nil {
// Line does match what is expected, continue looking for valid lines
logrus.Debugf("Bad size value %q in matched line, ignoring\n", string(matches[3]))
continue
}
targets = append(targets, target{
reference: registry.ParseReference(string(matches[1])),
digest: dgst,
size: s,
})
}
}
targetChan <- targets
}()
return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan
}
func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error {
streamOut, targetChan := targetStream(cli.out)
v := url.Values{}
v.Set("tag", tag)
_, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
// Close stream channel to finish target parsing
if err := streamOut.Close(); err != nil {
return err
}
// Check error from request
if err != nil {
return err
}
// Get target results
targets := <-targetChan
if tag == "" {
fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n")
return nil
}
if len(targets) == 0 {
fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n")
return nil
}
fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n")
repo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
return err
}
for _, target := range targets {
h, err := hex.DecodeString(target.digest.Hex())
if err != nil {
return err
}
t := &client.Target{
Name: target.reference.String(),
Hashes: data.Hashes{
string(target.digest.Algorithm()): h,
},
Length: int64(target.size),
}
if err := repo.AddTarget(t); err != nil {
return err
}
}
err = repo.Publish()
if _, ok := err.(*client.ErrRepoNotInitialized); !ok {
return notaryError(err)
}
ks := repo.KeyStoreManager
keys := ks.RootKeyStore().ListKeys()
rootKey := selectKey(keys)
if rootKey == "" {
rootKey, err = ks.GenRootKey("ecdsa")
if err != nil {
return err
}
}
cryptoService, err := ks.GetRootCryptoService(rootKey)
if err != nil {
return err
}
if err := repo.Initialize(cryptoService); err != nil {
return notaryError(err)
}
fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.CanonicalName)
return notaryError(repo.Publish())
}
-32
View File
@@ -1,32 +0,0 @@
package client
import (
"fmt"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdUnpause unpauses all processes within a container, for one or more containers.
//
// Usage: docker unpause CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdUnpause(args ...string) error {
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, "Unpause all processes within a container", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var errNames []string
for _, name := range cmd.Args() {
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, nil)); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to unpause containers: %v", errNames)
}
return nil
}
+142 -225
View File
@@ -12,34 +12,24 @@ import (
"net/url"
"os"
gosignal "os/signal"
"runtime"
"strconv"
"strings"
"time"
"github.com/Sirupsen/logrus"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
var (
errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
)
type serverResponse struct {
body io.ReadCloser
header http.Header
statusCode int
}
// HTTPClient creates a new HTTP client with the cli's client transport instance.
func (cli *DockerCli) HTTPClient() *http.Client {
return &http.Client{Transport: cli.transport}
}
@@ -47,188 +37,143 @@ func (cli *DockerCli) HTTPClient() *http.Client {
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
params := bytes.NewBuffer(nil)
if data != nil {
if err := json.NewEncoder(params).Encode(data); err != nil {
return nil, err
if env, ok := data.(engine.Env); ok {
if err := env.Encode(params); err != nil {
return nil, err
}
} else {
buf, err := json.Marshal(data)
if err != nil {
return nil, err
}
if _, err := params.Write(buf); err != nil {
return nil, err
}
}
}
return params, nil
}
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
serverResp := &serverResponse{
body: nil,
statusCode: -1,
}
expectedPayload := (method == "POST" || method == "PUT")
if expectedPayload && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
params, err := cli.encodeData(data)
if err != nil {
return serverResp, err
return nil, -1, err
}
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k, v := range cli.configFile.HTTPHeaders {
req.Header.Set(k, v)
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
if err != nil {
return nil, -1, err
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
if passAuthInfo {
cli.LoadConfigFile()
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.Configs[registry.IndexServerAddress()]
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return nil, err
}
registryAuthHeader := []string{
base64.URLEncoding.EncodeToString(buf),
}
return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
}
if headers, err := getHeaders(authConfig); err == nil && headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
req.URL.Host = cli.addr
req.URL.Scheme = cli.scheme
if data != nil {
req.Header.Set("Content-Type", "application/json")
} else if method == "POST" {
req.Header.Set("Content-Type", "text/plain")
}
resp, err := cli.HTTPClient().Do(req)
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return nil, -1, ErrConnectionRefused
}
if cli.tlsConfig == nil {
return nil, -1, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
}
return nil, -1, fmt.Errorf("An error occurred trying to connect: %v", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, -1, err
}
if len(body) == 0 {
return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
}
return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
}
return resp.Body, resp.StatusCode, nil
}
func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
return cli.streamHelper(method, path, true, in, out, nil, headers)
}
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
req.URL.Host = cli.addr
req.URL.Scheme = cli.scheme
if method == "POST" {
req.Header.Set("Content-Type", "text/plain")
}
if headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
if expectedPayload && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "text/plain")
}
resp, err := cli.HTTPClient().Do(req)
if resp != nil {
serverResp.statusCode = resp.StatusCode
}
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return serverResp, errConnectionRefused
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
}
if cli.tlsConfig == nil {
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err)
}
if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
}
return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
return err
}
defer resp.Body.Close()
if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return serverResp, err
return err
}
if len(body) == 0 {
return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
}
return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
}
serverResp.body = resp.Body
serverResp.header = resp.Header
return serverResp, nil
}
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return nil, -1, err
}
registryAuthHeader := []string{
base64.URLEncoding.EncodeToString(buf),
}
// begin the request
serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
"X-Registry-Auth": registryAuthHeader,
})
if err == nil && out != nil {
// If we are streaming output, complete the stream since
// errors may not appear until later.
err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
}
if err != nil {
// Since errors in a stream appear after status 200 has been written,
// we may need to change the status code.
if strings.Contains(err.Error(), "Authentication is required") ||
strings.Contains(err.Error(), "Status 401") ||
strings.Contains(err.Error(), "401 Unauthorized") ||
strings.Contains(err.Error(), "status code 401") {
serverResp.statusCode = http.StatusUnauthorized
}
}
return serverResp.body, serverResp.statusCode, err
}
// Resolve the Auth config relevant for this server
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
body, statusCode, err := cmdAttempt(authConfig)
if statusCode == http.StatusUnauthorized {
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
return nil, -1, err
}
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
return cmdAttempt(authConfig)
}
return body, statusCode, err
}
func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
sr, err := cli.call(method, path, data, headers)
return sr.body, sr.header, sr.statusCode, err
}
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) {
params, err := cli.encodeData(data)
if err != nil {
sr := &serverResponse{
body: nil,
header: nil,
statusCode: -1,
}
return sr, nil
}
if data != nil {
if headers == nil {
headers = make(map[string][]string)
}
headers["Content-Type"] = []string{"application/json"}
}
serverResp, err := cli.clientRequest(method, path, params, headers)
return serverResp, err
}
type streamOpts struct {
rawTerminal bool
in io.Reader
out io.Writer
err io.Writer
headers map[string][]string
}
func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
if err != nil {
return serverResp, err
}
return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
}
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
defer body.Close()
if api.MatchesContentType(contentType, "application/json") {
return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
}
if stdout != nil || stderr != nil {
// When TTY is ON, use regular copy
var err error
if rawTerminal {
_, err = io.Copy(stdout, body)
if setRawTerminal {
_, err = io.Copy(stdout, resp.Body)
} else {
_, err = stdcopy.StdCopy(stdout, stderr, body)
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
}
logrus.Debugf("[stream] End of stdout")
log.Debugf("[stream] End of stdout")
return err
}
return nil
@@ -250,103 +195,75 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
path = "/exec/" + id + "/resize?"
}
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
logrus.Debugf("Error resize: %s", err)
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
log.Debugf("Error resize: %s", err)
}
}
func waitForExit(cli *DockerCli, containerID string) (int, error) {
serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
func waitForExit(cli *DockerCli, containerId string) (int, error) {
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
if err != nil {
return -1, err
}
defer serverResp.body.Close()
var res types.ContainerWaitResponse
if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil {
var out engine.Env
if err := out.Decode(stream); err != nil {
return -1, err
}
return res.StatusCode, nil
return out.GetInt("StatusCode"), nil
}
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != errConnectionRefused {
if err != ErrConnectionRefused {
return false, -1, err
}
return false, -1, nil
}
defer serverResp.body.Close()
var c types.ContainerJSON
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
var result engine.Env
if err := result.Decode(stream); err != nil {
return false, -1, err
}
return c.State.Running, c.State.ExitCode, nil
state := result.GetSubEnv("State")
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
}
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != errConnectionRefused {
if err != ErrConnectionRefused {
return false, -1, err
}
return false, -1, nil
}
defer serverResp.body.Close()
//TODO: Should we reconsider having a type in api/types?
//this is a response to exex/id/json not container
var c struct {
Running bool
ExitCode int
}
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
var result engine.Env
if err := result.Decode(stream); err != nil {
return false, -1, err
}
return c.Running, c.ExitCode, nil
return result.GetBool("Running"), result.GetInt("ExitCode"), nil
}
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
cli.resizeTty(id, isExec)
if runtime.GOOS == "windows" {
go func() {
prevH, prevW := cli.getTtySize()
for {
time.Sleep(time.Millisecond * 250)
h, w := cli.getTtySize()
if prevW != w || prevH != h {
cli.resizeTty(id, isExec)
}
prevH = h
prevW = w
}
}()
} else {
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
go func() {
for range sigchan {
cli.resizeTty(id, isExec)
}
}()
}
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
go func() {
for _ = range sigchan {
cli.resizeTty(id, isExec)
}
}()
return nil
}
@@ -356,7 +273,7 @@ func (cli *DockerCli) getTtySize() (int, int) {
}
ws, err := term.GetWinsize(cli.outFd)
if err != nil {
logrus.Debugf("Error getting size: %s", err)
log.Debugf("Error getting size: %s", err)
if ws == nil {
return 0, 0
}
@@ -364,16 +281,16 @@ func (cli *DockerCli) getTtySize() (int, int) {
return int(ws.Height), int(ws.Width)
}
func readBody(serverResp *serverResponse, err error) ([]byte, int, error) {
if serverResp.body != nil {
defer serverResp.body.Close()
func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
if stream != nil {
defer stream.Close()
}
if err != nil {
return nil, serverResp.statusCode, err
return nil, statusCode, err
}
body, err := ioutil.ReadAll(serverResp.body)
body, err := ioutil.ReadAll(stream)
if err != nil {
return nil, -1, err
}
return body, serverResp.statusCode, nil
return body, statusCode, nil
}
-96
View File
@@ -1,96 +0,0 @@
package client
import (
"encoding/json"
"runtime"
"text/template"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/autogen/dockerversion"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/utils"
)
var VersionTemplate = `Client:
Version: {{.Client.Version}}
API version: {{.Client.ApiVersion}}
Go version: {{.Client.GoVersion}}
Git commit: {{.Client.GitCommit}}
Built: {{.Client.BuildTime}}
OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}}
Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}}
Server:
Version: {{.Server.Version}}
API version: {{.Server.ApiVersion}}
Go version: {{.Server.GoVersion}}
Git commit: {{.Server.GitCommit}}
Built: {{.Server.BuildTime}}
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
type VersionData struct {
Client types.Version
ServerOK bool
Server types.Version
}
// CmdVersion shows Docker version information.
//
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
//
// Usage: docker version
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
cmd := Cli.Subcmd("version", nil, "Show the Docker version information.", true)
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
if *tmplStr == "" {
*tmplStr = VersionTemplate
}
var tmpl *template.Template
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
return Cli.StatusError{StatusCode: 64,
Status: "Template parsing error: " + err.Error()}
}
vd := VersionData{
Client: types.Version{
Version: dockerversion.VERSION,
ApiVersion: api.Version,
GoVersion: runtime.Version(),
GitCommit: dockerversion.GITCOMMIT,
BuildTime: dockerversion.BUILDTIME,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: utils.ExperimentalBuild(),
},
}
defer func() {
if err2 := tmpl.Execute(cli.out, vd); err2 != nil && err == nil {
err = err2
}
cli.out.Write([]byte{'\n'})
}()
serverResp, err := cli.call("GET", "/version", nil, nil)
if err != nil {
return err
}
defer serverResp.body.Close()
if err = json.NewDecoder(serverResp.body).Decode(&vd.Server); err != nil {
return Cli.StatusError{StatusCode: 1,
Status: "Error reading remote version: " + err.Error()}
}
vd.ServerOK = true
return
}
-35
View File
@@ -1,35 +0,0 @@
package client
import (
"fmt"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
)
// CmdWait blocks until a container stops, then prints its exit code.
//
// If more than one container is specified, this will wait synchronously on each container.
//
// Usage: docker wait CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdWait(args ...string) error {
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, "Block until a container stops, then print its exit code.", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var errNames []string
for _, name := range cmd.Args() {
status, err := waitForExit(cli, name)
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
errNames = append(errNames, name)
} else {
fmt.Fprintf(cli.out, "%d\n", status)
}
}
if len(errNames) > 0 {
return fmt.Errorf("Error: failed to wait containers: %v", errNames)
}
return nil
}
+29 -87
View File
@@ -3,109 +3,51 @@ package api
import (
"fmt"
"mime"
"path/filepath"
"sort"
"os"
"path"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/system"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/version"
"github.com/docker/libtrust"
)
// Common constants for daemon and client.
const (
// Current REST API version
Version version.Version = "1.20"
// Minimun REST API version supported
MinVersion version.Version = "1.12"
// Default filename with Docker commands, read by docker build
DefaultDockerfileName string = "Dockerfile"
APIVERSION version.Version = "1.17"
DEFAULTHTTPHOST = "127.0.0.1"
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
DefaultDockerfileName string = "Dockerfile"
)
type ByPrivatePort []types.Port
func (r ByPrivatePort) Len() int { return len(r) }
func (r ByPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort }
func DisplayablePorts(ports []types.Port) string {
var (
result = []string{}
hostMappings = []string{}
firstInGroupMap map[string]int
lastInGroupMap map[string]int
)
firstInGroupMap = make(map[string]int)
lastInGroupMap = make(map[string]int)
sort.Sort(ByPrivatePort(ports))
for _, port := range ports {
var (
current = port.PrivatePort
portKey = port.Type
firstInGroup int
lastInGroup int
)
if port.IP != "" {
if port.PublicPort != current {
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
continue
}
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
}
firstInGroup = firstInGroupMap[portKey]
lastInGroup = lastInGroupMap[portKey]
if firstInGroup == 0 {
firstInGroupMap[portKey] = current
lastInGroupMap[portKey] = current
continue
}
if current == (lastInGroup + 1) {
lastInGroupMap[portKey] = current
continue
}
result = append(result, FormGroup(portKey, firstInGroup, lastInGroup))
firstInGroupMap[portKey] = current
lastInGroupMap[portKey] = current
func ValidateHost(val string) (string, error) {
host, err := parsers.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
if err != nil {
return val, err
}
for portKey, firstInGroup := range firstInGroupMap {
result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
}
result = append(result, hostMappings...)
return strings.Join(result, ", ")
return host, nil
}
func FormGroup(key string, start, last int) string {
var (
group string
parts = strings.Split(key, "/")
groupType = parts[0]
ip = ""
)
if len(parts) > 1 {
ip = parts[0]
groupType = parts[1]
//TODO remove, used on < 1.5 in getContainersJSON
func DisplayablePorts(ports *engine.Table) string {
result := []string{}
ports.SetKey("PublicPort")
ports.Sort()
for _, port := range ports.Data {
if port.Get("IP") == "" {
result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PrivatePort"), port.Get("Type")))
} else {
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
}
}
if start == last {
group = fmt.Sprintf("%d", start)
} else {
group = fmt.Sprintf("%d-%d", start, last)
}
if ip != "" {
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
}
return fmt.Sprintf("%s/%s", group, groupType)
return strings.Join(result, ", ")
}
func MatchesContentType(contentType, expectedType string) bool {
mimetype, _, err := mime.ParseMediaType(contentType)
if err != nil {
logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
log.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
}
return err == nil && mimetype == expectedType
}
@@ -113,7 +55,7 @@ func MatchesContentType(contentType, expectedType string) bool {
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
// otherwise generates a new one
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700)
err := os.MkdirAll(path.Dir(trustKeyPath), 0700)
if err != nil {
return nil, err
}
@@ -127,7 +69,7 @@ func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
return nil, fmt.Errorf("Error saving key file: %s", err)
}
} else if err != nil {
return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
return nil, fmt.Errorf("Error loading key file: %s", err)
}
return trustKey, nil
}
+2
View File
@@ -0,0 +1,2 @@
Victor Vieux <vieux@docker.com> (@vieux)
# Johan Euphrosine <proppy@google.com> (@proppy)
-56
View File
@@ -1,56 +0,0 @@
package server
import (
"fmt"
"net/http"
"strconv"
"strings"
)
func boolValue(r *http.Request, k string) bool {
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
}
// boolValueOrDefault returns the default bool passed if the query param is
// missing, otherwise it's just a proxy to boolValue above
func boolValueOrDefault(r *http.Request, k string, d bool) bool {
if _, ok := r.Form[k]; !ok {
return d
}
return boolValue(r, k)
}
func int64ValueOrZero(r *http.Request, k string) int64 {
val, err := strconv.ParseInt(r.FormValue(k), 10, 64)
if err != nil {
return 0
}
return val
}
type archiveOptions struct {
name string
path string
}
func archiveFormValues(r *http.Request, vars map[string]string) (archiveOptions, error) {
if vars == nil {
return archiveOptions{}, fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return archiveOptions{}, err
}
name := vars["name"]
path := r.Form.Get("path")
switch {
case name == "":
return archiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
case path == "":
return archiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
}
return archiveOptions{name, path}, nil
}
-70
View File
@@ -1,70 +0,0 @@
package server
import (
"net/http"
"net/url"
"testing"
)
func TestBoolValue(t *testing.T) {
cases := map[string]bool{
"": false,
"0": false,
"no": false,
"false": false,
"none": false,
"1": true,
"yes": true,
"true": true,
"one": true,
"100": true,
}
for c, e := range cases {
v := url.Values{}
v.Set("test", c)
r, _ := http.NewRequest("POST", "", nil)
r.Form = v
a := boolValue(r, "test")
if a != e {
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
}
}
}
func TestBoolValueOrDefault(t *testing.T) {
r, _ := http.NewRequest("GET", "", nil)
if !boolValueOrDefault(r, "queryparam", true) {
t.Fatal("Expected to get true default value, got false")
}
v := url.Values{}
v.Set("param", "")
r, _ = http.NewRequest("GET", "", nil)
r.Form = v
if boolValueOrDefault(r, "param", true) {
t.Fatal("Expected not to get true")
}
}
func TestInt64ValueOrZero(t *testing.T) {
cases := map[string]int64{
"": 0,
"asdf": 0,
"0": 0,
"1": 1,
}
for c, e := range cases {
v := url.Values{}
v.Set("test", c)
r, _ := http.NewRequest("POST", "", nil)
r.Form = v
a := int64ValueOrZero(r, "test")
if a != e {
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
}
}
}
-38
View File
@@ -1,38 +0,0 @@
package server
import (
"expvar"
"fmt"
"net/http"
"net/http/pprof"
"github.com/gorilla/mux"
)
func ProfilerSetup(mainRouter *mux.Router, path string) {
var r = mainRouter.PathPrefix(path).Subrouter()
r.HandleFunc("/vars", expVars)
r.HandleFunc("/pprof/", pprof.Index)
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/pprof/profile", pprof.Profile)
r.HandleFunc("/pprof/symbol", pprof.Symbol)
r.HandleFunc("/pprof/block", pprof.Handler("block").ServeHTTP)
r.HandleFunc("/pprof/heap", pprof.Handler("heap").ServeHTTP)
r.HandleFunc("/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
r.HandleFunc("/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
}
// Replicated from expvar.go as not public.
func expVars(w http.ResponseWriter, r *http.Request) {
first := true
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n")
expvar.Do(func(kv expvar.KeyValue) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}
+1117 -1164
View File
File diff suppressed because it is too large Load Diff
-17
View File
@@ -1,17 +0,0 @@
// +build experimental
package server
func (s *Server) registerSubRouter() {
httpHandler := s.daemon.NetworkApiRouter()
subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter()
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
subrouter = s.router.PathPrefix("/networks").Subrouter()
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/services").Subrouter()
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
subrouter = s.router.PathPrefix("/services").Subrouter()
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
}
-123
View File
@@ -1,123 +0,0 @@
// +build linux
package server
import (
"fmt"
"net"
"net/http"
"strconv"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon"
"github.com/docker/docker/pkg/sockets"
"github.com/docker/docker/pkg/systemd"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork/portallocator"
)
const (
// See http://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269
linuxMinCpuShares = 2
linuxMaxCpuShares = 262144
)
// newServer sets up the required serverClosers and does protocol specific checking.
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
var (
err error
ls []net.Listener
)
switch proto {
case "fd":
ls, err = systemd.ListenFD(addr)
if err != nil {
return nil, err
}
// We don't want to start serving on these sockets until the
// daemon is initialized and installed. Otherwise required handlers
// won't be ready.
<-s.start
case "tcp":
l, err := s.initTcpSocket(addr)
if err != nil {
return nil, err
}
ls = append(ls, l)
case "unix":
l, err := sockets.NewUnixSocket(addr, s.cfg.SocketGroup, s.start)
if err != nil {
return nil, err
}
ls = append(ls, l)
default:
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
}
var res []serverCloser
for _, l := range ls {
res = append(res, &HttpServer{
&http.Server{
Addr: addr,
Handler: s.router,
},
l,
})
}
return res, nil
}
func (s *Server) AcceptConnections(d *daemon.Daemon) {
// Tell the init daemon we are accepting requests
s.daemon = d
s.registerSubRouter()
go systemd.SdNotify("READY=1")
// close the lock so the listeners start accepting connections
select {
case <-s.start:
default:
close(s.start)
}
}
func allocateDaemonPort(addr string) error {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return err
}
intPort, err := strconv.Atoi(port)
if err != nil {
return err
}
var hostIPs []net.IP
if parsedIP := net.ParseIP(host); parsedIP != nil {
hostIPs = append(hostIPs, parsedIP)
} else if hostIPs, err = net.LookupIP(host); err != nil {
return fmt.Errorf("failed to lookup %s address in host specification", host)
}
pa := portallocator.Get()
for _, hostIP := range hostIPs {
if _, err := pa.RequestPort(hostIP, "tcp", intPort); err != nil {
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
}
}
return nil
}
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
if version.LessThan("1.19") {
if hostConfig != nil && hostConfig.CpuShares > 0 {
// Handle unsupported CpuShares
if hostConfig.CpuShares < linuxMinCpuShares {
logrus.Warnf("Changing requested CpuShares of %d to minimum allowed of %d", hostConfig.CpuShares, linuxMinCpuShares)
hostConfig.CpuShares = linuxMinCpuShares
} else if hostConfig.CpuShares > linuxMaxCpuShares {
logrus.Warnf("Changing requested CpuShares of %d to maximum allowed of %d", hostConfig.CpuShares, linuxMaxCpuShares)
hostConfig.CpuShares = linuxMaxCpuShares
}
}
}
}
-68
View File
@@ -1,68 +0,0 @@
// +build linux
package server
import (
"testing"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/runconfig"
)
func TestAdjustCpuSharesOldApi(t *testing.T) {
apiVersion := version.Version("1.18")
hostConfig := &runconfig.HostConfig{
CpuShares: linuxMinCpuShares - 1,
}
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != linuxMinCpuShares {
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares)
}
hostConfig.CpuShares = linuxMaxCpuShares + 1
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != linuxMaxCpuShares {
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares)
}
hostConfig.CpuShares = 0
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != 0 {
t.Error("Expected CpuShares to be unchanged")
}
hostConfig.CpuShares = 1024
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != 1024 {
t.Error("Expected CpuShares to be unchanged")
}
}
func TestAdjustCpuSharesNoAdjustment(t *testing.T) {
apiVersion := version.Version("1.19")
hostConfig := &runconfig.HostConfig{
CpuShares: linuxMinCpuShares - 1,
}
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != linuxMinCpuShares-1 {
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares-1)
}
hostConfig.CpuShares = linuxMaxCpuShares + 1
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != linuxMaxCpuShares+1 {
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares+1)
}
hostConfig.CpuShares = 0
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != 0 {
t.Error("Expected CpuShares to be unchanged")
}
hostConfig.CpuShares = 1024
adjustCpuShares(apiVersion, hostConfig)
if hostConfig.CpuShares != 1024 {
t.Error("Expected CpuShares to be unchanged")
}
}
-6
View File
@@ -1,6 +0,0 @@
// +build !experimental
package server
func (s *Server) registerSubRouter() {
}
+553
View File
@@ -0,0 +1,553 @@
package server
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"github.com/docker/docker/api"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/version"
)
func TestGetBoolParam(t *testing.T) {
if ret, err := getBoolParam("true"); err != nil || !ret {
t.Fatalf("true -> true, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("True"); err != nil || !ret {
t.Fatalf("True -> true, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("1"); err != nil || !ret {
t.Fatalf("1 -> true, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam(""); err != nil || ret {
t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("false"); err != nil || ret {
t.Fatalf("false -> false, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("0"); err != nil || ret {
t.Fatalf("0 -> false, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("faux"); err == nil || ret {
t.Fatalf("faux -> false, err | got %t %s", ret, err)
}
}
func TesthttpError(t *testing.T) {
r := httptest.NewRecorder()
httpError(r, fmt.Errorf("No such method"))
if r.Code != http.StatusNotFound {
t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code)
}
httpError(r, fmt.Errorf("This accound hasn't been activated"))
if r.Code != http.StatusForbidden {
t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code)
}
httpError(r, fmt.Errorf("Some error"))
if r.Code != http.StatusInternalServerError {
t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code)
}
}
func TestGetVersion(t *testing.T) {
eng := engine.New()
var called bool
eng.Register("version", func(job *engine.Job) engine.Status {
called = true
v := &engine.Env{}
v.SetJson("Version", "42.1")
v.Set("ApiVersion", "1.1.1.1.1")
v.Set("GoVersion", "2.42")
v.Set("Os", "Linux")
v.Set("Arch", "x86_64")
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequest("GET", "/version", nil, eng, t)
if !called {
t.Fatalf("handler was not called")
}
v := readEnv(r.Body, t)
if v.Get("Version") != "42.1" {
t.Fatalf("%#v\n", v)
}
if r.HeaderMap.Get("Content-Type") != "application/json" {
t.Fatalf("%#v\n", r)
}
}
func TestGetInfo(t *testing.T) {
eng := engine.New()
var called bool
eng.Register("info", func(job *engine.Job) engine.Status {
called = true
v := &engine.Env{}
v.SetInt("Containers", 1)
v.SetInt("Images", 42000)
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequest("GET", "/info", nil, eng, t)
if !called {
t.Fatalf("handler was not called")
}
v := readEnv(r.Body, t)
if v.GetInt("Images") != 42000 {
t.Fatalf("%#v\n", v)
}
if v.GetInt("Containers") != 1 {
t.Fatalf("%#v\n", v)
}
assertContentType(r, "application/json", t)
}
func TestGetImagesJSON(t *testing.T) {
eng := engine.New()
var called bool
eng.Register("images", func(job *engine.Job) engine.Status {
called = true
v := createEnvFromGetImagesJSONStruct(sampleImage)
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequest("GET", "/images/json", nil, eng, t)
if !called {
t.Fatal("handler was not called")
}
assertHttpNotError(r, t)
assertContentType(r, "application/json", t)
var observed getImagesJSONStruct
if err := json.Unmarshal(r.Body.Bytes(), &observed); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(observed, sampleImage) {
t.Errorf("Expected %#v but got %#v", sampleImage, observed)
}
}
func TestGetImagesJSONFilter(t *testing.T) {
eng := engine.New()
filter := "nothing"
eng.Register("images", func(job *engine.Job) engine.Status {
filter = job.Getenv("filter")
return engine.StatusOK
})
serveRequest("GET", "/images/json?filter=aaaa", nil, eng, t)
if filter != "aaaa" {
t.Errorf("%#v", filter)
}
}
func TestGetImagesJSONFilters(t *testing.T) {
eng := engine.New()
filter := "nothing"
eng.Register("images", func(job *engine.Job) engine.Status {
filter = job.Getenv("filters")
return engine.StatusOK
})
serveRequest("GET", "/images/json?filters=nnnn", nil, eng, t)
if filter != "nnnn" {
t.Errorf("%#v", filter)
}
}
func TestGetImagesJSONAll(t *testing.T) {
eng := engine.New()
allFilter := "-1"
eng.Register("images", func(job *engine.Job) engine.Status {
allFilter = job.Getenv("all")
return engine.StatusOK
})
serveRequest("GET", "/images/json?all=1", nil, eng, t)
if allFilter != "1" {
t.Errorf("%#v", allFilter)
}
}
func TestGetImagesJSONLegacyFormat(t *testing.T) {
eng := engine.New()
var called bool
eng.Register("images", func(job *engine.Job) engine.Status {
called = true
outsLegacy := engine.NewTable("Created", 0)
outsLegacy.Add(createEnvFromGetImagesJSONStruct(sampleImage))
if _, err := outsLegacy.WriteListTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequestUsingVersion("GET", "/images/json", "1.6", nil, eng, t)
if !called {
t.Fatal("handler was not called")
}
assertHttpNotError(r, t)
assertContentType(r, "application/json", t)
images := engine.NewTable("Created", 0)
if _, err := images.ReadListFrom(r.Body.Bytes()); err != nil {
t.Fatal(err)
}
if images.Len() != 1 {
t.Fatalf("Expected 1 image, %d found", images.Len())
}
image := images.Data[0]
if image.Get("Tag") != "test-tag" {
t.Errorf("Expected tag 'test-tag', found '%s'", image.Get("Tag"))
}
if image.Get("Repository") != "test-name" {
t.Errorf("Expected repository 'test-name', found '%s'", image.Get("Repository"))
}
}
func TestGetContainersByName(t *testing.T) {
eng := engine.New()
name := "container_name"
var called bool
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
called = true
if job.Args[0] != name {
t.Errorf("name != '%s': %#v", name, job.Args[0])
}
if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") {
t.Errorf("dirty env variable not set")
} else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") {
t.Errorf("dirty env variable set when it shouldn't")
}
v := &engine.Env{}
v.SetBool("dirty", true)
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequest("GET", "/containers/"+name+"/json", nil, eng, t)
if !called {
t.Fatal("handler was not called")
}
assertContentType(r, "application/json", t)
var stdoutJson interface{}
if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil {
t.Fatalf("%#v", err)
}
if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 {
t.Fatalf("%#v", stdoutJson)
}
}
func TestGetEvents(t *testing.T) {
eng := engine.New()
var called bool
eng.Register("events", func(job *engine.Job) engine.Status {
called = true
since := job.Getenv("since")
if since != "1" {
t.Fatalf("'since' should be 1, found %#v instead", since)
}
until := job.Getenv("until")
if until != "0" {
t.Fatalf("'until' should be 0, found %#v instead", until)
}
v := &engine.Env{}
v.Set("since", since)
v.Set("until", until)
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequest("GET", "/events?since=1&until=0", nil, eng, t)
if !called {
t.Fatal("handler was not called")
}
assertContentType(r, "application/json", t)
var stdout_json struct {
Since int
Until int
}
if err := json.Unmarshal(r.Body.Bytes(), &stdout_json); err != nil {
t.Fatal(err)
}
if stdout_json.Since != 1 {
t.Errorf("since != 1: %#v", stdout_json.Since)
}
if stdout_json.Until != 0 {
t.Errorf("until != 0: %#v", stdout_json.Until)
}
}
func TestLogs(t *testing.T) {
eng := engine.New()
var inspect bool
var logs bool
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
inspect = true
if len(job.Args) == 0 {
t.Fatal("Job arguments is empty")
}
if job.Args[0] != "test" {
t.Fatalf("Container name %s, must be test", job.Args[0])
}
return engine.StatusOK
})
expected := "logs"
eng.Register("logs", func(job *engine.Job) engine.Status {
logs = true
if len(job.Args) == 0 {
t.Fatal("Job arguments is empty")
}
if job.Args[0] != "test" {
t.Fatalf("Container name %s, must be test", job.Args[0])
}
follow := job.Getenv("follow")
if follow != "1" {
t.Fatalf("follow: %s, must be 1", follow)
}
stdout := job.Getenv("stdout")
if stdout != "1" {
t.Fatalf("stdout %s, must be 1", stdout)
}
stderr := job.Getenv("stderr")
if stderr != "" {
t.Fatalf("stderr %s, must be empty", stderr)
}
timestamps := job.Getenv("timestamps")
if timestamps != "1" {
t.Fatalf("timestamps %s, must be 1", timestamps)
}
job.Stdout.Write([]byte(expected))
return engine.StatusOK
})
r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1&timestamps=1", nil, eng, t)
if r.Code != http.StatusOK {
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
}
if !inspect {
t.Fatal("container_inspect job was not called")
}
if !logs {
t.Fatal("logs job was not called")
}
res := r.Body.String()
if res != expected {
t.Fatalf("Output %s, expected %s", res, expected)
}
}
func TestLogsNoStreams(t *testing.T) {
eng := engine.New()
var inspect bool
var logs bool
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
inspect = true
if len(job.Args) == 0 {
t.Fatal("Job arguments is empty")
}
if job.Args[0] != "test" {
t.Fatalf("Container name %s, must be test", job.Args[0])
}
return engine.StatusOK
})
eng.Register("logs", func(job *engine.Job) engine.Status {
logs = true
return engine.StatusOK
})
r := serveRequest("GET", "/containers/test/logs", nil, eng, t)
if r.Code != http.StatusBadRequest {
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest)
}
if inspect {
t.Fatal("container_inspect job was called, but it shouldn't")
}
if logs {
t.Fatal("logs job was called, but it shouldn't")
}
res := strings.TrimSpace(r.Body.String())
expected := "Bad parameters: you must choose at least one stream"
if !strings.Contains(res, expected) {
t.Fatalf("Output %s, expected %s in it", res, expected)
}
}
func TestGetImagesHistory(t *testing.T) {
eng := engine.New()
imageName := "docker-test-image"
var called bool
eng.Register("history", func(job *engine.Job) engine.Status {
called = true
if len(job.Args) == 0 {
t.Fatal("Job arguments is empty")
}
if job.Args[0] != imageName {
t.Fatalf("name != '%s': %#v", imageName, job.Args[0])
}
v := &engine.Env{}
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequest("GET", "/images/"+imageName+"/history", nil, eng, t)
if !called {
t.Fatalf("handler was not called")
}
if r.Code != http.StatusOK {
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
}
if r.HeaderMap.Get("Content-Type") != "application/json" {
t.Fatalf("%#v\n", r)
}
}
func TestGetImagesByName(t *testing.T) {
eng := engine.New()
name := "image_name"
var called bool
eng.Register("image_inspect", func(job *engine.Job) engine.Status {
called = true
if job.Args[0] != name {
t.Fatalf("name != '%s': %#v", name, job.Args[0])
}
if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") {
t.Fatal("dirty env variable not set")
} else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") {
t.Fatal("dirty env variable set when it shouldn't")
}
v := &engine.Env{}
v.SetBool("dirty", true)
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
})
r := serveRequest("GET", "/images/"+name+"/json", nil, eng, t)
if !called {
t.Fatal("handler was not called")
}
if r.HeaderMap.Get("Content-Type") != "application/json" {
t.Fatalf("%#v\n", r)
}
var stdoutJson interface{}
if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil {
t.Fatalf("%#v", err)
}
if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 {
t.Fatalf("%#v", stdoutJson)
}
}
func TestDeleteContainers(t *testing.T) {
eng := engine.New()
name := "foo"
var called bool
eng.Register("rm", func(job *engine.Job) engine.Status {
called = true
if len(job.Args) == 0 {
t.Fatalf("Job arguments is empty")
}
if job.Args[0] != name {
t.Fatalf("name != '%s': %#v", name, job.Args[0])
}
return engine.StatusOK
})
r := serveRequest("DELETE", "/containers/"+name, nil, eng, t)
if !called {
t.Fatalf("handler was not called")
}
if r.Code != http.StatusNoContent {
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusNoContent)
}
}
func serveRequest(method, target string, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder {
return serveRequestUsingVersion(method, target, api.APIVERSION, body, eng, t)
}
func serveRequestUsingVersion(method, target string, version version.Version, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder {
r := httptest.NewRecorder()
req, err := http.NewRequest(method, target, body)
if err != nil {
t.Fatal(err)
}
ServeRequest(eng, version, r, req)
return r
}
func readEnv(src io.Reader, t *testing.T) *engine.Env {
out := engine.NewOutput()
v, err := out.AddEnv()
if err != nil {
t.Fatal(err)
}
if _, err := io.Copy(out, src); err != nil {
t.Fatal(err)
}
out.Close()
return v
}
func toJson(data interface{}, t *testing.T) io.Reader {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(data); err != nil {
t.Fatal(err)
}
return &buf
}
func assertContentType(recorder *httptest.ResponseRecorder, content_type string, t *testing.T) {
if recorder.HeaderMap.Get("Content-Type") != content_type {
t.Fatalf("%#v\n", recorder)
}
}
// XXX: Duplicated from integration/utils_test.go, but maybe that's OK as that
// should die as soon as we converted all integration tests?
// assertHttpNotError expect the given response to not have an error.
// Otherwise the it causes the test to fail.
func assertHttpNotError(r *httptest.ResponseRecorder, t *testing.T) {
// 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))
}
}
func createEnvFromGetImagesJSONStruct(data getImagesJSONStruct) *engine.Env {
v := &engine.Env{}
v.SetList("RepoTags", data.RepoTags)
v.Set("Id", data.Id)
v.SetInt64("Created", data.Created)
v.SetInt64("Size", data.Size)
v.SetInt64("VirtualSize", data.VirtualSize)
return v
}
type getImagesJSONStruct struct {
RepoTags []string
Id string
Created int64
Size int64
VirtualSize int64
}
var sampleImage getImagesJSONStruct = getImagesJSONStruct{
RepoTags: []string{"test-name:test-tag"},
Id: "ID",
Created: 999,
Size: 777,
VirtualSize: 666,
}
-62
View File
@@ -1,62 +0,0 @@
// +build windows
package server
import (
"errors"
"net"
"net/http"
"github.com/docker/docker/daemon"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/runconfig"
)
// NewServer sets up the required Server and does protocol specific checking.
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
var (
ls []net.Listener
)
switch proto {
case "tcp":
l, err := s.initTcpSocket(addr)
if err != nil {
return nil, err
}
ls = append(ls, l)
default:
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
}
var res []serverCloser
for _, l := range ls {
res = append(res, &HttpServer{
&http.Server{
Addr: addr,
Handler: s.router,
},
l,
})
}
return res, nil
}
func (s *Server) AcceptConnections(d *daemon.Daemon) {
s.daemon = d
s.registerSubRouter()
// close the lock so the listeners start accepting connections
select {
case <-s.start:
default:
close(s.start)
}
}
func allocateDaemonPort(addr string) error {
return nil
}
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
}
+87
View File
@@ -0,0 +1,87 @@
// This package is used for API stability in the types and response to the
// consumers of the API stats endpoint.
package stats
import "time"
type ThrottlingData struct {
// Number of periods with throttling active
Periods uint64 `json:"periods,omitempty"`
// Number of periods when the container hit its throttling limit.
ThrottledPeriods uint64 `json:"throttled_periods,omitempty"`
// Aggregate time the container was throttled for in nanoseconds.
ThrottledTime uint64 `json:"throttled_time,omitempty"`
}
// All CPU stats are aggregated since container inception.
type CpuUsage struct {
// Total CPU time consumed.
// Units: nanoseconds.
TotalUsage uint64 `json:"total_usage,omitempty"`
// Total CPU time consumed per core.
// Units: nanoseconds.
PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
// Time spent by tasks of the cgroup in kernel mode.
// Units: nanoseconds.
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode.
// Units: nanoseconds.
UsageInUsermode uint64 `json:"usage_in_usermode"`
}
type CpuStats struct {
CpuUsage CpuUsage `json:"cpu_usage,omitempty"`
SystemUsage uint64 `json:"system_cpu_usage"`
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
type MemoryStats struct {
// current res_counter usage for memory
Usage uint64 `json:"usage,omitempty"`
// maximum usage ever recorded.
MaxUsage uint64 `json:"max_usage,omitempty"`
// TODO(vishh): Export these as stronger types.
// all the stats exported via memory.stat.
Stats map[string]uint64 `json:"stats,omitempty"`
// number of times memory usage hits limits.
Failcnt uint64 `json:"failcnt"`
Limit uint64 `json:"limit"`
}
type BlkioStatEntry struct {
Major uint64 `json:"major,omitempty"`
Minor uint64 `json:"minor,omitempty"`
Op string `json:"op,omitempty"`
Value uint64 `json:"value,omitempty"`
}
type BlkioStats struct {
// number of bytes tranferred to and from the block device
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"`
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"`
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"`
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"`
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"`
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"`
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"`
}
type Network struct {
RxBytes uint64 `json:"rx_bytes"`
RxPackets uint64 `json:"rx_packets"`
RxErrors uint64 `json:"rx_errors"`
RxDropped uint64 `json:"rx_dropped"`
TxBytes uint64 `json:"tx_bytes"`
TxPackets uint64 `json:"tx_packets"`
TxErrors uint64 `json:"tx_errors"`
TxDropped uint64 `json:"tx_dropped"`
}
type Stats struct {
Read time.Time `json:"read"`
Network Network `json:"network,omitempty"`
CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
}
-91
View File
@@ -1,91 +0,0 @@
// This package is used for API stability in the types and response to the
// consumers of the API stats endpoint.
package types
import "time"
type ThrottlingData struct {
// Number of periods with throttling active
Periods uint64 `json:"periods"`
// Number of periods when the container hit its throttling limit.
ThrottledPeriods uint64 `json:"throttled_periods"`
// Aggregate time the container was throttled for in nanoseconds.
ThrottledTime uint64 `json:"throttled_time"`
}
// All CPU stats are aggregated since container inception.
type CpuUsage struct {
// Total CPU time consumed.
// Units: nanoseconds.
TotalUsage uint64 `json:"total_usage"`
// Total CPU time consumed per core.
// Units: nanoseconds.
PercpuUsage []uint64 `json:"percpu_usage"`
// Time spent by tasks of the cgroup in kernel mode.
// Units: nanoseconds.
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode.
// Units: nanoseconds.
UsageInUsermode uint64 `json:"usage_in_usermode"`
}
type CpuStats struct {
CpuUsage CpuUsage `json:"cpu_usage"`
SystemUsage uint64 `json:"system_cpu_usage"`
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
type MemoryStats struct {
// current res_counter usage for memory
Usage uint64 `json:"usage"`
// maximum usage ever recorded.
MaxUsage uint64 `json:"max_usage"`
// TODO(vishh): Export these as stronger types.
// all the stats exported via memory.stat.
Stats map[string]uint64 `json:"stats"`
// number of times memory usage hits limits.
Failcnt uint64 `json:"failcnt"`
Limit uint64 `json:"limit"`
}
// TODO Windows: This can be factored out
type BlkioStatEntry struct {
Major uint64 `json:"major"`
Minor uint64 `json:"minor"`
Op string `json:"op"`
Value uint64 `json:"value"`
}
// TODO Windows: This can be factored out
type BlkioStats struct {
// number of bytes tranferred to and from the block device
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive"`
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive"`
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive"`
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive"`
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive"`
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive"`
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
}
// TODO Windows: This will require refactoring
type Network struct {
RxBytes uint64 `json:"rx_bytes"`
RxPackets uint64 `json:"rx_packets"`
RxErrors uint64 `json:"rx_errors"`
RxDropped uint64 `json:"rx_dropped"`
TxBytes uint64 `json:"tx_bytes"`
TxPackets uint64 `json:"tx_packets"`
TxErrors uint64 `json:"tx_errors"`
TxDropped uint64 `json:"tx_dropped"`
}
type Stats struct {
Read time.Time `json:"read"`
Network Network `json:"network,omitempty"`
PreCpuStats CpuStats `json:"precpu_stats,omitempty"`
CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
}
-278
View File
@@ -1,278 +0,0 @@
package types
import (
"os"
"time"
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/runconfig"
)
// ContainerCreateResponse contains the information returned to a client on the
// creation of a new container.
type ContainerCreateResponse struct {
// ID is the ID of the created container.
ID string `json:"Id"`
// Warnings are any warnings encountered during the creation of the container.
Warnings []string `json:"Warnings"`
}
// POST /containers/{name:.*}/exec
type ContainerExecCreateResponse struct {
// ID is the exec ID.
ID string `json:"Id"`
}
// POST /auth
type AuthResponse struct {
// Status is the authentication status
Status string `json:"Status"`
}
// POST "/containers/"+containerID+"/wait"
type ContainerWaitResponse struct {
// StatusCode is the status code of the wait job
StatusCode int `json:"StatusCode"`
}
// POST "/commit?container="+containerID
type ContainerCommitResponse struct {
ID string `json:"Id"`
}
// GET "/containers/{name:.*}/changes"
type ContainerChange struct {
Kind int
Path string
}
// GET "/images/{name:.*}/history"
type ImageHistory struct {
ID string `json:"Id"`
Created int64
CreatedBy string
Tags []string
Size int64
Comment string
}
// DELETE "/images/{name:.*}"
type ImageDelete struct {
Untagged string `json:",omitempty"`
Deleted string `json:",omitempty"`
}
// GET "/images/json"
type Image struct {
ID string `json:"Id"`
ParentId string
RepoTags []string
RepoDigests []string
Created int
Size int
VirtualSize int
Labels map[string]string
}
type GraphDriverData struct {
Name string
Data map[string]string
}
// GET "/images/{name:.*}/json"
type ImageInspect struct {
Id string
Parent string
Comment string
Created string
Container string
ContainerConfig *runconfig.Config
DockerVersion string
Author string
Config *runconfig.Config
Architecture string
Os string
Size int64
VirtualSize int64
GraphDriver GraphDriverData
}
// GET "/containers/json"
type Port struct {
IP string `json:",omitempty"`
PrivatePort int
PublicPort int `json:",omitempty"`
Type string
}
type Container struct {
ID string `json:"Id"`
Names []string
Image string
Command string
Created int
Ports []Port
SizeRw int `json:",omitempty"`
SizeRootFs int `json:",omitempty"`
Labels map[string]string
Status string
HostConfig struct {
NetworkMode string `json:",omitempty"`
}
}
// POST "/containers/"+containerID+"/copy"
type CopyConfig struct {
Resource string
}
// ContainerPathStat is used to encode the header from
// GET /containers/{name:.*}/archive
// "name" is basename of the resource.
type ContainerPathStat struct {
Name string `json:"name"`
Size int64 `json:"size"`
Mode os.FileMode `json:"mode"`
Mtime time.Time `json:"mtime"`
LinkTarget string `json:"linkTarget"`
}
// GET "/containers/{name:.*}/top"
type ContainerProcessList struct {
Processes [][]string
Titles []string
}
type Version struct {
Version string
ApiVersion version.Version
GitCommit string
GoVersion string
Os string
Arch string
KernelVersion string `json:",omitempty"`
Experimental bool `json:",omitempty"`
BuildTime string `json:",omitempty"`
}
// GET "/info"
type Info struct {
ID string
Containers int
Images int
Driver string
DriverStatus [][2]string
MemoryLimit bool
SwapLimit bool
CpuCfsPeriod bool
CpuCfsQuota bool
IPv4Forwarding bool
BridgeNfIptables bool
BridgeNfIp6tables bool
Debug bool
NFd int
OomKillDisable bool
NGoroutines int
SystemTime string
ExecutionDriver string
LoggingDriver string
NEventsListener int
KernelVersion string
OperatingSystem string
IndexServerAddress string
RegistryConfig interface{}
InitSha1 string
InitPath string
NCPU int
MemTotal int64
DockerRootDir string
HttpProxy string
HttpsProxy string
NoProxy string
Name string
Labels []string
ExperimentalBuild bool
}
// This struct is a temp struct used by execStart
// Config fields is part of ExecConfig in runconfig package
type ExecStartCheck struct {
// ExecStart will first check if it's detached
Detach bool
// Check if there's a tty
Tty bool
}
type ContainerState struct {
Running bool
Paused bool
Restarting bool
OOMKilled bool
Dead bool
Pid int
ExitCode int
Error string
StartedAt string
FinishedAt string
}
// GET "/containers/{name:.*}/json"
type ContainerJSONBase struct {
Id string
Created string
Path string
Args []string
State *ContainerState
Image string
NetworkSettings *network.Settings
ResolvConfPath string
HostnamePath string
HostsPath string
LogPath string
Name string
RestartCount int
Driver string
ExecDriver string
MountLabel string
ProcessLabel string
AppArmorProfile string
ExecIDs []string
HostConfig *runconfig.HostConfig
GraphDriver GraphDriverData
}
type ContainerJSON struct {
*ContainerJSONBase
Mounts []MountPoint
Config *runconfig.Config
}
// backcompatibility struct along with ContainerConfig
type ContainerJSONPre120 struct {
*ContainerJSONBase
Volumes map[string]string
VolumesRW map[string]bool
Config *ContainerConfig
}
type ContainerConfig struct {
*runconfig.Config
// backward compatibility, they now live in HostConfig
Memory int64
MemorySwap int64
CpuShares int64
Cpuset string
}
// MountPoint represents a mount point configuration inside the container.
type MountPoint struct {
Name string `json:",omitempty"`
Source string
Destination string
Driver string `json:",omitempty"`
Mode string // this is internally named `Relabel`
RW bool
}
+3
View File
@@ -0,0 +1,3 @@
Tibor Vass <teabee89@gmail.com> (@tiborvass)
Erik Hollensbe <github@hollensbe.org> (@erikh)
Doug Davis <dug@us.ibm.com> (@duglin)
-164
View File
@@ -1,164 +0,0 @@
package builder
import (
"fmt"
"strings"
)
// FlagType is the type of the build flag
type FlagType int
const (
boolType FlagType = iota
stringType
)
// BFlags contains all flags information for the builder
type BFlags struct {
Args []string // actual flags/args from cmd line
flags map[string]*Flag
used map[string]*Flag
Err error
}
// Flag contains all information for a flag
type Flag struct {
bf *BFlags
name string
flagType FlagType
Value string
}
// NewBFlags return the new BFlags struct
func NewBFlags() *BFlags {
return &BFlags{
flags: make(map[string]*Flag),
used: make(map[string]*Flag),
}
}
// AddBool adds a bool flag to BFlags
func (bf *BFlags) AddBool(name string, def bool) *Flag {
flag := bf.addFlag(name, boolType)
if flag == nil {
return nil
}
if def {
flag.Value = "true"
} else {
flag.Value = "false"
}
return flag
}
// AddString adds a string flag to BFlags
func (bf *BFlags) AddString(name string, def string) *Flag {
flag := bf.addFlag(name, stringType)
if flag == nil {
return nil
}
flag.Value = def
return flag
}
func (bf *BFlags) addFlag(name string, flagType FlagType) *Flag {
if _, ok := bf.flags[name]; ok {
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
return nil
}
newFlag := &Flag{
bf: bf,
name: name,
flagType: flagType,
}
bf.flags[name] = newFlag
return newFlag
}
// IsUsed checks if the flag is used
func (fl *Flag) IsUsed() bool {
if _, ok := fl.bf.used[fl.name]; ok {
return true
}
return false
}
// IsTrue checks if a bool flag is true
func (fl *Flag) IsTrue() bool {
if fl.flagType != boolType {
// Should never get here
panic(fmt.Errorf("Trying to use IsTrue on a non-boolean: %s", fl.name))
}
return fl.Value == "true"
}
// Parse parses and checks if the BFlags is valid
func (bf *BFlags) Parse() error {
// If there was an error while defining the possible flags
// go ahead and bubble it back up here since we didn't do it
// earlier in the processing
if bf.Err != nil {
return fmt.Errorf("Error setting up flags: %s", bf.Err)
}
for _, arg := range bf.Args {
if !strings.HasPrefix(arg, "--") {
return fmt.Errorf("Arg should start with -- : %s", arg)
}
if arg == "--" {
return nil
}
arg = arg[2:]
value := ""
index := strings.Index(arg, "=")
if index >= 0 {
value = arg[index+1:]
arg = arg[:index]
}
flag, ok := bf.flags[arg]
if !ok {
return fmt.Errorf("Unknown flag: %s", arg)
}
if _, ok = bf.used[arg]; ok {
return fmt.Errorf("Duplicate flag specified: %s", arg)
}
bf.used[arg] = flag
switch flag.flagType {
case boolType:
// value == "" is only ok if no "=" was specified
if index >= 0 && value == "" {
return fmt.Errorf("Missing a value on flag: %s", arg)
}
lower := strings.ToLower(value)
if lower == "" {
flag.Value = "true"
} else if lower == "true" || lower == "false" {
flag.Value = lower
} else {
return fmt.Errorf("Expecting boolean value for flag %s, not: %s", arg, value)
}
case stringType:
if index < 0 {
return fmt.Errorf("Missing a value on flag: %s", arg)
}
flag.Value = value
default:
panic(fmt.Errorf("No idea what kind of flag we have! Should never get here!"))
}
}
return nil
}
-187
View File
@@ -1,187 +0,0 @@
package builder
import (
"testing"
)
func TestBuilderFlags(t *testing.T) {
var expected string
var err error
// ---
bf := NewBFlags()
bf.Args = []string{}
if err := bf.Parse(); err != nil {
t.Fatalf("Test1 of %q was supposed to work: %s", bf.Args, err)
}
// ---
bf = NewBFlags()
bf.Args = []string{"--"}
if err := bf.Parse(); err != nil {
t.Fatalf("Test2 of %q was supposed to work: %s", bf.Args, err)
}
// ---
bf = NewBFlags()
flStr1 := bf.AddString("str1", "")
flBool1 := bf.AddBool("bool1", false)
bf.Args = []string{}
if err = bf.Parse(); err != nil {
t.Fatalf("Test3 of %q was supposed to work: %s", bf.Args, err)
}
if flStr1.IsUsed() == true {
t.Fatalf("Test3 - str1 was not used!")
}
if flBool1.IsUsed() == true {
t.Fatalf("Test3 - bool1 was not used!")
}
// ---
bf = NewBFlags()
flStr1 = bf.AddString("str1", "HI")
flBool1 = bf.AddBool("bool1", false)
bf.Args = []string{}
if err = bf.Parse(); err != nil {
t.Fatalf("Test4 of %q was supposed to work: %s", bf.Args, err)
}
if flStr1.Value != "HI" {
t.Fatalf("Str1 was supposed to default to: HI")
}
if flBool1.IsTrue() {
t.Fatalf("Bool1 was supposed to default to: false")
}
if flStr1.IsUsed() == true {
t.Fatalf("Str1 was not used!")
}
if flBool1.IsUsed() == true {
t.Fatalf("Bool1 was not used!")
}
// ---
bf = NewBFlags()
flStr1 = bf.AddString("str1", "HI")
bf.Args = []string{"--str1"}
if err = bf.Parse(); err == nil {
t.Fatalf("Test %q was supposed to fail", bf.Args)
}
// ---
bf = NewBFlags()
flStr1 = bf.AddString("str1", "HI")
bf.Args = []string{"--str1="}
if err = bf.Parse(); err != nil {
t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
}
expected = ""
if flStr1.Value != expected {
t.Fatalf("Str1 (%q) should be: %q", flStr1.Value, expected)
}
// ---
bf = NewBFlags()
flStr1 = bf.AddString("str1", "HI")
bf.Args = []string{"--str1=BYE"}
if err = bf.Parse(); err != nil {
t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
}
expected = "BYE"
if flStr1.Value != expected {
t.Fatalf("Str1 (%q) should be: %q", flStr1.Value, expected)
}
// ---
bf = NewBFlags()
flBool1 = bf.AddBool("bool1", false)
bf.Args = []string{"--bool1"}
if err = bf.Parse(); err != nil {
t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
}
if !flBool1.IsTrue() {
t.Fatalf("Test-b1 Bool1 was supposed to be true")
}
// ---
bf = NewBFlags()
flBool1 = bf.AddBool("bool1", false)
bf.Args = []string{"--bool1=true"}
if err = bf.Parse(); err != nil {
t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
}
if !flBool1.IsTrue() {
t.Fatalf("Test-b2 Bool1 was supposed to be true")
}
// ---
bf = NewBFlags()
flBool1 = bf.AddBool("bool1", false)
bf.Args = []string{"--bool1=false"}
if err = bf.Parse(); err != nil {
t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
}
if flBool1.IsTrue() {
t.Fatalf("Test-b3 Bool1 was supposed to be false")
}
// ---
bf = NewBFlags()
flBool1 = bf.AddBool("bool1", false)
bf.Args = []string{"--bool1=false1"}
if err = bf.Parse(); err == nil {
t.Fatalf("Test %q was supposed to fail", bf.Args)
}
// ---
bf = NewBFlags()
flBool1 = bf.AddBool("bool1", false)
bf.Args = []string{"--bool2"}
if err = bf.Parse(); err == nil {
t.Fatalf("Test %q was supposed to fail", bf.Args)
}
// ---
bf = NewBFlags()
flStr1 = bf.AddString("str1", "HI")
flBool1 = bf.AddBool("bool1", false)
bf.Args = []string{"--bool1", "--str1=BYE"}
if err = bf.Parse(); err != nil {
t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
}
if flStr1.Value != "BYE" {
t.Fatalf("Teset %s, str1 should be BYE", bf.Args)
}
if !flBool1.IsTrue() {
t.Fatalf("Teset %s, bool1 should be true", bf.Args)
}
}
-38
View File
@@ -1,38 +0,0 @@
// Package command contains the set of Dockerfile commands.
package command
// Define constants for the command strings
const (
Env = "env"
Label = "label"
Maintainer = "maintainer"
Add = "add"
Copy = "copy"
From = "from"
Onbuild = "onbuild"
Workdir = "workdir"
Run = "run"
Cmd = "cmd"
Entrypoint = "entrypoint"
Expose = "expose"
Volume = "volume"
User = "user"
)
// Commands is list of all Dockerfile commands
var Commands = map[string]struct{}{
Env: {},
Label: {},
Maintainer: {},
Add: {},
Copy: {},
From: {},
Onbuild: {},
Workdir: {},
Run: {},
Cmd: {},
Entrypoint: {},
Expose: {},
Volume: {},
User: {},
}
+47 -207
View File
@@ -10,16 +10,14 @@ package builder
import (
"fmt"
"io/ioutil"
"path"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"github.com/Sirupsen/logrus"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/nat"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/runconfig"
)
@@ -30,7 +28,7 @@ const (
)
// dispatch with no layer / parsing. This is effectively not a command.
func nullDispatch(b *builder, args []string, attributes map[string]bool, original string) error {
func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
return nil
}
@@ -39,9 +37,9 @@ func nullDispatch(b *builder, args []string, attributes map[string]bool, origina
// Sets the environment variable foo to bar, also makes interpolation
// in the dockerfile available from the next statement on via ${foo}.
//
func env(b *builder, args []string, attributes map[string]bool, original string) error {
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) == 0 {
return fmt.Errorf("ENV requires at least one argument")
return fmt.Errorf("ENV is missing arguments")
}
if len(args)%2 != 0 {
@@ -49,26 +47,6 @@ func env(b *builder, args []string, attributes map[string]bool, original string)
return fmt.Errorf("Bad input to ENV, too many args")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
// TODO/FIXME/NOT USED
// Just here to show how to use the builder flags stuff within the
// context of a builder command. Will remove once we actually add
// a builder command to something!
/*
flBool1 := b.BuilderFlags.AddBool("bool1", false)
flStr1 := b.BuilderFlags.AddString("str1", "HI")
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
fmt.Printf("Bool1:%v\n", flBool1)
fmt.Printf("Str1:%v\n", flStr1)
*/
commitStr := "ENV"
for j := 0; j < len(args); j++ {
@@ -98,68 +76,25 @@ func env(b *builder, args []string, attributes map[string]bool, original string)
// MAINTAINER some text <maybe@an.email.address>
//
// Sets the maintainer metadata.
func maintainer(b *builder, args []string, attributes map[string]bool, original string) error {
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) != 1 {
return fmt.Errorf("MAINTAINER requires exactly one argument")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
return fmt.Errorf("MAINTAINER requires only one argument")
}
b.maintainer = args[0]
return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
}
// LABEL some json data describing the image
//
// Sets the Label variable foo to bar,
//
func label(b *builder, args []string, attributes map[string]bool, original string) error {
if len(args) == 0 {
return fmt.Errorf("LABEL requires at least one argument")
}
if len(args)%2 != 0 {
// should never get here, but just in case
return fmt.Errorf("Bad input to LABEL, too many args")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
commitStr := "LABEL"
if b.Config.Labels == nil {
b.Config.Labels = map[string]string{}
}
for j := 0; j < len(args); j++ {
// name ==> args[j]
// value ==> args[j+1]
newVar := args[j] + "=" + args[j+1] + ""
commitStr += " " + newVar
b.Config.Labels[args[j]] = args[j+1]
j++
}
return b.commit("", b.Config.Cmd, commitStr)
}
// ADD foo /path
//
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
// exist here. If you do not wish to have this automatic handling, use COPY.
//
func add(b *builder, args []string, attributes map[string]bool, original string) error {
func add(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) < 2 {
return fmt.Errorf("ADD requires at least two arguments")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
return b.runContextCommand(args, true, true, "ADD")
}
@@ -167,15 +102,11 @@ func add(b *builder, args []string, attributes map[string]bool, original string)
//
// Same as 'ADD' but without the tar and remote url handling.
//
func dispatchCopy(b *builder, args []string, attributes map[string]bool, original string) error {
func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) < 2 {
return fmt.Errorf("COPY requires at least two arguments")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
return b.runContextCommand(args, false, false, "COPY")
}
@@ -183,15 +114,11 @@ func dispatchCopy(b *builder, args []string, attributes map[string]bool, origina
//
// This sets the image the dockerfile will build on top of.
//
func from(b *builder, args []string, attributes map[string]bool, original string) error {
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) != 1 {
return fmt.Errorf("FROM requires one argument")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
name := args[0]
if name == NoBaseImageSpecifier {
@@ -208,12 +135,12 @@ func from(b *builder, args []string, attributes map[string]bool, original string
}
}
if err != nil {
if b.Daemon.Graph().IsNotExist(err, name) {
if b.Daemon.Graph().IsNotExist(err) {
image, err = b.pullImage(name)
}
// note that the top level err will still be !nil here if IsNotExist is
// not the error. This approach just simplifies the logic a bit.
// not the error. This approach just simplifies hte logic a bit.
if err != nil {
return err
}
@@ -231,15 +158,7 @@ func from(b *builder, args []string, attributes map[string]bool, original string
// special cases. search for 'OnBuild' in internals.go for additional special
// cases.
//
func onbuild(b *builder, args []string, attributes map[string]bool, original string) error {
if len(args) == 0 {
return fmt.Errorf("ONBUILD requires at least one argument")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
switch triggerInstruction {
case "ONBUILD":
@@ -258,48 +177,17 @@ func onbuild(b *builder, args []string, attributes map[string]bool, original str
//
// Set the working directory for future RUN/CMD/etc statements.
//
func workdir(b *builder, args []string, attributes map[string]bool, original string) error {
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) != 1 {
return fmt.Errorf("WORKDIR requires exactly one argument")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
// Note that workdir passed comes from the Dockerfile. Hence it is in
// Linux format using forward-slashes, even on Windows. However,
// b.Config.WorkingDir is in platform-specific notation (in other words
// on Windows will use `\`
workdir := args[0]
isAbs := false
if runtime.GOOS == "windows" {
// Alternate processing for Windows here is necessary as we can't call
// filepath.IsAbs(workDir) as that would verify Windows style paths,
// along with drive-letters (eg c:\pathto\file.txt). We (arguably
// correctly or not) check for both forward and back slashes as this
// is what the 1.4.2 GoLang implementation of IsAbs() does in the
// isSlash() function.
isAbs = workdir[0] == '\\' || workdir[0] == '/'
} else {
isAbs = filepath.IsAbs(workdir)
if !filepath.IsAbs(workdir) {
workdir = filepath.Join("/", b.Config.WorkingDir, workdir)
}
if !isAbs {
current := b.Config.WorkingDir
if runtime.GOOS == "windows" {
// Convert to Linux format before join
current = strings.Replace(current, "\\", "/", -1)
}
// Must use path.Join so works correctly on Windows, not filepath
workdir = path.Join("/", current, workdir)
}
// Convert to platform specific format
if runtime.GOOS == "windows" {
workdir = strings.Replace(workdir, "/", "\\", -1)
}
b.Config.WorkingDir = workdir
return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
@@ -308,30 +196,21 @@ func workdir(b *builder, args []string, attributes map[string]bool, original str
// RUN some command yo
//
// run a command and commit the image. Args are automatically prepended with
// 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is
// only one argument. The difference in processing:
// 'sh -c' in the event there is only one argument. The difference in
// processing:
//
// RUN echo hi # sh -c echo hi (Linux)
// RUN echo hi # cmd /S /C echo hi (Windows)
// RUN echo hi # sh -c echo hi
// RUN [ "echo", "hi" ] # echo hi
//
func run(b *builder, args []string, attributes map[string]bool, original string) error {
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
if b.image == "" && !b.noBaseImage {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
args = handleJsonArgs(args, attributes)
args = handleJSONArgs(args, attributes)
if !attributes["json"] {
if runtime.GOOS != "windows" {
args = append([]string{"/bin/sh", "-c"}, args...)
} else {
args = append([]string{"cmd", "/S /C"}, args...)
}
if len(args) == 1 {
args = append([]string{"/bin/sh", "-c"}, args[0])
}
runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
@@ -348,9 +227,9 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
b.Config.Cmd = config.Cmd
runconfig.Merge(b.Config, config)
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
log.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
hit, err := b.probeCache()
if err != nil {
@@ -386,24 +265,14 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
// Set the default command to run in the container (which may be empty).
// Argument handling is the same as RUN.
//
func cmd(b *builder, args []string, attributes map[string]bool, original string) error {
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
cmdSlice := handleJSONArgs(args, attributes)
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
b.Config.Cmd = handleJsonArgs(args, attributes)
if !attributes["json"] {
if runtime.GOOS != "windows" {
cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
} else {
cmdSlice = append([]string{"cmd", "/S /C"}, cmdSlice...)
}
b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...)
}
b.Config.Cmd = runconfig.NewCommand(cmdSlice...)
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %v", b.Config.Cmd)); err != nil {
return err
}
@@ -416,33 +285,25 @@ func cmd(b *builder, args []string, attributes map[string]bool, original string)
// ENTRYPOINT /usr/sbin/nginx
//
// Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to
// /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx.
// Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will
// accept the CMD as the arguments to /usr/sbin/nginx.
//
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
// is initialized at NewBuilder time instead of through argument parsing.
//
func entrypoint(b *builder, args []string, attributes map[string]bool, original string) error {
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
parsed := handleJSONArgs(args, attributes)
func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
parsed := handleJsonArgs(args, attributes)
switch {
case attributes["json"]:
// ENTRYPOINT ["echo", "hi"]
b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...)
b.Config.Entrypoint = parsed
case len(parsed) == 0:
// ENTRYPOINT []
b.Config.Entrypoint = nil
default:
// ENTRYPOINT echo hi
if runtime.GOOS != "windows" {
b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0])
} else {
b.Config.Entrypoint = runconfig.NewEntrypoint("cmd", "/S /C", parsed[0])
}
b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]}
}
// when setting the entrypoint if a CMD was not explicitly set then
@@ -451,7 +312,7 @@ func entrypoint(b *builder, args []string, attributes map[string]bool, original
b.Config.Cmd = nil
}
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.Config.Entrypoint)); err != nil {
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %v", b.Config.Entrypoint)); err != nil {
return err
}
@@ -463,22 +324,14 @@ func entrypoint(b *builder, args []string, attributes map[string]bool, original
// Expose ports for links and port mappings. This all ends up in
// b.Config.ExposedPorts for runconfig.
//
func expose(b *builder, args []string, attributes map[string]bool, original string) error {
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
portsTab := args
if len(args) == 0 {
return fmt.Errorf("EXPOSE requires at least one argument")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
if b.Config.ExposedPorts == nil {
b.Config.ExposedPorts = make(nat.PortSet)
}
ports, _, err := nat.ParsePortSpecs(portsTab)
ports, _, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
if err != nil {
return err
}
@@ -496,6 +349,7 @@ func expose(b *builder, args []string, attributes map[string]bool, original stri
i++
}
sort.Strings(portList)
b.Config.PortSpecs = nil
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
}
@@ -504,19 +358,11 @@ func expose(b *builder, args []string, attributes map[string]bool, original stri
// Set the user to 'foo' for future commands and when running the
// ENTRYPOINT/CMD at container run time.
//
func user(b *builder, args []string, attributes map[string]bool, original string) error {
if runtime.GOOS == "windows" {
return fmt.Errorf("USER is not supported on Windows")
}
func user(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) != 1 {
return fmt.Errorf("USER requires exactly one argument")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
}
b.Config.User = args[0]
return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
}
@@ -525,26 +371,15 @@ func user(b *builder, args []string, attributes map[string]bool, original string
//
// Expose the volume /foo for use. Will also accept the JSON array form.
//
func volume(b *builder, args []string, attributes map[string]bool, original string) error {
if runtime.GOOS == "windows" {
return fmt.Errorf("VOLUME is not supported on Windows")
}
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) == 0 {
return fmt.Errorf("VOLUME requires at least one argument")
}
if err := b.BuilderFlags.Parse(); err != nil {
return err
return fmt.Errorf("Volume cannot be empty")
}
if b.Config.Volumes == nil {
b.Config.Volumes = map[string]struct{}{}
}
for _, v := range args {
v = strings.TrimSpace(v)
if v == "" {
return fmt.Errorf("Volume specified can not be an empty string")
}
b.Config.Volumes[v] = struct{}{}
}
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
@@ -552,3 +387,8 @@ func volume(b *builder, args []string, attributes map[string]bool, original stri
}
return nil
}
// INSERT is no longer accepted, but we still parse it.
func insert(b *Builder, args []string, attributes map[string]bool, original string) error {
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
}
+58 -126
View File
@@ -1,4 +1,4 @@
// Package builder is the evaluation step in the Dockerfile parse/evaluate pipeline.
// builder is the evaluation step in the Dockerfile parse/evaluate pipeline.
//
// It incorporates a dispatch table based on the parser.Node values (see the
// parser package for more information) that are yielded from the parser itself.
@@ -20,65 +20,65 @@
package builder
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"path"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/builder/command"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/builder/parser"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/daemon"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/tarsum"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
var (
ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
)
// Environment variable interpolation will happen on these statements only.
var replaceEnvAllowed = map[string]struct{}{
command.Env: {},
command.Label: {},
command.Add: {},
command.Copy: {},
command.Workdir: {},
command.Expose: {},
command.Volume: {},
command.User: {},
"env": {},
"add": {},
"copy": {},
"workdir": {},
"expose": {},
"volume": {},
"user": {},
}
var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error
func init() {
evaluateTable = map[string]func(*builder, []string, map[string]bool, string) error{
command.Env: env,
command.Label: label,
command.Maintainer: maintainer,
command.Add: add,
command.Copy: dispatchCopy, // copy() is a go builtin
command.From: from,
command.Onbuild: onbuild,
command.Workdir: workdir,
command.Run: run,
command.Cmd: cmd,
command.Entrypoint: entrypoint,
command.Expose: expose,
command.Volume: volume,
command.User: user,
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
"env": env,
"maintainer": maintainer,
"add": add,
"copy": dispatchCopy, // copy() is a go builtin
"from": from,
"onbuild": onbuild,
"workdir": workdir,
"run": run,
"cmd": cmd,
"entrypoint": entrypoint,
"expose": expose,
"volume": volume,
"user": user,
"insert": insert,
}
}
// builder is an internal struct, used to maintain configuration of the Dockerfile's
// internal struct, used to maintain configuration of the Dockerfile's
// processing as it evaluates the parsing result.
type builder struct {
type Builder struct {
Daemon *daemon.Daemon
Engine *engine.Engine
// effectively stdio for the run. Because it is not stdio, I said
// "Effectively". Do not use stdio anywhere in this package for any reason.
@@ -87,24 +87,18 @@ type builder struct {
Verbose bool
UtilizeCache bool
cacheBusted bool
// controls how images and containers are handled between steps.
Remove bool
ForceRemove bool
Pull bool
// set this to true if we want the builder to not commit between steps.
// This is useful when we only want to use the evaluator table to generate
// the final configs of the Dockerfile but dont want the layers
disableCommit bool
// Registry server auth configs used to pull images when handling `FROM`.
AuthConfigs map[string]cliconfig.AuthConfig
AuthConfig *registry.AuthConfig
AuthConfigFile *registry.ConfigFile
// Deprecated, original writer used for ImagePull. To be removed.
OutOld io.Writer
StreamFormatter *streamformatter.StreamFormatter
StreamFormatter *utils.StreamFormatter
Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
@@ -116,26 +110,9 @@ type builder struct {
image string // image name for commit processing
maintainer string // maintainer name. could probably be removed.
cmdSet bool // indicates is CMD was set in current Dockerfile
BuilderFlags *BFlags // current cmd's BuilderFlags - temporary
context tarsum.TarSum // the context is a tarball that is uploaded by the client
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system.
// Set resource restrictions for build containers
cpuSetCpus string
cpuSetMems string
cpuShares int64
cpuPeriod int64
cpuQuota int64
cgroupParent string
memory int64
memorySwap int64
ulimits []*ulimit.Ulimit
cancelled <-chan struct{} // When closed, job was cancelled.
activeImages []string
id string // Used to hold reference images
}
// Run the builder with the context. This is the lynchpin of this package. This
@@ -150,85 +127,57 @@ type builder struct {
// processing.
// * Print a happy message and return the image ID.
//
func (b *builder) Run(context io.Reader) (string, error) {
func (b *Builder) Run(context io.Reader) (string, error) {
if err := b.readContext(context); err != nil {
return "", err
}
defer func() {
if err := os.RemoveAll(b.contextPath); err != nil {
logrus.Debugf("[BUILDER] failed to remove temporary context: %s", err)
log.Debugf("[BUILDER] failed to remove temporary context: %s", err)
}
}()
if err := b.readDockerfile(); err != nil {
if err := b.readDockerfile(b.dockerfileName); err != nil {
return "", err
}
// some initializations that would not have been supplied by the caller.
b.Config = &runconfig.Config{}
b.TmpContainers = map[string]struct{}{}
for i, n := range b.dockerfile.Children {
select {
case <-b.cancelled:
logrus.Debug("Builder: build cancelled!")
fmt.Fprintf(b.OutStream, "Build cancelled")
return "", fmt.Errorf("Build cancelled")
default:
// Not cancelled yet, keep going...
}
if err := b.dispatch(i, n); err != nil {
if b.ForceRemove {
b.clearTmp()
}
return "", err
}
fmt.Fprintf(b.OutStream, " ---> %s\n", stringid.TruncateID(b.image))
fmt.Fprintf(b.OutStream, " ---> %s\n", utils.TruncateID(b.image))
if b.Remove {
b.clearTmp()
}
}
if b.image == "" {
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
}
fmt.Fprintf(b.OutStream, "Successfully built %s\n", stringid.TruncateID(b.image))
fmt.Fprintf(b.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
return b.image, nil
}
// Reads a Dockerfile from the current context. It assumes that the
// 'filename' is a relative path from the root of the context
func (b *builder) readDockerfile() error {
// If no -f was specified then look for 'Dockerfile'. If we can't find
// that then look for 'dockerfile'. If neither are found then default
// back to 'Dockerfile' and use that in the error message.
if b.dockerfileName == "" {
b.dockerfileName = api.DefaultDockerfileName
tmpFN := filepath.Join(b.contextPath, api.DefaultDockerfileName)
if _, err := os.Lstat(tmpFN); err != nil {
tmpFN = filepath.Join(b.contextPath, strings.ToLower(api.DefaultDockerfileName))
if _, err := os.Lstat(tmpFN); err == nil {
b.dockerfileName = strings.ToLower(api.DefaultDockerfileName)
}
}
}
func (b *Builder) readDockerfile(filename string) error {
filename = path.Join(b.contextPath, filename)
origFile := b.dockerfileName
filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
if err != nil {
return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
}
fi, err := os.Lstat(filename)
fi, err := os.Stat(filename)
if os.IsNotExist(err) {
return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile)
return fmt.Errorf("Cannot build a directory without a Dockerfile")
}
if fi.Size() == 0 {
return fmt.Errorf("The Dockerfile (%s) cannot be empty", origFile)
return ErrDockerfileEmpty
}
f, err := os.Open(filename)
@@ -252,13 +201,13 @@ func (b *builder) readDockerfile() error {
// Note that this assumes the Dockerfile has been read into memory and
// is now safe to be removed.
excludes, _ := utils.ReadDockerIgnore(filepath.Join(b.contextPath, ".dockerignore"))
excludes, _ := utils.ReadDockerIgnore(path.Join(b.contextPath, ".dockerignore"))
if rm, _ := fileutils.Matches(".dockerignore", excludes); rm == true {
os.Remove(filepath.Join(b.contextPath, ".dockerignore"))
os.Remove(path.Join(b.contextPath, ".dockerignore"))
b.context.(tarsum.BuilderContext).Remove(".dockerignore")
}
if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true {
os.Remove(filepath.Join(b.contextPath, b.dockerfileName))
os.Remove(path.Join(b.contextPath, b.dockerfileName))
b.context.(tarsum.BuilderContext).Remove(b.dockerfileName)
}
@@ -279,30 +228,17 @@ func (b *builder) readDockerfile() error {
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
// deal with that, at least until it becomes more of a general concern with new
// features.
func (b *builder) dispatch(stepN int, ast *parser.Node) error {
func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
cmd := ast.Value
attrs := ast.Attributes
original := ast.Original
flags := ast.Flags
strs := []string{}
msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
if len(ast.Flags) > 0 {
msg += " " + strings.Join(ast.Flags, " ")
}
if cmd == "onbuild" {
if ast.Next == nil {
return fmt.Errorf("ONBUILD requires at least one argument")
}
ast = ast.Next.Children[0]
strs = append(strs, ast.Value)
msg += " " + ast.Value
if len(ast.Flags) > 0 {
msg += " " + strings.Join(ast.Flags, " ")
}
}
// count the number of nodes that we are going to traverse first
@@ -325,11 +261,7 @@ func (b *builder) dispatch(stepN int, ast *parser.Node) error {
var str string
str = ast.Value
if _, ok := replaceEnvAllowed[cmd]; ok {
var err error
str, err = ProcessWord(ast.Value, b.Config.Env)
if err != nil {
return err
}
str = b.replaceEnv(ast.Value)
}
strList[i+l] = str
msgList[i] = ast.Value
@@ -342,10 +274,10 @@ func (b *builder) dispatch(stepN int, ast *parser.Node) error {
// XXX yes, we skip any cmds that are not valid; the parser should have
// picked these out already.
if f, ok := evaluateTable[cmd]; ok {
b.BuilderFlags = NewBFlags()
b.BuilderFlags.Args = flags
return f(b, strList, attrs, original)
}
return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(cmd))
fmt.Fprintf(b.ErrStream, "# Skipping unknown instruction %s\n", strings.ToUpper(cmd))
return nil
}
+158 -245
View File
@@ -12,83 +12,60 @@ import (
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"syscall"
"time"
"github.com/Sirupsen/logrus"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/builder/parser"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/daemon"
"github.com/docker/docker/graph"
"github.com/docker/docker/image"
imagepkg "github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/progressreader"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/tarsum"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
func (b *builder) readContext(context io.Reader) (err error) {
func (b *Builder) readContext(context io.Reader) error {
tmpdirPath, err := ioutil.TempDir("", "docker-build")
if err != nil {
return
return err
}
// Make sure we clean-up upon error. In the happy case the caller
// is expected to manage the clean-up
defer func() {
if err != nil {
if e := os.RemoveAll(tmpdirPath); e != nil {
logrus.Debugf("[BUILDER] failed to remove temporary context: %s", e)
}
}
}()
decompressedStream, err := archive.DecompressStream(context)
if err != nil {
return
return err
}
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version1); err != nil {
return
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version0); err != nil {
return err
}
if err = chrootarchive.Untar(b.context, tmpdirPath, nil); err != nil {
return
if err := chrootarchive.Untar(b.context, tmpdirPath, nil); err != nil {
return err
}
b.contextPath = tmpdirPath
return
return nil
}
func (b *builder) commit(id string, autoCmd *runconfig.Command, comment string) error {
if b.disableCommit {
return nil
}
func (b *Builder) commit(id string, autoCmd []string, comment string) error {
if b.image == "" && !b.noBaseImage {
return fmt.Errorf("Please provide a source image with `from` prior to commit")
}
b.Config.Image = b.image
if id == "" {
cmd := b.Config.Cmd
if runtime.GOOS != "windows" {
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment)
} else {
b.Config.Cmd = runconfig.NewCommand("cmd", "/S /C", "REM (nop) "+comment)
}
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
hit, err := b.probeCache()
if err != nil {
@@ -109,28 +86,20 @@ func (b *builder) commit(id string, autoCmd *runconfig.Command, comment string)
}
defer container.Unmount()
}
container, err := b.Daemon.Get(id)
if err != nil {
return err
container := b.Daemon.Get(id)
if container == nil {
return fmt.Errorf("An error occured while creating the container")
}
// Note: Actually copy the struct
autoConfig := *b.Config
autoConfig.Cmd = autoCmd
commitCfg := &daemon.ContainerCommitConfig{
Author: b.maintainer,
Pause: true,
Config: &autoConfig,
}
// Commit the container
image, err := b.Daemon.Commit(container, commitCfg)
image, err := b.Daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
if err != nil {
return err
}
b.Daemon.Graph().Retain(b.id, image.ID)
b.activeImages = append(b.activeImages, image.ID)
b.image = image.ID
return nil
}
@@ -143,7 +112,7 @@ type copyInfo struct {
tmpDir string
}
func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
if b.context == nil {
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
}
@@ -152,8 +121,7 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
}
// Work in daemon-specific filepath semantics
dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest
dest := args[len(args)-1] // last one is always the dest
copyInfos := []*copyInfo{}
@@ -171,16 +139,8 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
// do the copy (e.g. hash value if cached). Don't actually do
// the copy until we've looked at all src files
for _, orig := range args[0 : len(args)-1] {
if err := calcCopyInfo(
b,
cmdName,
&copyInfos,
orig,
dest,
allowRemote,
allowDecompression,
true,
); err != nil {
err := calcCopyInfo(b, cmdName, &copyInfos, orig, dest, allowRemote, allowDecompression)
if err != nil {
return err
}
}
@@ -188,7 +148,8 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
if len(copyInfos) == 0 {
return fmt.Errorf("No source files were specified")
}
if len(copyInfos) > 1 && !strings.HasSuffix(dest, string(os.PathSeparator)) {
if len(copyInfos) > 1 && !strings.HasSuffix(dest, "/") {
return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
}
@@ -214,19 +175,15 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
}
cmd := b.Config.Cmd
if runtime.GOOS != "windows" {
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
} else {
b.Config.Cmd = runconfig.NewCommand("cmd", "/S /C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
}
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)}
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
hit, err := b.probeCache()
if err != nil {
return err
}
if hit {
// If we do not have at least one hash, never use the cache
if hit && b.UtilizeCache {
return nil
}
@@ -241,59 +198,39 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
}
defer container.Unmount()
if err := container.PrepareStorage(); err != nil {
return err
}
for _, ci := range copyInfos {
if err := b.addContext(container, ci.origPath, ci.destPath, ci.decompress); err != nil {
return err
}
}
if err := container.CleanupStorage(); err != nil {
return err
}
if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)); err != nil {
return err
}
return nil
}
func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool, allowWildcards bool) error {
func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool) error {
// Work in daemon-specific OS filepath semantics. However, we save
// the the origPath passed in here, as it might also be a URL which
// we need to check for in this function.
passedInOrigPath := origPath
origPath = filepath.FromSlash(origPath)
destPath = filepath.FromSlash(destPath)
if origPath != "" && origPath[0] == os.PathSeparator && len(origPath) > 1 {
if origPath != "" && origPath[0] == '/' && len(origPath) > 1 {
origPath = origPath[1:]
}
origPath = strings.TrimPrefix(origPath, "."+string(os.PathSeparator))
origPath = strings.TrimPrefix(origPath, "./")
// Twiddle the destPath when its a relative path - meaning, make it
// relative to the WORKINGDIR
if !filepath.IsAbs(destPath) {
hasSlash := strings.HasSuffix(destPath, string(os.PathSeparator))
destPath = filepath.Join(string(os.PathSeparator), filepath.FromSlash(b.Config.WorkingDir), destPath)
hasSlash := strings.HasSuffix(destPath, "/")
destPath = filepath.Join("/", b.Config.WorkingDir, destPath)
// Make sure we preserve any trailing slash
if hasSlash {
destPath += string(os.PathSeparator)
destPath += "/"
}
}
// In the remote/URL case, download it and gen its hashcode
if urlutil.IsURL(passedInOrigPath) {
// As it's a URL, we go back to processing on what was passed in
// to this function
origPath = passedInOrigPath
if urlutil.IsURL(origPath) {
if !allowRemote {
return fmt.Errorf("Source can't be a URL for %s", cmdName)
}
@@ -306,7 +243,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
*cInfos = append(*cInfos, &ci)
// Initiate the download
resp, err := httputils.Download(ci.origPath)
resp, err := utils.Download(ci.origPath)
if err != nil {
return err
}
@@ -319,22 +256,14 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
ci.tmpDir = tmpDirName
// Create a tmp file within our tmp dir
tmpFileName := filepath.Join(tmpDirName, "tmp")
tmpFileName := path.Join(tmpDirName, "tmp")
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return err
}
// Download and dump result to tmp file
if _, err := io.Copy(tmpFile, progressreader.New(progressreader.Config{
In: resp.Body,
Out: b.OutOld,
Formatter: b.StreamFormatter,
Size: int(resp.ContentLength),
NewLines: true,
ID: "",
Action: "Downloading",
})); err != nil {
if _, err := io.Copy(tmpFile, utils.ProgressReader(resp.Body, int(resp.ContentLength), b.OutOld, b.StreamFormatter, true, "", "Downloading")); err != nil {
tmpFile.Close()
return err
}
@@ -359,19 +288,19 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
return err
}
ci.origPath = filepath.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
ci.origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
// If the destination is a directory, figure out the filename.
if strings.HasSuffix(ci.destPath, string(os.PathSeparator)) {
if strings.HasSuffix(ci.destPath, "/") {
u, err := url.Parse(origPath)
if err != nil {
return err
}
path := u.Path
if strings.HasSuffix(path, string(os.PathSeparator)) {
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}
parts := strings.Split(path, string(os.PathSeparator))
parts := strings.Split(path, "/")
filename := parts[len(parts)-1]
if filename == "" {
return fmt.Errorf("cannot determine filename from url: %s", u)
@@ -384,7 +313,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
if err != nil {
return err
}
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version1)
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version0)
if err != nil {
return err
}
@@ -398,19 +327,17 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
}
// Deal with wildcards
if allowWildcards && containsWildcards(origPath) {
if ContainsWildcards(origPath) {
for _, fileInfo := range b.context.GetSums() {
if fileInfo.Name() == "" {
continue
}
match, _ := filepath.Match(origPath, fileInfo.Name())
match, _ := path.Match(origPath, fileInfo.Name())
if !match {
continue
}
// Note we set allowWildcards to false in case the name has
// a * in it
calcCopyInfo(b, cmdName, cInfos, fileInfo.Name(), destPath, allowRemote, allowDecompression, false)
calcCopyInfo(b, cmdName, cInfos, fileInfo.Name(), destPath, allowRemote, allowDecompression)
}
return nil
}
@@ -420,7 +347,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
if err := b.checkPathForAddition(origPath); err != nil {
return err
}
fi, _ := os.Stat(filepath.Join(b.contextPath, origPath))
fi, _ := os.Stat(path.Join(b.contextPath, origPath))
ci := copyInfo{}
ci.origPath = origPath
@@ -441,20 +368,20 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
// Must be a dir
var subfiles []string
absOrigPath := filepath.Join(b.contextPath, ci.origPath)
absOrigPath := path.Join(b.contextPath, ci.origPath)
// Add a trailing / to make sure we only pick up nested files under
// the dir and not sibling files of the dir that just happen to
// start with the same chars
if !strings.HasSuffix(absOrigPath, string(os.PathSeparator)) {
absOrigPath += string(os.PathSeparator)
if !strings.HasSuffix(absOrigPath, "/") {
absOrigPath += "/"
}
// Need path w/o slash too to find matching dir w/o trailing slash
// Need path w/o / too to find matching dir w/o trailing /
absOrigPathNoSlash := absOrigPath[:len(absOrigPath)-1]
for _, fileInfo := range b.context.GetSums() {
absFile := filepath.Join(b.contextPath, fileInfo.Name())
absFile := path.Join(b.contextPath, fileInfo.Name())
// Any file in the context that starts with the given path will be
// picked up and its hashcode used. However, we'll exclude the
// root dir itself. We do this for a coupel of reasons:
@@ -475,7 +402,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
return nil
}
func containsWildcards(name string) bool {
func ContainsWildcards(name string) bool {
for i := 0; i < len(name); i++ {
ch := name[i]
if ch == '\\' {
@@ -487,36 +414,29 @@ func containsWildcards(name string) bool {
return false
}
func (b *builder) pullImage(name string) (*image.Image, error) {
func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
remote, tag := parsers.ParseRepositoryTag(name)
if tag == "" {
tag = "latest"
}
pullRegistryAuth := &cliconfig.AuthConfig{}
if len(b.AuthConfigs) > 0 {
job := b.Engine.Job("pull", remote, tag)
pullRegistryAuth := b.AuthConfig
if len(b.AuthConfigFile.Configs) > 0 {
// The request came with a full auth config file, we prefer to use that
repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote)
repoInfo, err := registry.ResolveRepositoryInfo(job, remote)
if err != nil {
return nil, err
}
resolvedConfig := registry.ResolveAuthConfig(
&cliconfig.ConfigFile{AuthConfigs: b.AuthConfigs},
repoInfo.Index,
)
pullRegistryAuth = &resolvedConfig
resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index)
pullRegistryAuth = &resolvedAuth
}
imagePullConfig := &graph.ImagePullConfig{
AuthConfig: pullRegistryAuth,
OutStream: ioutils.NopWriteCloser(b.OutOld),
}
if err := b.Daemon.Repositories().Pull(remote, tag, imagePullConfig); err != nil {
job.SetenvBool("json", b.StreamFormatter.Json())
job.SetenvBool("parallel", true)
job.SetenvJson("authConfig", pullRegistryAuth)
job.Stdout.Add(b.OutOld)
if err := job.Run(); err != nil {
return nil, err
}
image, err := b.Daemon.Repositories().LookupImage(name)
if err != nil {
return nil, err
@@ -525,15 +445,14 @@ func (b *builder) pullImage(name string) (*image.Image, error) {
return image, nil
}
func (b *builder) processImageFrom(img *image.Image) error {
func (b *Builder) processImageFrom(img *imagepkg.Image) error {
b.image = img.ID
if img.Config != nil {
b.Config = img.Config
}
// The default path will be blank on Windows (set by HCS)
if len(b.Config.Env) == 0 && daemon.DefaultPathEnv != "" {
if len(b.Config.Env) == 0 {
b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
}
@@ -542,7 +461,7 @@ func (b *builder) processImageFrom(img *image.Image) error {
fmt.Fprintf(b.ErrStream, "# Executing %d build triggers\n", nTriggers)
}
// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
// Copy the ONBUILD triggers, and remove them from the config, since the config will be commited.
onBuildTriggers := b.Config.OnBuild
b.Config.OnBuild = []string{}
@@ -577,51 +496,32 @@ func (b *builder) processImageFrom(img *image.Image) error {
// in the current server `b.Daemon`. If an image is found, probeCache returns
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
// is any error, it returns `(false, err)`.
func (b *builder) probeCache() (bool, error) {
if !b.UtilizeCache || b.cacheBusted {
return false, nil
func (b *Builder) probeCache() (bool, error) {
if b.UtilizeCache {
if cache, err := b.Daemon.ImageGetCached(b.image, b.Config); err != nil {
return false, err
} else if cache != nil {
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
log.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return true, nil
} else {
log.Debugf("[BUILDER] Cache miss")
}
}
cache, err := b.Daemon.ImageGetCached(b.image, b.Config)
if err != nil {
return false, err
}
if cache == nil {
logrus.Debugf("[BUILDER] Cache miss")
b.cacheBusted = true
return false, nil
}
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
logrus.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
b.Daemon.Graph().Retain(b.id, cache.ID)
b.activeImages = append(b.activeImages, cache.ID)
return true, nil
return false, nil
}
func (b *builder) create() (*daemon.Container, error) {
func (b *Builder) create() (*daemon.Container, error) {
if b.image == "" && !b.noBaseImage {
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.Config.Image = b.image
hostConfig := &runconfig.HostConfig{
CpuShares: b.cpuShares,
CpuPeriod: b.cpuPeriod,
CpuQuota: b.cpuQuota,
CpusetCpus: b.cpuSetCpus,
CpusetMems: b.cpuSetMems,
CgroupParent: b.cgroupParent,
Memory: b.memory,
MemorySwap: b.memorySwap,
Ulimits: b.ulimits,
}
config := *b.Config
// Create the container
c, warnings, err := b.Daemon.Create(b.Config, hostConfig, "")
c, warnings, err := b.Daemon.Create(b.Config, nil, "")
if err != nil {
return nil, err
}
@@ -630,62 +530,51 @@ func (b *builder) create() (*daemon.Container, error) {
}
b.TmpContainers[c.ID] = struct{}{}
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID))
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
if config.Cmd.Len() > 0 {
if len(config.Cmd) > 0 {
// override the entry point that may have been picked up from the base image
s := config.Cmd.Slice()
c.Path = s[0]
c.Args = s[1:]
c.Path = config.Cmd[0]
c.Args = config.Cmd[1:]
} else {
config.Cmd = runconfig.NewCommand()
config.Cmd = []string{}
}
return c, nil
}
func (b *builder) run(c *daemon.Container) error {
var errCh chan error
if b.Verbose {
errCh = c.Attach(nil, b.OutStream, b.ErrStream)
}
func (b *Builder) run(c *daemon.Container) error {
//start the container
if err := c.Start(); err != nil {
return err
}
finished := make(chan struct{})
defer close(finished)
go func() {
select {
case <-b.cancelled:
logrus.Debugln("Build cancelled, killing container:", c.ID)
c.Kill()
case <-finished:
}
}()
if b.Verbose {
// Block on reading output from container, stop on err or chan closed
if err := <-errCh; err != nil {
logsJob := b.Engine.Job("logs", c.ID)
logsJob.Setenv("follow", "1")
logsJob.Setenv("stdout", "1")
logsJob.Setenv("stderr", "1")
logsJob.Stdout.Add(b.OutStream)
logsJob.Stderr.Set(b.ErrStream)
if err := logsJob.Run(); err != nil {
return err
}
}
// Wait for it to finish
if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 {
return &jsonmessage.JSONError{
Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", b.Config.Cmd.ToString(), ret),
err := &utils.JSONError{
Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret),
Code: ret,
}
return err
}
return nil
}
func (b *builder) checkPathForAddition(orig string) error {
origPath := filepath.Join(b.contextPath, orig)
func (b *Builder) checkPathForAddition(orig string) error {
origPath := path.Join(b.contextPath, orig)
origPath, err := filepath.EvalSymlinks(origPath)
if err != nil {
if os.IsNotExist(err) {
@@ -693,11 +582,7 @@ func (b *builder) checkPathForAddition(orig string) error {
}
return err
}
contextPath, err := filepath.EvalSymlinks(b.contextPath)
if err != nil {
return err
}
if !strings.HasPrefix(origPath, contextPath) {
if !strings.HasPrefix(origPath, b.contextPath) {
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
}
if _, err := os.Stat(origPath); err != nil {
@@ -709,31 +594,29 @@ func (b *builder) checkPathForAddition(orig string) error {
return nil
}
func (b *builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
func (b *Builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
var (
err error
destExists = true
origPath = filepath.Join(b.contextPath, orig)
destPath string
origPath = path.Join(b.contextPath, orig)
destPath = path.Join(container.RootfsPath(), dest)
)
// Work in daemon-local OS specific file paths
dest = filepath.FromSlash(dest)
destPath, err = container.GetResourcePath(dest)
if err != nil {
return err
if destPath != container.RootfsPath() {
destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath())
if err != nil {
return err
}
}
// Preserve the trailing slash
if strings.HasSuffix(dest, string(os.PathSeparator)) || dest == "." {
destPath = destPath + string(os.PathSeparator)
// Preserve the trailing '/'
if strings.HasSuffix(dest, "/") || dest == "." {
destPath = destPath + "/"
}
destStat, err := os.Stat(destPath)
if err != nil {
if !os.IsNotExist(err) {
logrus.Errorf("Error performing os.Stat on %s. %s", destPath, err)
return err
}
destExists = false
@@ -756,9 +639,9 @@ func (b *builder) addContext(container *daemon.Container, orig, dest string, dec
// First try to unpack the source as an archive
// to support the untar feature we need to clean up the path a little bit
// because tar is very forgiving. First we need to strip off the archive's
// filename from the path but this is only added if it does not end in slash
// filename from the path but this is only added if it does not end in / .
tarDest := destPath
if strings.HasSuffix(tarDest, string(os.PathSeparator)) {
if strings.HasSuffix(tarDest, "/") {
tarDest = filepath.Dir(destPath)
}
@@ -766,11 +649,11 @@ func (b *builder) addContext(container *daemon.Container, orig, dest string, dec
if err := chrootarchive.UntarPath(origPath, tarDest); err == nil {
return nil
} else if err != io.EOF {
logrus.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err)
log.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err)
}
}
if err := system.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
return err
}
if err := chrootarchive.CopyWithTar(origPath, destPath); err != nil {
@@ -779,7 +662,7 @@ func (b *builder) addContext(container *daemon.Container, orig, dest string, dec
resPath := destPath
if destExists && destStat.IsDir() {
resPath = filepath.Join(destPath, filepath.Base(origPath))
resPath = path.Join(destPath, path.Base(origPath))
}
return fixPermissions(origPath, resPath, 0, 0, destExists)
@@ -792,17 +675,47 @@ func copyAsDirectory(source, destination string, destExisted bool) error {
return fixPermissions(source, destination, 0, 0, destExisted)
}
func (b *builder) clearTmp() {
for c := range b.TmpContainers {
rmConfig := &daemon.ContainerRmConfig{
ForceRemove: true,
RemoveVolume: true,
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
// If the destination didn't already exist, or the destination isn't a
// directory, then we should Lchown the destination. Otherwise, we shouldn't
// Lchown the destination.
destStat, err := os.Stat(destination)
if err != nil {
// This should *never* be reached, because the destination must've already
// been created while untar-ing the context.
return err
}
doChownDestination := !destExisted || !destStat.IsDir()
// We Walk on the source rather than on the destination because we don't
// want to change permissions on things we haven't created or modified.
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
// Do not alter the walk root iff. it existed before, as it doesn't fall under
// the domain of "things we should chown".
if !doChownDestination && (source == fullpath) {
return nil
}
if err := b.Daemon.ContainerRm(c, rmConfig); err != nil {
fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %v\n", stringid.TruncateID(c), err)
// Path is prefixed by source: substitute with destination instead.
cleaned, err := filepath.Rel(source, fullpath)
if err != nil {
return err
}
fullpath = path.Join(destination, cleaned)
return os.Lchown(fullpath, uid, gid)
})
}
func (b *Builder) clearTmp() {
for c := range b.TmpContainers {
tmp := b.Daemon.Get(c)
if err := b.Daemon.Destroy(tmp); err != nil {
fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
return
}
b.Daemon.DeleteVolumes(tmp.VolumePaths())
delete(b.TmpContainers, c)
fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", stringid.TruncateID(c))
fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
}
}
-40
View File
@@ -1,40 +0,0 @@
// +build linux
package builder
import (
"os"
"path/filepath"
)
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
// If the destination didn't already exist, or the destination isn't a
// directory, then we should Lchown the destination. Otherwise, we shouldn't
// Lchown the destination.
destStat, err := os.Stat(destination)
if err != nil {
// This should *never* be reached, because the destination must've already
// been created while untar-ing the context.
return err
}
doChownDestination := !destExisted || !destStat.IsDir()
// We Walk on the source rather than on the destination because we don't
// want to change permissions on things we haven't created or modified.
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
// Do not alter the walk root iff. it existed before, as it doesn't fall under
// the domain of "things we should chown".
if !doChownDestination && (source == fullpath) {
return nil
}
// Path is prefixed by source: substitute with destination instead.
cleaned, err := filepath.Rel(source, fullpath)
if err != nil {
return err
}
fullpath = filepath.Join(destination, cleaned)
return os.Lchown(fullpath, uid, gid)
})
}
-8
View File
@@ -1,8 +0,0 @@
// +build windows
package builder
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
// chown is not supported on Windows
return nil
}
+83 -298
View File
@@ -1,355 +1,140 @@
package builder
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"sync"
"os/exec"
"github.com/docker/docker/api"
"github.com/docker/docker/builder/parser"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/daemon"
"github.com/docker/docker/graph/tags"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/progressreader"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
// When downloading remote contexts, limit the amount (in bytes)
// to be read from the response body in order to detect its Content-Type
const maxPreambleLength = 100
// whitelist of commands allowed for a commit/import
var validCommitCommands = map[string]bool{
"cmd": true,
"entrypoint": true,
"env": true,
"expose": true,
"label": true,
"onbuild": true,
"user": true,
"volume": true,
"workdir": true,
type BuilderJob struct {
Engine *engine.Engine
Daemon *daemon.Daemon
}
// Config contains all configs for a build job
type Config struct {
DockerfileName string
RemoteURL string
RepoName string
SuppressOutput bool
NoCache bool
Remove bool
ForceRemove bool
Pull bool
Memory int64
MemorySwap int64
CPUShares int64
CPUPeriod int64
CPUQuota int64
CPUSetCpus string
CPUSetMems string
CgroupParent string
Ulimits []*ulimit.Ulimit
AuthConfigs map[string]cliconfig.AuthConfig
Stdout io.Writer
Context io.ReadCloser
// 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
func (b *BuilderJob) Install() {
b.Engine.Register("build", b.CmdBuild)
}
// Cancel signals the build job to cancel
func (b *Config) Cancel() {
b.cancelOnce.Do(func() {
close(b.cancelled)
})
}
// WaitCancelled returns a channel which is closed ("never blocks") when
// the job is cancelled.
func (b *Config) WaitCancelled() <-chan struct{} {
return b.cancelled
}
// NewBuildConfig returns a new Config struct
func NewBuildConfig() *Config {
return &Config{
AuthConfigs: map[string]cliconfig.AuthConfig{},
cancelled: make(chan struct{}),
func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
if len(job.Args) != 0 {
return job.Errorf("Usage: %s\n", job.Name)
}
}
// Build is the main interface of the package, it gathers the Builder
// struct and calls builder.Run() to do all the real build job.
func Build(d *daemon.Daemon, buildConfig *Config) error {
var (
repoName string
tag string
context io.ReadCloser
dockerfileName = job.Getenv("dockerfile")
remoteURL = job.Getenv("remote")
repoName = job.Getenv("t")
suppressOutput = job.GetenvBool("q")
noCache = job.GetenvBool("nocache")
rm = job.GetenvBool("rm")
forceRm = job.GetenvBool("forcerm")
pull = job.GetenvBool("pull")
authConfig = &registry.AuthConfig{}
configFile = &registry.ConfigFile{}
tag string
context io.ReadCloser
)
sf := streamformatter.NewJSONStreamFormatter()
repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName)
job.GetenvJson("authConfig", authConfig)
job.GetenvJson("configFile", configFile)
repoName, tag = parsers.ParseRepositoryTag(repoName)
if repoName != "" {
if err := registry.ValidateRepositoryName(repoName); err != nil {
return err
return job.Error(err)
}
if len(tag) > 0 {
if err := tags.ValidateTagName(tag); err != nil {
return err
if err := graph.ValidateTagName(tag); err != nil {
return job.Error(err)
}
}
}
if buildConfig.RemoteURL == "" {
context = ioutil.NopCloser(buildConfig.Context)
} else if urlutil.IsGitURL(buildConfig.RemoteURL) {
root, err := utils.GitClone(buildConfig.RemoteURL)
if dockerfileName == "" {
dockerfileName = api.DefaultDockerfileName
}
if remoteURL == "" {
context = ioutil.NopCloser(job.Stdin)
} else if urlutil.IsGitURL(remoteURL) {
if !urlutil.IsGitTransport(remoteURL) {
remoteURL = "https://" + remoteURL
}
root, err := ioutil.TempDir("", "docker-build-git")
if err != nil {
return err
return job.Error(err)
}
defer os.RemoveAll(root)
if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
return job.Errorf("Error trying to use git: %s (%s)", err, output)
}
c, err := archive.Tar(root, archive.Uncompressed)
if err != nil {
return err
return job.Error(err)
}
context = c
} else if urlutil.IsURL(buildConfig.RemoteURL) {
f, err := httputils.Download(buildConfig.RemoteURL)
} else if urlutil.IsURL(remoteURL) {
f, err := utils.Download(remoteURL)
if err != nil {
return fmt.Errorf("Error downloading remote context %s: %v", buildConfig.RemoteURL, err)
return job.Error(err)
}
defer f.Body.Close()
ct := f.Header.Get("Content-Type")
clen := int(f.ContentLength)
contentType, bodyReader, err := inspectResponse(ct, f.Body, clen)
defer bodyReader.Close()
dockerFile, err := ioutil.ReadAll(f.Body)
if err != nil {
return fmt.Errorf("Error detecting content type for remote %s: %v", buildConfig.RemoteURL, err)
return job.Error(err)
}
if contentType == httputils.MimeTypes.TextPlain {
dockerFile, err := ioutil.ReadAll(bodyReader)
if err != nil {
return err
}
// When we're downloading just a Dockerfile put it in
// the default name - don't allow the client to move/specify it
buildConfig.DockerfileName = api.DefaultDockerfileName
c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
if err != nil {
return err
}
context = c
} else {
// Pass through - this is a pre-packaged context, presumably
// with a Dockerfile with the right name inside it.
prCfg := progressreader.Config{
In: bodyReader,
Out: buildConfig.Stdout,
Formatter: sf,
Size: clen,
NewLines: true,
ID: "Downloading context",
Action: buildConfig.RemoteURL,
}
context = progressreader.New(prCfg)
c, err := archive.Generate(dockerfileName, string(dockerFile))
if err != nil {
return job.Error(err)
}
context = c
}
defer context.Close()
builder := &builder{
Daemon: d,
OutStream: &streamformatter.StdoutFormater{
Writer: buildConfig.Stdout,
StreamFormatter: sf,
},
ErrStream: &streamformatter.StderrFormater{
Writer: buildConfig.Stdout,
StreamFormatter: sf,
},
Verbose: !buildConfig.SuppressOutput,
UtilizeCache: !buildConfig.NoCache,
Remove: buildConfig.Remove,
ForceRemove: buildConfig.ForceRemove,
Pull: buildConfig.Pull,
OutOld: buildConfig.Stdout,
StreamFormatter: sf,
AuthConfigs: buildConfig.AuthConfigs,
dockerfileName: buildConfig.DockerfileName,
cpuShares: buildConfig.CPUShares,
cpuPeriod: buildConfig.CPUPeriod,
cpuQuota: buildConfig.CPUQuota,
cpuSetCpus: buildConfig.CPUSetCpus,
cpuSetMems: buildConfig.CPUSetMems,
cgroupParent: buildConfig.CgroupParent,
memory: buildConfig.Memory,
memorySwap: buildConfig.MemorySwap,
ulimits: buildConfig.Ulimits,
cancelled: buildConfig.WaitCancelled(),
id: stringid.GenerateRandomID(),
}
sf := utils.NewStreamFormatter(job.GetenvBool("json"))
defer func() {
builder.Daemon.Graph().Release(builder.id, builder.activeImages...)
}()
builder := &Builder{
Daemon: b.Daemon,
Engine: b.Engine,
OutStream: &utils.StdoutFormater{
Writer: job.Stdout,
StreamFormatter: sf,
},
ErrStream: &utils.StderrFormater{
Writer: job.Stdout,
StreamFormatter: sf,
},
Verbose: !suppressOutput,
UtilizeCache: !noCache,
Remove: rm,
ForceRemove: forceRm,
Pull: pull,
OutOld: job.Stdout,
StreamFormatter: sf,
AuthConfig: authConfig,
AuthConfigFile: configFile,
dockerfileName: dockerfileName,
}
id, err := builder.Run(context)
if err != nil {
return err
return job.Error(err)
}
if repoName != "" {
return d.Repositories().Tag(repoName, tag, id, true)
b.Daemon.Repositories().Set(repoName, tag, id, true)
}
return nil
}
// BuildFromConfig will do build directly from parameter 'changes', which comes
// from Dockerfile entries, it will:
//
// - call parse.Parse() to get AST root from Dockerfile entries
// - do build by calling builder.dispatch() to call all entries' handling routines
func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) {
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
if err != nil {
return nil, err
}
// ensure that the commands are valid
for _, n := range ast.Children {
if !validCommitCommands[n.Value] {
return nil, fmt.Errorf("%s is not a valid change command", n.Value)
}
}
builder := &builder{
Daemon: d,
Config: c,
OutStream: ioutil.Discard,
ErrStream: ioutil.Discard,
disableCommit: true,
}
for i, n := range ast.Children {
if err := builder.dispatch(i, n); err != nil {
return nil, err
}
}
return builder.Config, nil
}
// CommitConfig contains build configs for commit operation
type CommitConfig struct {
Pause bool
Repo string
Tag string
Author string
Comment string
Changes []string
Config *runconfig.Config
}
// Commit will create a new image from a container's changes
func Commit(name string, d *daemon.Daemon, c *CommitConfig) (string, error) {
container, err := d.Get(name)
if err != nil {
return "", err
}
if c.Config == nil {
c.Config = &runconfig.Config{}
}
newConfig, err := BuildFromConfig(d, c.Config, c.Changes)
if err != nil {
return "", err
}
if err := runconfig.Merge(newConfig, container.Config); err != nil {
return "", err
}
commitCfg := &daemon.ContainerCommitConfig{
Pause: c.Pause,
Repo: c.Repo,
Tag: c.Tag,
Author: c.Author,
Comment: c.Comment,
Config: newConfig,
}
img, err := d.Commit(container, commitCfg)
if err != nil {
return "", err
}
return img.ID, nil
}
// inspectResponse looks into the http response data at r to determine whether its
// content-type is on the list of acceptable content types for remote build contexts.
// This function returns:
// - a string representation of the detected content-type
// - an io.Reader for the response body
// - an error value which will be non-nil either when something goes wrong while
// reading bytes from r or when the detected content-type is not acceptable.
func inspectResponse(ct string, r io.ReadCloser, clen int) (string, io.ReadCloser, error) {
plen := clen
if plen <= 0 || plen > maxPreambleLength {
plen = maxPreambleLength
}
preamble := make([]byte, plen, plen)
rlen, err := r.Read(preamble)
if rlen == 0 {
return ct, r, errors.New("Empty response")
}
if err != nil && err != io.EOF {
return ct, r, err
}
preambleR := bytes.NewReader(preamble)
bodyReader := ioutil.NopCloser(io.MultiReader(preambleR, r))
// Some web servers will use application/octet-stream as the default
// content type for files without an extension (e.g. 'Dockerfile')
// so if we receive this value we better check for text content
contentType := ct
if len(ct) == 0 || ct == httputils.MimeTypes.OctetStream {
contentType, _, err = httputils.DetectContentType(preamble)
if err != nil {
return contentType, bodyReader, err
}
}
contentType = selectAcceptableMIME(contentType)
var cterr error
if len(contentType) == 0 {
cterr = fmt.Errorf("unsupported Content-Type %q", ct)
contentType = ct
}
return contentType, bodyReader, cterr
return engine.StatusOK
}
-113
View File
@@ -1,113 +0,0 @@
package builder
import (
"bytes"
"io/ioutil"
"testing"
)
var textPlainDockerfile = "FROM busybox"
var binaryContext = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} //xz magic
func TestInspectEmptyResponse(t *testing.T) {
ct := "application/octet-stream"
br := ioutil.NopCloser(bytes.NewReader([]byte("")))
contentType, bReader, err := inspectResponse(ct, br, 0)
if err == nil {
t.Fatalf("Should have generated an error for an empty response")
}
if contentType != "application/octet-stream" {
t.Fatalf("Content type should be 'application/octet-stream' but is %q", contentType)
}
body, err := ioutil.ReadAll(bReader)
if err != nil {
t.Fatal(err)
}
if len(body) != 0 {
t.Fatal("response body should remain empty")
}
}
func TestInspectResponseBinary(t *testing.T) {
ct := "application/octet-stream"
br := ioutil.NopCloser(bytes.NewReader(binaryContext))
contentType, bReader, err := inspectResponse(ct, br, len(binaryContext))
if err != nil {
t.Fatal(err)
}
if contentType != "application/octet-stream" {
t.Fatalf("Content type should be 'application/octet-stream' but is %q", contentType)
}
body, err := ioutil.ReadAll(bReader)
if err != nil {
t.Fatal(err)
}
if len(body) != len(binaryContext) {
t.Fatalf("Wrong response size %d, should be == len(binaryContext)", len(body))
}
for i := range body {
if body[i] != binaryContext[i] {
t.Fatalf("Corrupted response body at byte index %d", i)
}
}
}
func TestResponseUnsupportedContentType(t *testing.T) {
content := []byte(textPlainDockerfile)
ct := "application/json"
br := ioutil.NopCloser(bytes.NewReader(content))
contentType, bReader, err := inspectResponse(ct, br, len(textPlainDockerfile))
if err == nil {
t.Fatal("Should have returned an error on content-type 'application/json'")
}
if contentType != ct {
t.Fatalf("Should not have altered content-type: orig: %s, altered: %s", ct, contentType)
}
body, err := ioutil.ReadAll(bReader)
if err != nil {
t.Fatal(err)
}
if string(body) != textPlainDockerfile {
t.Fatalf("Corrupted response body %s", body)
}
}
func TestInspectResponseTextSimple(t *testing.T) {
content := []byte(textPlainDockerfile)
ct := "text/plain"
br := ioutil.NopCloser(bytes.NewReader(content))
contentType, bReader, err := inspectResponse(ct, br, len(content))
if err != nil {
t.Fatal(err)
}
if contentType != "text/plain" {
t.Fatalf("Content type should be 'text/plain' but is %q", contentType)
}
body, err := ioutil.ReadAll(bReader)
if err != nil {
t.Fatal(err)
}
if string(body) != textPlainDockerfile {
t.Fatalf("Corrupted response body %s", body)
}
}
func TestInspectResponseEmptyContentType(t *testing.T) {
content := []byte(textPlainDockerfile)
br := ioutil.NopCloser(bytes.NewReader(content))
contentType, bodyReader, err := inspectResponse("", br, len(content))
if err != nil {
t.Fatal(err)
}
if contentType != "text/plain" {
t.Fatalf("Content type should be 'text/plain' but is %q", contentType)
}
body, err := ioutil.ReadAll(bodyReader)
if err != nil {
t.Fatal(err)
}
if string(body) != textPlainDockerfile {
t.Fatalf("Corrupted response body %s", body)
}
}
+28 -53
View File
@@ -30,10 +30,6 @@ func parseIgnore(rest string) (*Node, map[string]bool, error) {
// ONBUILD RUN foo bar -> (onbuild (run foo bar))
//
func parseSubCommand(rest string) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
_, child, err := parseLine(rest)
if err != nil {
return nil, nil, err
@@ -44,10 +40,10 @@ func parseSubCommand(rest string) (*Node, map[string]bool, error) {
// parse environment like statements. Note that this does *not* handle
// variable interpolation, which will be handled in the evaluator.
func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
func parseEnv(rest string) (*Node, map[string]bool, error) {
// This is kind of tricky because we need to support the old
// variant: KEY name value
// as well as the new one: KEY name=value ...
// variant: ENV name value
// as well as the new one: ENV name=value ...
// The trigger to know which one is being used will be whether we hit
// a space or = first. space ==> old, "=" ==> new
@@ -90,7 +86,7 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
if blankOK || len(word) > 0 {
words = append(words, word)
// Look for = and if not there assume
// Look for = and if no there assume
// we're doing the old stuff and
// just read the rest of the line
if !strings.Contains(word, "=") {
@@ -107,15 +103,12 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
quote = ch
blankOK = true
phase = inQuote
continue
}
if ch == '\\' {
if pos+1 == len(rest) {
continue // just skip \ at end
}
// If we're not quoted and we see a \, then always just
// add \ plus the char to the word, even if the char
// is a quote.
word += string(ch)
pos++
ch = rune(rest[pos])
}
@@ -125,36 +118,34 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
if phase == inQuote {
if ch == quote {
phase = inWord
continue
}
// \ is special except for ' quotes - can't escape anything for '
if ch == '\\' && quote != '\'' {
if ch == '\\' {
if pos+1 == len(rest) {
phase = inWord
continue // just skip \ at end
}
pos++
nextCh := rune(rest[pos])
word += string(ch)
ch = nextCh
ch = rune(rest[pos])
}
word += string(ch)
}
}
if len(words) == 0 {
return nil, nil, nil
return nil, nil, fmt.Errorf("ENV must have some arguments")
}
// Old format (KEY name value)
// Old format (ENV name value)
var rootnode *Node
if !strings.Contains(words[0], "=") {
node := &Node{}
rootnode = node
strs := tokenWhitespace.Split(rest, 2)
strs := TOKEN_WHITESPACE.Split(rest, 2)
if len(strs) < 2 {
return nil, nil, fmt.Errorf(key + " must have two arguments")
return nil, nil, fmt.Errorf("ENV must have two arguments")
}
node.Value = strs[0]
@@ -187,25 +178,13 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
return rootnode, nil, nil
}
func parseEnv(rest string) (*Node, map[string]bool, error) {
return parseNameVal(rest, "ENV")
}
func parseLabel(rest string) (*Node, map[string]bool, error) {
return parseNameVal(rest, "LABEL")
}
// parses a whitespace-delimited set of arguments. The result is effectively a
// linked list of string arguments.
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
node := &Node{}
rootnode := node
prevnode := node
for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp
for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp
prevnode = node
node.Value = str
node.Next = &Node{}
@@ -222,9 +201,6 @@ func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error
// parsestring just wraps the string in quotes and returns a working node.
func parseString(rest string) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
n := &Node{}
n.Value = rest
return n, nil, nil
@@ -232,25 +208,24 @@ func parseString(rest string) (*Node, map[string]bool, error) {
// parseJSON converts JSON arrays to an AST.
func parseJSON(rest string) (*Node, map[string]bool, error) {
var myJSON []interface{}
if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil {
var myJson []interface{}
if err := json.Unmarshal([]byte(rest), &myJson); err != nil {
return nil, nil, err
}
var top, prev *Node
for _, str := range myJSON {
s, ok := str.(string)
if !ok {
for _, str := range myJson {
if s, ok := str.(string); !ok {
return nil, nil, errDockerfileNotStringArray
}
node := &Node{Value: s}
if prev == nil {
top = node
} else {
prev.Next = node
node := &Node{Value: s}
if prev == nil {
top = node
} else {
prev.Next = node
}
prev = node
}
prev = node
}
return top, map[string]bool{"json": true}, nil
@@ -260,9 +235,7 @@ func parseJSON(rest string) (*Node, map[string]bool, error) {
// so, passes to parseJSON; if not, quotes the result and returns a single
// node.
func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
rest = strings.TrimSpace(rest)
node, attrs, err := parseJSON(rest)
@@ -279,9 +252,11 @@ func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
}
// parseMaybeJSONToList determines if the argument appears to be a JSON array. If
// so, passes to parseJSON; if not, attempts to parse it as a whitespace
// so, passes to parseJSON; if not, attmpts to parse it as a whitespace
// delimited string.
func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) {
rest = strings.TrimSpace(rest)
node, attrs, err := parseJSON(rest)
if err == nil {
+30 -29
View File
@@ -1,14 +1,13 @@
// Package parser implements a parser and parse tree dumper for Dockerfiles.
// This package implements a parser and parse tree dumper for Dockerfiles.
package parser
import (
"bufio"
"fmt"
"io"
"regexp"
"strings"
"unicode"
"github.com/docker/docker/builder/command"
)
// Node is a structure used to represent a parse tree.
@@ -29,14 +28,13 @@ type Node struct {
Children []*Node // the children of this sexp
Attributes map[string]bool // special attributes for this node
Original string // original line used before parsing
Flags []string // only top Node should have this set
}
var (
dispatch map[string]func(string) (*Node, map[string]bool, error)
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
tokenLineContinuation = regexp.MustCompile(`\\[ \t]*$`)
tokenComment = regexp.MustCompile(`^#.*$`)
dispatch map[string]func(string) (*Node, map[string]bool, error)
TOKEN_WHITESPACE = regexp.MustCompile(`[\t\v\f\r ]+`)
TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\[ \t]*$`)
TOKEN_COMMENT = regexp.MustCompile(`^#.*$`)
)
func init() {
@@ -44,23 +42,23 @@ func init() {
// The command is parsed and mapped to the line parser. The line parser
// recieves the arguments but not the command, and returns an AST after
// reformulating the arguments according to the rules in the parser
// functions. Errors are propagated up by Parse() and the resulting AST can
// functions. Errors are propogated up by Parse() and the resulting AST can
// be incorporated directly into the existing AST as a next.
dispatch = map[string]func(string) (*Node, map[string]bool, error){
command.User: parseString,
command.Onbuild: parseSubCommand,
command.Workdir: parseString,
command.Env: parseEnv,
command.Label: parseLabel,
command.Maintainer: parseString,
command.From: parseString,
command.Add: parseMaybeJSONToList,
command.Copy: parseMaybeJSONToList,
command.Run: parseMaybeJSON,
command.Cmd: parseMaybeJSON,
command.Entrypoint: parseMaybeJSON,
command.Expose: parseStringsWhitespaceDelimited,
command.Volume: parseMaybeJSONToList,
"user": parseString,
"onbuild": parseSubCommand,
"workdir": parseString,
"env": parseEnv,
"maintainer": parseString,
"from": parseString,
"add": parseMaybeJSONToList,
"copy": parseMaybeJSONToList,
"run": parseMaybeJSON,
"cmd": parseMaybeJSON,
"entrypoint": parseMaybeJSON,
"expose": parseStringsWhitespaceDelimited,
"volume": parseMaybeJSONToList,
"insert": parseIgnore,
}
}
@@ -70,16 +68,20 @@ func parseLine(line string) (string, *Node, error) {
return "", nil, nil
}
if tokenLineContinuation.MatchString(line) {
line = tokenLineContinuation.ReplaceAllString(line, "")
if TOKEN_LINE_CONTINUATION.MatchString(line) {
line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
return line, nil, nil
}
cmd, flags, args, err := splitCommand(line)
cmd, args, err := splitCommand(line)
if err != nil {
return "", nil, err
}
if len(args) == 0 {
return "", nil, fmt.Errorf("Instruction %q is empty; cannot continue", cmd)
}
node := &Node{}
node.Value = cmd
@@ -91,13 +93,12 @@ func parseLine(line string) (string, *Node, error) {
node.Next = sexp
node.Attributes = attrs
node.Original = line
node.Flags = flags
return "", node, nil
}
// Parse is the main parse routine.
// It handles an io.ReadWriteCloser and returns the root of the AST.
// The main parse routine. Handles an io.ReadWriteCloser and returns the root
// of the AST.
func Parse(rwc io.Reader) (*Node, error) {
root := &Node{}
scanner := bufio.NewScanner(rwc)
+11 -11
View File
@@ -11,7 +11,7 @@ import (
const testDir = "testfiles"
const negativeTestDir = "testfiles-negative"
func getDirs(t *testing.T, dir string) []string {
func getDirs(t *testing.T, dir string) []os.FileInfo {
f, err := os.Open(dir)
if err != nil {
t.Fatal(err)
@@ -19,7 +19,7 @@ func getDirs(t *testing.T, dir string) []string {
defer f.Close()
dirs, err := f.Readdirnames(0)
dirs, err := f.Readdir(0)
if err != nil {
t.Fatal(err)
}
@@ -29,16 +29,16 @@ func getDirs(t *testing.T, dir string) []string {
func TestTestNegative(t *testing.T) {
for _, dir := range getDirs(t, negativeTestDir) {
dockerfile := filepath.Join(negativeTestDir, dir, "Dockerfile")
dockerfile := filepath.Join(negativeTestDir, dir.Name(), "Dockerfile")
df, err := os.Open(dockerfile)
if err != nil {
t.Fatalf("Dockerfile missing for %s: %v", dir, err)
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
}
_, err = Parse(df)
if err == nil {
t.Fatalf("No error parsing broken dockerfile for %s", dir)
t.Fatalf("No error parsing broken dockerfile for %s", dir.Name())
}
df.Close()
@@ -47,29 +47,29 @@ func TestTestNegative(t *testing.T) {
func TestTestData(t *testing.T) {
for _, dir := range getDirs(t, testDir) {
dockerfile := filepath.Join(testDir, dir, "Dockerfile")
resultfile := filepath.Join(testDir, dir, "result")
dockerfile := filepath.Join(testDir, dir.Name(), "Dockerfile")
resultfile := filepath.Join(testDir, dir.Name(), "result")
df, err := os.Open(dockerfile)
if err != nil {
t.Fatalf("Dockerfile missing for %s: %v", dir, err)
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
}
defer df.Close()
ast, err := Parse(df)
if err != nil {
t.Fatalf("Error parsing %s's dockerfile: %v", dir, err)
t.Fatalf("Error parsing %s's dockerfile: %s", dir.Name(), err.Error())
}
content, err := ioutil.ReadFile(resultfile)
if err != nil {
t.Fatalf("Error reading %s's result file: %v", dir, err)
t.Fatalf("Error reading %s's result file: %s", dir.Name(), err.Error())
}
if ast.Dump()+"\n" != string(content) {
fmt.Fprintln(os.Stderr, "Result:\n"+ast.Dump())
fmt.Fprintln(os.Stderr, "Expected:\n"+string(content))
t.Fatalf("%s: AST dump of dockerfile does not match result", dir)
t.Fatalf("%s: AST dump of dockerfile does not match result", dir.Name())
}
}
}
@@ -0,0 +1,8 @@
FROM dockerfile/rabbitmq
RUN
rabbitmq-plugins enable \
rabbitmq_shovel \
rabbitmq_shovel_management \
rabbitmq_federation \
rabbitmq_federation_management
@@ -0,0 +1,2 @@
<html>
</html>
+1 -1
View File
@@ -19,7 +19,7 @@
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: AppArmor used to mess with privileged mode, but this is no longer
# Note: Apparmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
-8
View File
@@ -7,14 +7,6 @@ ENV name=value\ value2
ENV name="value'quote space'value2"
ENV name='value"double quote"value2'
ENV name=value\ value2 name2=value2\ value3
ENV name="a\"b"
ENV name="a\'b"
ENV name='a\'b'
ENV name='a\'b''
ENV name='a\"b'
ENV name="''"
# don't put anything after the next line - it must be the last line of the
# Dockerfile and it must end with \
ENV name=value \
name1=value1 \
name2="value2a \

Some files were not shown because too many files have changed in this diff Show More