Merge pull request #11635 from jfrazelle/bump_v1.6.0

Bump v1.6.0
This commit is contained in:
Jessie Frazelle
2015-04-16 12:31:02 -07:00
929 changed files with 35509 additions and 44270 deletions
-14
View File
@@ -1,14 +0,0 @@
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 validate-toml
- hack/make.sh binary cross test-unit test-integration-cli test-integration test-docker-py
+2
View File
@@ -29,3 +29,5 @@ docs/GIT_BRANCH
docs/VERSION
docs/GITCOMMIT
docs/changed-files
autogen/
.bashrc
+1 -1
View File
@@ -1,4 +1,4 @@
# Generate AUTHORS: project/generate-authors.sh
# Generate AUTHORS: hack/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:
+1 -1
View File
@@ -1,5 +1,5 @@
# This file lists all individuals having contributed content to the repository.
# For how it is generated, see `project/generate-authors.sh`.
# For how it is generated, see `hack/generate-authors.sh`.
Aanand Prasad <aanand.prasad@gmail.com>
Aaron Feng <aaron.feng@gmail.com>
+20
View File
@@ -1,5 +1,25 @@
# Changelog
## 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
+ basic build cancellation
#### 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)
#### Builder
+178 -147
View File
@@ -1,70 +1,60 @@
# Contributing to Docker
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.
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/sources/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.
## Topics
* [Reporting Security Issues](#reporting-security-issues)
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
* [Reporting Issues](#reporting-issues)
* [Build Environment](#build-environment)
* [Contribution Guidelines](#contribution-guidelines)
* [Reporting Issues](#reporting-other-issues)
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
* [Community Guidelines](#docker-community-guidelines)
## Reporting Security Issues
## Reporting security issues
The Docker maintainers take security very seriously. If you discover a security issue,
please bring it to their attention right away!
The Docker maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
Please send your report privately to [security@docker.com](mailto:security@docker.com),
please **DO NOT** file a public issue.
Please **DO NOT** file a public issue, instead send your report privately to
[security@docker.com](mailto:security@docker.com),
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.
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.
## Design and Cleanup Proposals
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
## Reporting other 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!
When reporting [issues](https://github.com/docker/docker/issues) on
GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc).
Please include:
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:
* 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.
### Template
**Issue Report Template**:
```
Description of problem:
@@ -103,123 +93,165 @@ Additional info:
```
## Build Environment
For instructions on setting up your development environment, please
see our dedicated [dev environment setup
docs](http://docs.docker.com/contributing/devenvironment/).
##Quick contribution tips and guidelines
## Contribution guidelines
This section gives the experienced contributor some tips and guidelines.
### Pull requests are always welcome
###Pull requests are always welcome
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.
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.
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.
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/).
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.
### Design and cleanup proposals
### Discuss your design on the mailing list
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.
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.
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.
### Create issues...
### Talking to other Docker users and contributors
Any significant improvement should be documented as [a GitHub
issue](https://github.com/docker/docker/issues) before anybody
starts working on it.
<table class="tg">
<col width="45%">
<col width="65%">
<tr>
<td>Internet&nbsp;Relay&nbsp;Chat&nbsp;(IRC)</th>
<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="http://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
and so do many other knowledgeable Docker users.
</td>
</tr>
</table>
### ...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 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](https://docs.docker.com/project/test-and-docs/) 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 `docs/README.md` for more
information on building the docs and how they get released.
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).
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 requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
Pull request 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. 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.
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.
Pull requests must be cleanly rebased ontop of master without multiple branches
Pull requests must be cleanly rebased on top 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 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.
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 majory of submissions should have a single commit, so if in doubt: squash
down to one.
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.
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.
Please do not add yourself to the `AUTHORS` file, as it is regenerated
regularly from the Git history.
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.
### 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 [MAINTAINERS](MAINTAINERS)
For more details, see the [MAINTAINERS](MAINTAINERS) page.
### Sign your work
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/)):
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/)):
```
Developer Certificate of Origin
@@ -263,7 +295,7 @@ Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Using your real name (sorry, no pseudonyms or anonymous contributions.)
Use 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`.
@@ -280,45 +312,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 accomodate all timezones.
Anybody can ask for a topic to be discussed prior to the meeting.
There are two monthly meetings taking place on #docker-dev IRC to accomodate all
timezones. Anybody can propose a topic for discussion 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).
They 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). The minutes 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.
@@ -337,20 +369,19 @@ 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.
+20 -15
View File
@@ -73,7 +73,7 @@ RUN cd /usr/src/lxc \
&& ldconfig
# Install Go
ENV GO_VERSION 1.4.1
ENV GO_VERSION 1.4.2
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
@@ -84,10 +84,8 @@ RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
ENV DOCKER_CROSSPLATFORMS \
linux/386 linux/arm \
darwin/amd64 darwin/386 \
freebsd/amd64 freebsd/386 freebsd/arm
# TODO when https://jenkins.dockerproject.com/job/Windows/ is green, add windows back to the list above
# windows/amd64 windows/386
freebsd/amd64 freebsd/386 freebsd/arm \
windows/amd64 windows/386
# (set an explicit GOARM of 5 for maximum compatibility)
ENV GOARM 5
@@ -109,14 +107,8 @@ 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
# 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
# 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
ENV REGISTRY_COMMIT d957768537c5af40e4f4cd96871f7b2bde9e2923
RUN set -x \
&& 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) \
@@ -124,7 +116,7 @@ RUN set -x \
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 aa19d7b6609c6676e8258f6b900dea2eda1dbe95
ENV DOCKER_PY_COMMIT 91985b239764fe54714fa0a93d52aa362357d251
RUN git clone https://github.com/docker/docker-py.git /docker-py \
&& cd /docker-py \
&& git checkout -q $DOCKER_PY_COMMIT
@@ -147,6 +139,16 @@ VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor selinux btrfs_noversion
# Let us use a .bashrc file
RUN ln -sfv $PWD/.bashrc ~/.bashrc
# 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@4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125 \
hello-world:frozen@e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
# Install man page generator
COPY vendor /go/src/github.com/docker/docker/vendor
# (copy vendor/ because go-md2man needs golang.org/x/net)
@@ -156,8 +158,11 @@ RUN set -x \
&& go install -v github.com/cpuguy83/go-md2man
# install toml validator
RUN git clone -b v0.1.0 https://github.com/BurntSushi/toml.git /go/src/github.com/BurntSushi/toml \
&& go install -v github.com/BurntSushi/toml/cmd/tomlv
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
RUN set -x \
&& git clone https://github.com/BurntSushi/toml.git /go/src/github.com/BurntSushi/toml \
&& (cd /go/src/github.com/BurntSushi/toml && git checkout -q $TOMLV_COMMIT) \
&& go install -v github.com/BurntSushi/toml/cmd/tomlv
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]
+34
View File
@@ -0,0 +1,34 @@
# 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
+57 -6
View File
@@ -12,9 +12,9 @@
[Rules.maintainers]
title = "What is a maintainer?"
title = "What is a maintainer?"
text = """
text = """
There are different types of maintainers, with different responsibilities, but
all maintainers have 3 things in common:
@@ -193,13 +193,18 @@ for each.
# They should ask for any editorial change that makes the documentation more
# consistent and easier to understand.
#
# Once documentation is approved, a maintainer should make sure to remove this
# Once documentation is approved (see below), a maintainer should make sure to remove this
# label and add the next one.
close = ""
2-code-review = "requires more code changes"
1-design-review = "raises design concerns"
4-merge = "general case"
# Docs approval
[Rules.review.docs-approval]
# Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of two docs sub-project maintainers.
# If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR).
# Merge
[Rules.review.states.4-merge]
@@ -337,13 +342,14 @@ made through a pull request.
"unclejack",
"crosbymichael",
"erikh",
"estesp",
"icecrime",
"jfrazelle",
"lk4d4",
"tibor",
"vbatts",
"vieux",
"vish"
"vishh"
]
@@ -403,6 +409,8 @@ made through a pull request.
"fredlf",
"james",
"sven",
"spf13",
"mary"
]
[Org.Subsystems.libcontainer]
@@ -421,7 +429,10 @@ made through a pull request.
"dmp42",
"vbatts",
"joffrey",
"samalba"
"samalba",
"sday",
"jlhawn",
"dmcg"
]
[Org.Subsystems."build tools"]
@@ -499,6 +510,16 @@ made through a pull request.
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"
@@ -514,6 +535,16 @@ made through a pull request.
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"
@@ -524,11 +555,31 @@ made through a pull request.
Email = "jess@docker.com"
GitHub = "jfrazelle"
[people.jlhawn]
Name = "Josh Hawn"
Email = "josh.hawn@docker.com"
Github = "jlhawn"
[people.joffrey]
Name = "Joffrey Fuhrer"
Email = "joffrey@docker.com"
Github = "shin-"
[people.lk4d4]
Name = "Alexander Morozov"
Email = "lk4d4@docker.com"
GitHub = "lk4d4"
[people.mary]
Name = "Mary Anthony"
Email = "mary.anthony@docker.com"
GitHub = "moxiegirl"
[people.sday]
Name = "Stephen Day"
Email = "stephen.day@docker.com"
Github = "stevvooe"
[people.shykes]
Name = "Solomon Hykes"
Email = "solomon@docker.com"
@@ -584,7 +635,7 @@ made through a pull request.
Email = "cristian.staretu@gmail.com"
GitHub = "unclejack"
[people.vish]
[people.vishh]
Name = "Vishnu Kannan"
Email = "vishnuk@google.com"
GitHub = "vishh"
+9 -6
View File
@@ -13,10 +13,11 @@ DOCKER_ENVS := \
-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 BINDDIR=. shell` or `make BINDDIR= test`
# to allow `make BIND_DIR=. shell` or `make BIND_DIR= test`
# (default to no bind mount if DOCKER_HOST is set)
BINDDIR := $(if $(DOCKER_HOST),,bundles)
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/docker/docker/$(BINDDIR)")
# 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)")
# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs)
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
@@ -53,7 +54,9 @@ 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
$(DOCKER_RUN_DOCS) -e OPTIONS -e BUILD_ROOT -e DISTRIBUTION_ID \
-v $(CURDIR)/docs/awsconfig:/docs/awsconfig \
"$(DOCKER_DOCS_IMAGE)" ./release.sh
docs-test: docs-build
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh
@@ -83,11 +86,11 @@ 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 "$(AWS_S3_BUCKET)" > docs/AWS_S3_BUCKET
echo "$(GITCOMMIT)" > docs/GITCOMMIT
docker pull docs/base
docker build -t "$(DOCKER_DOCS_IMAGE)" docs
bundles:
+1 -1
View File
@@ -1,5 +1,5 @@
Docker
Copyright 2012-2014 Docker, Inc.
Copyright 2012-2015 Docker, Inc.
This product includes software developed at Docker, Inc. (http://www.docker.com).
+11 -7
View File
@@ -18,7 +18,7 @@ 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/theme/mkdocs/images/docker-logo-compressed.png "Docker")
![Docker L](docs/sources/static_files/docker-logo-compressed.png "Docker")
## Security Disclosure
@@ -173,6 +173,8 @@ Under the hood, Docker is built on the following components:
[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.
* 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)
Contributing to Docker
======================
@@ -181,12 +183,14 @@ Contributing to 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.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).
started contributing code or documentation.](https://docs.docker.com/project/who-written-for/).
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.com](https://master.dockerproject.com).
They are updated with each commit merged into the master branch.
@@ -231,12 +235,12 @@ Docker platform to broaden its application and utility.
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 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, aka Fig](https://github.com/docker/fig):
Multi-container application management
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
Define and run multi-container apps
+1 -1
View File
@@ -1 +1 @@
1.5.0
1.6.0
-2
View File
@@ -1,2 +0,0 @@
Victor Vieux <vieux@docker.com> (@vieux)
Jessie Frazelle <jess@docker.com> (@jfrazelle)
+11 -14
View File
@@ -14,6 +14,7 @@ import (
"text/template"
"time"
"github.com/docker/docker/pkg/homedir"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
@@ -92,10 +93,13 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo
flags := flag.NewFlagSet(name, errorHandling)
flags.Usage = func() {
options := ""
if flags.FlagCountUndeprecated() > 0 {
options = "[OPTIONS] "
if signature != "" {
signature = " " + signature
}
fmt.Fprintf(cli.out, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description)
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)
@@ -104,7 +108,7 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo
}
func (cli *DockerCli) LoadConfigFile() (err error) {
cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
cli.configFile, err = registry.LoadConfig(homedir.Get())
if err != nil {
fmt.Fprintf(cli.err, "WARNING: %s\n", err)
}
@@ -133,19 +137,12 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
if tlsConfig != nil {
scheme = "https"
}
if in != nil {
if file, ok := in.(*os.File); ok {
inFd = file.Fd()
isTerminalIn = term.IsTerminal(inFd)
}
inFd, isTerminalIn = term.GetFdInfo(in)
}
if out != nil {
if file, ok := out.(*os.File); ok {
outFd = file.Fd()
isTerminalOut = term.IsTerminal(outFd)
}
outFd, isTerminalOut = term.GetFdInfo(out)
}
if err == nil {
@@ -154,7 +151,6 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
// The transport is created here for reuse during the client session
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: tlsConfig,
}
@@ -167,6 +163,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
return net.DialTimeout(proto, addr, timeout)
}
} else {
tr.Proxy = http.ProxyFromEnvironment
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
}
+345 -221
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -15,7 +15,7 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
+141 -103
View File
@@ -12,12 +12,14 @@ import (
"net/url"
"os"
gosignal "os/signal"
"runtime"
"strconv"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/stdcopy"
@@ -54,124 +56,144 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
return params, nil
}
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
params, err := cli.encodeData(data)
if err != nil {
return nil, -1, err
}
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
if err != nil {
return nil, -1, err
}
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 {
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) {
expectedPayload := (method == "POST" || method == "PUT")
if expectedPayload && 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
return nil, "", -1, 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)
statusCode := -1
if resp != nil {
statusCode = resp.StatusCode
}
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?")
return nil, "", statusCode, ErrConnectionRefused
}
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
if cli.tlsConfig == nil {
return nil, "", statusCode, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
}
return nil, "", statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
}
if statusCode < 200 || statusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
return nil, "", statusCode, err
}
if len(body) == 0 {
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
return nil, "", statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL)
}
return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
return nil, "", statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
}
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
return resp.Body, resp.Header.Get("Content-Type"), statusCode, 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 registry.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
body, contentType, statusCode, 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(body, contentType, 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(), "status code 401") {
statusCode = http.StatusUnauthorized
}
}
return body, statusCode, err
}
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(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 = cli.configFile.ResolveAuthConfig(index)
return cmdAttempt(authConfig)
}
return body, statusCode, err
}
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
params, err := cli.encodeData(data)
if err != nil {
return nil, -1, err
}
if data != nil {
if headers == nil {
headers = make(map[string][]string)
}
headers["Content-Type"] = []string{"application/json"}
}
body, _, statusCode, err := cli.clientRequest(method, path, params, headers)
return body, statusCode, err
}
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 {
body, contentType, _, err := cli.clientRequest(method, path, in, headers)
if err != nil {
return err
}
return cli.streamBody(body, contentType, setRawTerminal, stdout, stderr)
}
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error {
defer body.Close()
if api.MatchesContentType(contentType, "application/json") {
return utils.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
}
if stdout != nil || stderr != nil {
// When TTY is ON, use regular copy
var err error
if setRawTerminal {
_, err = io.Copy(stdout, resp.Body)
_, err = io.Copy(stdout, body)
} else {
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
_, err = stdcopy.StdCopy(stdout, stderr, body)
}
log.Debugf("[stream] End of stdout")
return err
@@ -195,13 +217,13 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
path = "/exec/" + id + "/resize?"
}
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
log.Debugf("Error resize: %s", err)
}
}
func waitForExit(cli *DockerCli, containerId string) (int, error) {
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
func waitForExit(cli *DockerCli, containerID string) (int, error) {
stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
if err != nil {
return -1, err
}
@@ -215,8 +237,8 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
// 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) {
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != ErrConnectionRefused {
@@ -236,8 +258,8 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
// 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) {
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != ErrConnectionRefused {
@@ -257,13 +279,29 @@ func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
cli.resizeTty(id, isExec)
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
go func() {
for _ = range sigchan {
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)
}
}()
}
return nil
}
+66 -9
View File
@@ -15,7 +15,7 @@ import (
)
const (
APIVERSION version.Version = "1.17"
APIVERSION version.Version = "1.18"
DEFAULTHTTPHOST = "127.0.0.1"
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
DefaultDockerfileName string = "Dockerfile"
@@ -29,25 +29,82 @@ func ValidateHost(val string) (string, error) {
return host, nil
}
//TODO remove, used on < 1.5 in getContainersJSON
// TODO remove, used on < 1.5 in getContainersJSON
func DisplayablePorts(ports *engine.Table) string {
result := []string{}
ports.SetKey("PublicPort")
var (
result = []string{}
hostMappings = []string{}
firstInGroupMap map[string]int
lastInGroupMap map[string]int
)
firstInGroupMap = make(map[string]int)
lastInGroupMap = make(map[string]int)
ports.SetKey("PrivatePort")
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")))
var (
current = port.GetInt("PrivatePort")
portKey = port.Get("Type")
firstInGroup int
lastInGroup int
)
if port.Get("IP") != "" {
if port.GetInt("PublicPort") != current {
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
continue
}
portKey = fmt.Sprintf("%s/%s", port.Get("IP"), port.Get("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
}
for portKey, firstInGroup := range firstInGroupMap {
result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
}
result = append(result, hostMappings...)
return strings.Join(result, ", ")
}
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]
}
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)
}
func MatchesContentType(contentType, expectedType string) bool {
mimetype, _, err := mime.ParseMediaType(contentType)
if err != nil {
log.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
log.Errorf("Error parsing media type: %s error: %v", contentType, err)
}
return err == nil && mimetype == expectedType
}
-2
View File
@@ -1,2 +0,0 @@
Victor Vieux <vieux@docker.com> (@vieux)
# Johan Euphrosine <proppy@google.com> (@proppy)
+87 -126
View File
@@ -16,7 +16,6 @@ import (
"os"
"strconv"
"strings"
"syscall"
"crypto/tls"
"crypto/x509"
@@ -27,19 +26,19 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/daemon/networkdriver/portallocator"
"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/listenbuffer"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/systemd"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
var (
activationLock chan struct{}
activationLock chan struct{} = make(chan struct{})
)
type HttpServer struct {
@@ -135,17 +134,27 @@ func httpError(w http.ResponseWriter, err error) {
}
if err != nil {
log.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error())
log.Errorf("HTTP Error: statusCode=%d %v", statusCode, err)
http.Error(w, err.Error(), statusCode)
}
}
func writeJSON(w http.ResponseWriter, code int, v engine.Env) error {
// writeJSONEnv writes the engine.Env values to the http response stream as a
// json encoded body.
func writeJSONEnv(w http.ResponseWriter, code int, v engine.Env) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
return v.Encode(w)
}
// writeJSON writes the value v to the http response stream as json with standard
// json encoding.
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
return json.NewEncoder(w).Encode(v)
}
func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) {
w.Header().Set("Content-Type", "application/json")
if flush {
@@ -183,7 +192,7 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter
if status := engine.Tail(stdoutBuffer, 1); status != "" {
var env engine.Env
env.Set("Status", status)
return writeJSON(w, http.StatusOK, env)
return writeJSONEnv(w, http.StatusOK, env)
}
w.WriteHeader(http.StatusNoContent)
return nil
@@ -518,6 +527,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
job.Setenv("tag", r.Form.Get("tag"))
job.Setenv("author", r.Form.Get("author"))
job.Setenv("comment", r.Form.Get("comment"))
job.SetenvList("changes", r.Form["changes"])
job.SetenvSubEnv("config", &config)
job.Stdout.Add(stdoutBuffer)
@@ -525,7 +535,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
return err
}
env.Set("Id", engine.Tail(stdoutBuffer, 1))
return writeJSON(w, http.StatusCreated, env)
return writeJSONEnv(w, http.StatusCreated, env)
}
// Creates an image from Pull or from Import
@@ -570,6 +580,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon
}
job = eng.Job("import", r.Form.Get("fromSrc"), repo, tag)
job.Stdin.Add(r.Body)
job.SetenvList("changes", r.Form["changes"])
}
if version.GreaterThan("1.0") {
@@ -703,18 +714,16 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
if err := parseForm(r); err != nil {
return nil
}
if err := checkForJson(r); err != nil {
return err
}
var (
out engine.Env
job = eng.Job("create", r.Form.Get("name"))
outWarnings []string
stdoutBuffer = bytes.NewBuffer(nil)
warnings = bytes.NewBuffer(nil)
)
if err := checkForJson(r); err != nil {
return err
}
if err := job.DecodeEnv(r.Body); err != nil {
return err
}
@@ -730,10 +739,10 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
for scanner.Scan() {
outWarnings = append(outWarnings, scanner.Text())
}
out.Set("Id", engine.Tail(stdoutBuffer, 1))
out.SetList("Warnings", outWarnings)
return writeJSON(w, http.StatusCreated, out)
return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{
ID: engine.Tail(stdoutBuffer, 1),
Warnings: outWarnings,
})
}
func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -876,7 +885,7 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp
}
env.Set("StatusCode", engine.Tail(stdoutBuffer, 1))
return writeJSON(w, http.StatusOK, env)
return writeJSONEnv(w, http.StatusOK, env)
}
func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -1073,6 +1082,24 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
job.Setenv("forcerm", r.FormValue("forcerm"))
job.SetenvJson("authConfig", authConfig)
job.SetenvJson("configFile", configFile)
job.Setenv("memswap", r.FormValue("memswap"))
job.Setenv("memory", r.FormValue("memory"))
job.Setenv("cpusetcpus", r.FormValue("cpusetcpus"))
job.Setenv("cpushares", r.FormValue("cpushares"))
// Job cancellation. Note: not all job types support this.
if closeNotifier, ok := w.(http.CloseNotifier); ok {
finished := make(chan struct{})
defer close(finished)
go func() {
select {
case <-finished:
case <-closeNotifier.CloseNotify():
log.Infof("Client disconnected, cancelling job: %v", job)
job.Cancel()
}
}()
}
if err := job.Run(); err != nil {
if !job.Stdout.Used() {
@@ -1113,8 +1140,8 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
job.Stdout.Add(w)
w.Header().Set("Content-Type", "application/x-tar")
if err := job.Run(); err != nil {
log.Errorf("%s", err.Error())
if strings.Contains(strings.ToLower(err.Error()), "no such container") {
log.Errorf("%v", err)
if strings.Contains(strings.ToLower(err.Error()), "no such id") {
w.WriteHeader(http.StatusNotFound)
} else if strings.Contains(err.Error(), "no such file or directory") {
return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"])
@@ -1147,7 +1174,7 @@ func postContainerExecCreate(eng *engine.Engine, version version.Version, w http
// Return the ID
out.Set("Id", engine.Tail(stdoutBuffer, 1))
return writeJSON(w, http.StatusCreated, out)
return writeJSONEnv(w, http.StatusCreated, out)
}
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
@@ -1219,8 +1246,9 @@ func optionsHandler(eng *engine.Engine, version version.Version, w http.Response
w.WriteHeader(http.StatusOK)
return nil
}
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "*")
func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
log.Debugf("CORS header is enabled and set to: %s", corsHeaders)
w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
}
@@ -1230,7 +1258,7 @@ func ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r
return err
}
func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion version.Version) http.HandlerFunc {
func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// log the request
log.Debugf("Calling %s %s", localMethod, localRoute)
@@ -1249,8 +1277,8 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local
if version == "" {
version = api.APIVERSION
}
if enableCors {
writeCorsHeaders(w, r)
if corsHeaders != "" {
writeCorsHeaders(w, r, corsHeaders)
}
if version.GreaterThan(api.APIVERSION) {
@@ -1292,7 +1320,8 @@ func AttachProfiler(router *mux.Router) {
router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
}
func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion string) *mux.Router {
// we keep enableCors just for legacy usage, need to be removed in the future
func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders string, dockerVersion string) *mux.Router {
r := mux.NewRouter()
if os.Getenv("DEBUG") != "" {
AttachProfiler(r)
@@ -1354,6 +1383,12 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
},
}
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
// otherwise, all head values will be passed to HTTP handler
if corsHeaders == "" && enableCors {
corsHeaders = "*"
}
for method, routes := range m {
for route, fct := range routes {
log.Debugf("Registering %s, %s", method, route)
@@ -1363,7 +1398,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
localMethod := method
// build the handler function
f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, version.Version(dockerVersion))
f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, corsHeaders, version.Version(dockerVersion))
// add the new route
if localRoute == "" {
@@ -1382,49 +1417,12 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
// FIXME: refactor this to be part of Server and not require re-creating a new
// router each time. This requires first moving ListenAndServe into Server.
func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) {
router := createRouter(eng, false, true, "")
router := createRouter(eng, false, true, "", "")
// Insert APIVERSION into the request as a convenience
req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path)
router.ServeHTTP(w, req)
}
// serveFd creates an http.Server and sets it up to serve given a socket activated
// argument.
func serveFd(addr string, job *engine.Job) error {
r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
ls, e := systemd.ListenFD(addr)
if e != nil {
return e
}
chErrors := make(chan error, len(ls))
// We don't want to start serving on these sockets until the
// daemon is initialized and installed. Otherwise required handlers
// won't be ready.
<-activationLock
// Since ListenFD will return one or more sockets we have
// to create a go func to spawn off multiple serves
for i := range ls {
listener := ls[i]
go func() {
httpSrv := http.Server{Handler: r}
chErrors <- httpSrv.Serve(listener)
}()
}
for i := 0; i < len(ls); i++ {
err := <-chErrors
if err != nil {
return err
}
}
return nil
}
func lookupGidByName(nameOrGid string) (int, error) {
groupFile, err := user.GetGroupPath()
if err != nil {
@@ -1439,13 +1437,21 @@ func lookupGidByName(nameOrGid string) (int, error) {
if groups != nil && len(groups) > 0 {
return groups[0].Gid, nil
}
gid, err := strconv.Atoi(nameOrGid)
if err == nil {
log.Warnf("Could not find GID %d", gid)
return gid, nil
}
return -1, fmt.Errorf("Group %s not found", nameOrGid)
}
func setupTls(cert, key, ca string, l net.Listener) (net.Listener, error) {
tlsCert, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
if os.IsNotExist(err) {
return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", cert, key, err)
}
return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %q. Make sure the key is encrypted.",
cert, key, err)
}
tlsConfig := &tls.Config{
@@ -1459,7 +1465,7 @@ func setupTls(cert, key, ca string, l net.Listener) (net.Listener, error) {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(ca)
if err != nil {
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
return nil, fmt.Errorf("Could not read CA certificate: %v", err)
}
certPool.AppendCertsFromPEM(file)
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
@@ -1502,31 +1508,6 @@ func setSocketGroup(addr, group string) error {
return nil
}
func setupUnixHttp(addr string, job *engine.Job) (*HttpServer, error) {
r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
return nil, err
}
mask := syscall.Umask(0777)
defer syscall.Umask(mask)
l, err := newListener("unix", addr, job.GetenvBool("BufferRequests"))
if err != nil {
return nil, err
}
if err := setSocketGroup(addr, job.Getenv("SocketGroup")); err != nil {
return nil, err
}
if err := os.Chmod(addr, 0660); err != nil {
return nil, err
}
return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil
}
func allocateDaemonPort(addr string) error {
host, port, err := net.SplitHostPort(addr)
if err != nil {
@@ -1546,7 +1527,7 @@ func allocateDaemonPort(addr string) error {
}
for _, hostIP := range hostIPs {
if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil {
if _, err := bridge.RequestPort(hostIP, "tcp", intPort); err != nil {
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
}
}
@@ -1554,11 +1535,11 @@ func allocateDaemonPort(addr string) error {
}
func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) {
if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") {
log.Infof("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
if !job.GetenvBool("TlsVerify") {
log.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version"))
l, err := newListener("tcp", addr, job.GetenvBool("BufferRequests"))
if err != nil {
@@ -1582,21 +1563,6 @@ func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) {
return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil
}
// NewServer sets up the required Server and does protocol specific checking.
func NewServer(proto, addr string, job *engine.Job) (Server, error) {
// Basic error and sanity checking
switch proto {
case "fd":
return nil, serveFd(addr, job)
case "tcp":
return setupTcpHttp(addr, job)
case "unix":
return setupUnixHttp(addr, job)
default:
return nil, fmt.Errorf("Invalid protocol format.")
}
}
type Server interface {
Serve() error
Close() error
@@ -1612,7 +1578,6 @@ func ServeApi(job *engine.Job) engine.Status {
protoAddrs = job.Args
chErrors = make(chan error, len(protoAddrs))
)
activationLock = make(chan struct{})
for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
@@ -1626,7 +1591,15 @@ func ServeApi(job *engine.Job) engine.Status {
chErrors <- err
return
}
chErrors <- srv.Serve()
job.Eng.OnShutdown(func() {
if err := srv.Close(); err != nil {
log.Error(err)
}
})
if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
err = nil
}
chErrors <- err
}()
}
@@ -1639,15 +1612,3 @@ func ServeApi(job *engine.Job) engine.Status {
return engine.StatusOK
}
func AcceptConnections(job *engine.Job) engine.Status {
// Tell the init daemon we are accepting requests
go systemd.SdNotify("READY=1")
// close the lock so the listeners start accepting connections
if activationLock != nil {
close(activationLock)
}
return engine.StatusOK
}
+105
View File
@@ -0,0 +1,105 @@
// +build linux
package server
import (
"fmt"
"net/http"
"os"
"syscall"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/systemd"
)
// NewServer sets up the required Server and does protocol specific checking.
func NewServer(proto, addr string, job *engine.Job) (Server, error) {
// Basic error and sanity checking
switch proto {
case "fd":
return nil, serveFd(addr, job)
case "tcp":
return setupTcpHttp(addr, job)
case "unix":
return setupUnixHttp(addr, job)
default:
return nil, fmt.Errorf("Invalid protocol format.")
}
}
func setupUnixHttp(addr string, job *engine.Job) (*HttpServer, error) {
r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version"))
if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
return nil, err
}
mask := syscall.Umask(0777)
defer syscall.Umask(mask)
l, err := newListener("unix", addr, job.GetenvBool("BufferRequests"))
if err != nil {
return nil, err
}
if err := setSocketGroup(addr, job.Getenv("SocketGroup")); err != nil {
return nil, err
}
if err := os.Chmod(addr, 0660); err != nil {
return nil, err
}
return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil
}
// serveFd creates an http.Server and sets it up to serve given a socket activated
// argument.
func serveFd(addr string, job *engine.Job) error {
r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version"))
ls, e := systemd.ListenFD(addr)
if e != nil {
return e
}
chErrors := make(chan error, len(ls))
// We don't want to start serving on these sockets until the
// daemon is initialized and installed. Otherwise required handlers
// won't be ready.
<-activationLock
// Since ListenFD will return one or more sockets we have
// to create a go func to spawn off multiple serves
for i := range ls {
listener := ls[i]
go func() {
httpSrv := http.Server{Handler: r}
chErrors <- httpSrv.Serve(listener)
}()
}
for i := 0; i < len(ls); i++ {
err := <-chErrors
if err != nil {
return err
}
}
return nil
}
// Called through eng.Job("acceptconnections")
func AcceptConnections(job *engine.Job) engine.Status {
// Tell the init daemon we are accepting requests
go systemd.SdNotify("READY=1")
// close the lock so the listeners start accepting connections
select {
case <-activationLock:
default:
close(activationLock)
}
return engine.StatusOK
}
+31
View File
@@ -0,0 +1,31 @@
// +build windows
package server
import (
"fmt"
"github.com/docker/docker/engine"
)
// NewServer sets up the required Server and does protocol specific checking.
func NewServer(proto, addr string, job *engine.Job) (Server, error) {
// Basic error and sanity checking
switch proto {
case "tcp":
return setupTcpHttp(addr, job)
default:
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
}
}
// Called through eng.Job("acceptconnections")
func AcceptConnections(job *engine.Job) engine.Status {
// close the lock so the listeners start accepting connections
if activationLock != nil {
close(activationLock)
}
return engine.StatusOK
}
+1 -1
View File
@@ -1,6 +1,6 @@
// This package is used for API stability in the types and response to the
// consumers of the API stats endpoint.
package stats
package types
import "time"
+11
View File
@@ -0,0 +1,11 @@
package types
// 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"`
}
-3
View File
@@ -1,3 +0,0 @@
Tibor Vass <teabee89@gmail.com> (@tiborvass)
Erik Hollensbe <github@hollensbe.org> (@erikh)
Doug Davis <dug@us.ibm.com> (@duglin)
+39
View File
@@ -0,0 +1,39 @@
// This package contains the set of Dockerfile commands.
package command
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"
Insert = "insert"
)
// 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: {},
Insert: {},
}
+59 -8
View File
@@ -39,7 +39,7 @@ func nullDispatch(b *Builder, args []string, attributes map[string]bool, origina
//
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) == 0 {
return fmt.Errorf("ENV is missing arguments")
return fmt.Errorf("ENV requires at least one argument")
}
if len(args)%2 != 0 {
@@ -78,13 +78,44 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
// Sets the maintainer metadata.
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) != 1 {
return fmt.Errorf("MAINTAINER requires only one argument")
return fmt.Errorf("MAINTAINER requires exactly 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")
}
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
@@ -159,6 +190,10 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
// 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")
}
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
switch triggerInstruction {
case "ONBUILD":
@@ -209,8 +244,8 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
args = handleJsonArgs(args, attributes)
if len(args) == 1 {
args = append([]string{"/bin/sh", "-c"}, args[0])
if !attributes["json"] {
args = append([]string{"/bin/sh", "-c"}, args...)
}
runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
@@ -272,7 +307,7 @@ func cmd(b *Builder, args []string, attributes map[string]bool, original string)
b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...)
}
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %v", b.Config.Cmd)); err != nil {
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", b.Config.Cmd)); err != nil {
return err
}
@@ -312,7 +347,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 %v", b.Config.Entrypoint)); err != nil {
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.Config.Entrypoint)); err != nil {
return err
}
@@ -327,15 +362,27 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
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 b.Config.ExposedPorts == nil {
b.Config.ExposedPorts = make(nat.PortSet)
}
ports, _, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
ports, bindingMap, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
if err != nil {
return err
}
for _, bindings := range bindingMap {
if bindings[0].HostIp != "" || bindings[0].HostPort != "" {
fmt.Fprintf(b.ErrStream, " ---> Using Dockerfile's EXPOSE instruction"+
" to map host ports to container ports (ip:hostPort:containerPort) is deprecated.\n"+
" Please use -p to publish the ports.\n")
}
}
// instead of using ports directly, we build a list of ports and sort it so
// the order is consistent. This prevents cache burst where map ordering
// changes between builds
@@ -373,13 +420,17 @@ func user(b *Builder, args []string, attributes map[string]bool, original string
//
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
if len(args) == 0 {
return fmt.Errorf("Volume cannot be empty")
return fmt.Errorf("VOLUME requires at least one argument")
}
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 {
+79 -30
View File
@@ -28,9 +28,12 @@ import (
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/builder/command"
"github.com/docker/docker/builder/parser"
"github.com/docker/docker/daemon"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/tarsum"
@@ -45,33 +48,35 @@ var (
// Environment variable interpolation will happen on these statements only.
var replaceEnvAllowed = map[string]struct{}{
"env": {},
"add": {},
"copy": {},
"workdir": {},
"expose": {},
"volume": {},
"user": {},
command.Env: {},
command.Label: {},
command.Add: {},
command.Copy: {},
command.Workdir: {},
command.Expose: {},
command.Volume: {},
command.User: {},
}
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{
"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,
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,
command.Insert: insert,
}
}
@@ -88,12 +93,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
AuthConfig *registry.AuthConfig
AuthConfigFile *registry.ConfigFile
@@ -114,6 +125,14 @@ type Builder struct {
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
cpuShares int64
memory int64
memorySwap int64
cancelled <-chan struct{} // When closed, job was cancelled.
}
// Run the builder with the context. This is the lynchpin of this package. This
@@ -139,38 +158,63 @@ func (b *Builder) Run(context io.Reader) (string, error) {
}
}()
if err := b.readDockerfile(b.dockerfileName); err != nil {
if err := b.readDockerfile(); 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:
log.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", utils.TruncateID(b.image))
fmt.Fprintf(b.OutStream, " ---> %s\n", common.TruncateID(b.image))
if b.Remove {
b.clearTmp()
}
}
if b.image == "" {
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
}
fmt.Fprintf(b.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
fmt.Fprintf(b.OutStream, "Successfully built %s\n", common.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(origFile string) error {
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)
}
}
}
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)
@@ -240,6 +284,9 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
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
@@ -265,7 +312,11 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
var str string
str = ast.Value
if _, ok := replaceEnvAllowed[cmd]; ok {
str = b.replaceEnv(ast.Value)
var err error
str, err = ProcessWord(ast.Value, b.Config.Env)
if err != nil {
return err
}
}
strList[i+l] = str
msgList[i] = ast.Value
@@ -281,7 +332,5 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
return f(b, strList, attrs, original)
}
fmt.Fprintf(b.ErrStream, "# Skipping unknown instruction %s\n", strings.ToUpper(cmd))
return nil
return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(cmd))
}
+72 -31
View File
@@ -25,13 +25,16 @@ import (
imagepkg "github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/progressreader"
"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"
)
@@ -59,6 +62,9 @@ func (b *Builder) readContext(context io.Reader) error {
}
func (b *Builder) commit(id string, autoCmd []string, comment string) error {
if b.disableCommit {
return nil
}
if b.image == "" && !b.noBaseImage {
return fmt.Errorf("Please provide a source image with `from` prior to commit")
}
@@ -87,9 +93,9 @@ func (b *Builder) commit(id string, autoCmd []string, comment string) error {
}
defer container.Unmount()
}
container := b.Daemon.Get(id)
if container == nil {
return fmt.Errorf("An error occured while creating the container")
container, err := b.Daemon.Get(id)
if err != nil {
return err
}
// Note: Actually copy the struct
@@ -183,8 +189,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp
if err != nil {
return err
}
// If we do not have at least one hash, never use the cache
if hit && b.UtilizeCache {
if hit {
return nil
}
@@ -264,7 +270,15 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
}
// Download and dump result to tmp file
if _, err := io.Copy(tmpFile, utils.ProgressReader(resp.Body, int(resp.ContentLength), b.OutOld, b.StreamFormatter, true, "", "Downloading")); err != nil {
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 {
tmpFile.Close()
return err
}
@@ -498,19 +512,24 @@ func (b *Builder) processImageFrom(img *imagepkg.Image) error {
// `(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 {
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")
}
if !b.UtilizeCache || b.cacheBusted {
return false, nil
}
return false, nil
cache, err := b.Daemon.ImageGetCached(b.image, b.Config)
if err != nil {
return false, err
}
if cache == nil {
log.Debugf("[BUILDER] Cache miss")
b.cacheBusted = true
return false, nil
}
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
log.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return true, nil
}
func (b *Builder) create() (*daemon.Container, error) {
@@ -519,10 +538,17 @@ func (b *Builder) create() (*daemon.Container, error) {
}
b.Config.Image = b.image
hostConfig := &runconfig.HostConfig{
CpuShares: b.cpuShares,
CpusetCpus: b.cpuSetCpus,
Memory: b.memory,
MemorySwap: b.memorySwap,
}
config := *b.Config
// Create the container
c, warnings, err := b.Daemon.Create(b.Config, nil, "")
c, warnings, err := b.Daemon.Create(b.Config, hostConfig, "")
if err != nil {
return nil, err
}
@@ -531,7 +557,7 @@ func (b *Builder) create() (*daemon.Container, error) {
}
b.TmpContainers[c.ID] = struct{}{}
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", common.TruncateID(c.ID))
if len(config.Cmd) > 0 {
// override the entry point that may have been picked up from the base image
@@ -545,19 +571,30 @@ func (b *Builder) create() (*daemon.Container, error) {
}
func (b *Builder) run(c *daemon.Container) error {
var errCh chan error
if b.Verbose {
errCh = b.Daemon.Attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, b.OutStream, b.ErrStream)
}
//start the container
if err := c.Start(); err != nil {
return err
}
finished := make(chan struct{})
defer close(finished)
go func() {
select {
case <-b.cancelled:
log.Debugln("Build cancelled, killing container:", c.ID)
c.Kill()
case <-finished:
}
}()
if b.Verbose {
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 {
// Block on reading output from container, stop on err or chan closed
if err := <-errCh; err != nil {
return err
}
}
@@ -710,13 +747,17 @@ func fixPermissions(source, destination string, uid, gid int, destExisted bool)
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())
tmp, err := b.Daemon.Get(c)
if err != nil {
fmt.Fprint(b.OutStream, err.Error())
}
if err := b.Daemon.Rm(tmp); err != nil {
fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %v\n", common.TruncateID(c), err)
return
}
b.Daemon.DeleteVolumes(tmp.VolumePaths())
delete(b.TmpContainers, c)
fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", common.TruncateID(c))
}
}
+79 -4
View File
@@ -1,12 +1,16 @@
package builder
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"github.com/docker/docker/api"
"github.com/docker/docker/builder/parser"
"github.com/docker/docker/daemon"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
@@ -14,9 +18,22 @@ import (
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
// whitelist of commands allowed for a commit/import
var validCommitCommands = map[string]bool{
"entrypoint": true,
"cmd": true,
"user": true,
"workdir": true,
"env": true,
"volume": true,
"expose": true,
"onbuild": true,
}
type BuilderJob struct {
Engine *engine.Engine
Daemon *daemon.Daemon
@@ -24,6 +41,7 @@ type BuilderJob struct {
func (b *BuilderJob) Install() {
b.Engine.Register("build", b.CmdBuild)
b.Engine.Register("build_config", b.CmdBuildConfig)
}
func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
@@ -39,6 +57,10 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
rm = job.GetenvBool("rm")
forceRm = job.GetenvBool("forcerm")
pull = job.GetenvBool("pull")
memory = job.GetenvInt64("memory")
memorySwap = job.GetenvInt64("memswap")
cpuShares = job.GetenvInt64("cpushares")
cpuSetCpus = job.Getenv("cpusetcpus")
authConfig = &registry.AuthConfig{}
configFile = &registry.ConfigFile{}
tag string
@@ -60,10 +82,6 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
}
}
if dockerfileName == "" {
dockerfileName = api.DefaultDockerfileName
}
if remoteURL == "" {
context = ioutil.NopCloser(job.Stdin)
} else if urlutil.IsGitURL(remoteURL) {
@@ -95,6 +113,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
if err != nil {
return job.Error(err)
}
// When we're downloading just a Dockerfile put it in
// the default name - don't allow the client to move/specify it
dockerfileName = api.DefaultDockerfileName
c, err := archive.Generate(dockerfileName, string(dockerFile))
if err != nil {
return job.Error(err)
@@ -126,6 +149,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
AuthConfig: authConfig,
AuthConfigFile: configFile,
dockerfileName: dockerfileName,
cpuShares: cpuShares,
cpuSetCpus: cpuSetCpus,
memory: memory,
memorySwap: memorySwap,
cancelled: job.WaitCancelled(),
}
id, err := builder.Run(context)
@@ -138,3 +166,50 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
}
return engine.StatusOK
}
func (b *BuilderJob) CmdBuildConfig(job *engine.Job) engine.Status {
if len(job.Args) != 0 {
return job.Errorf("Usage: %s\n", job.Name)
}
var (
changes = job.GetenvList("changes")
newConfig runconfig.Config
)
if err := job.GetenvJson("config", &newConfig); err != nil {
return job.Error(err)
}
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
if err != nil {
return job.Error(err)
}
// ensure that the commands are valid
for _, n := range ast.Children {
if !validCommitCommands[n.Value] {
return job.Errorf("%s is not a valid change command", n.Value)
}
}
builder := &Builder{
Daemon: b.Daemon,
Engine: b.Engine,
Config: &newConfig,
OutStream: ioutil.Discard,
ErrStream: ioutil.Discard,
disableCommit: true,
}
for i, n := range ast.Children {
if err := builder.dispatch(i, n); err != nil {
return job.Error(err)
}
}
if err := json.NewEncoder(job.Stdout).Encode(builder.Config); err != nil {
return job.Error(err)
}
return engine.StatusOK
}
+38 -14
View File
@@ -30,6 +30,10 @@ 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
@@ -40,10 +44,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 parseEnv(rest string) (*Node, map[string]bool, error) {
func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
// This is kind of tricky because we need to support the old
// variant: ENV name value
// as well as the new one: ENV name=value ...
// variant: KEY name value
// as well as the new one: KEY name=value ...
// The trigger to know which one is being used will be whether we hit
// a space or = first. space ==> old, "=" ==> new
@@ -86,7 +90,7 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
if blankOK || len(word) > 0 {
words = append(words, word)
// Look for = and if no there assume
// Look for = and if not there assume
// we're doing the old stuff and
// just read the rest of the line
if !strings.Contains(word, "=") {
@@ -103,12 +107,15 @@ func parseEnv(rest 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])
}
@@ -118,25 +125,27 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
if phase == inQuote {
if ch == quote {
phase = inWord
continue
}
if ch == '\\' {
// \ is special except for ' quotes - can't escape anything for '
if ch == '\\' && quote != '\'' {
if pos+1 == len(rest) {
phase = inWord
continue // just skip \ at end
}
pos++
ch = rune(rest[pos])
nextCh := rune(rest[pos])
word += string(ch)
ch = nextCh
}
word += string(ch)
}
}
if len(words) == 0 {
return nil, nil, fmt.Errorf("ENV must have some arguments")
return nil, nil, nil
}
// Old format (ENV name value)
// Old format (KEY name value)
var rootnode *Node
if !strings.Contains(words[0], "=") {
@@ -145,7 +154,7 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
strs := TOKEN_WHITESPACE.Split(rest, 2)
if len(strs) < 2 {
return nil, nil, fmt.Errorf("ENV must have two arguments")
return nil, nil, fmt.Errorf(key + " must have two arguments")
}
node.Value = strs[0]
@@ -178,9 +187,21 @@ func parseEnv(rest 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
@@ -201,6 +222,9 @@ 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
@@ -235,7 +259,9 @@ 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) {
rest = strings.TrimSpace(rest)
if rest == "" {
return nil, nil, nil
}
node, attrs, err := parseJSON(rest)
@@ -255,8 +281,6 @@ func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
// 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 {
+18 -20
View File
@@ -3,11 +3,12 @@ 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.
@@ -42,23 +43,24 @@ 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 propogated up by Parse() and the resulting AST can
// functions. Errors are propagated 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){
"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,
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,
command.Insert: parseIgnore,
}
}
@@ -78,10 +80,6 @@ func parseLine(line string) (string, *Node, error) {
return "", nil, err
}
if len(args) == 0 {
return "", nil, fmt.Errorf("Instruction %q is empty; cannot continue", cmd)
}
node := &Node{}
node.Value = cmd
+11 -11
View File
@@ -11,7 +11,7 @@ import (
const testDir = "testfiles"
const negativeTestDir = "testfiles-negative"
func getDirs(t *testing.T, dir string) []os.FileInfo {
func getDirs(t *testing.T, dir string) []string {
f, err := os.Open(dir)
if err != nil {
t.Fatal(err)
@@ -19,7 +19,7 @@ func getDirs(t *testing.T, dir string) []os.FileInfo {
defer f.Close()
dirs, err := f.Readdir(0)
dirs, err := f.Readdirnames(0)
if err != nil {
t.Fatal(err)
}
@@ -29,16 +29,16 @@ func getDirs(t *testing.T, dir string) []os.FileInfo {
func TestTestNegative(t *testing.T) {
for _, dir := range getDirs(t, negativeTestDir) {
dockerfile := filepath.Join(negativeTestDir, dir.Name(), "Dockerfile")
dockerfile := filepath.Join(negativeTestDir, dir, "Dockerfile")
df, err := os.Open(dockerfile)
if err != nil {
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
t.Fatalf("Dockerfile missing for %s: %v", dir, err)
}
_, err = Parse(df)
if err == nil {
t.Fatalf("No error parsing broken dockerfile for %s", dir.Name())
t.Fatalf("No error parsing broken dockerfile for %s", dir)
}
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.Name(), "Dockerfile")
resultfile := filepath.Join(testDir, dir.Name(), "result")
dockerfile := filepath.Join(testDir, dir, "Dockerfile")
resultfile := filepath.Join(testDir, dir, "result")
df, err := os.Open(dockerfile)
if err != nil {
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
t.Fatalf("Dockerfile missing for %s: %v", dir, err)
}
defer df.Close()
ast, err := Parse(df)
if err != nil {
t.Fatalf("Error parsing %s's dockerfile: %s", dir.Name(), err.Error())
t.Fatalf("Error parsing %s's dockerfile: %v", dir, err)
}
content, err := ioutil.ReadFile(resultfile)
if err != nil {
t.Fatalf("Error reading %s's result file: %s", dir.Name(), err.Error())
t.Fatalf("Error reading %s's result file: %v", dir, err)
}
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.Name())
t.Fatalf("%s: AST dump of dockerfile does not match result", dir)
}
}
}
@@ -1,8 +0,0 @@
FROM dockerfile/rabbitmq
RUN
rabbitmq-plugins enable \
rabbitmq_shovel \
rabbitmq_shovel_management \
rabbitmq_federation \
rabbitmq_federation_management
@@ -1,2 +0,0 @@
<html>
</html>
+8
View File
@@ -7,6 +7,14 @@ 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 \
+12 -6
View File
@@ -2,9 +2,15 @@
(env "name" "value")
(env "name" "value")
(env "name" "value" "name2" "value2")
(env "name" "value value1")
(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" "value" "name1" "value1" "name2" "value2a value2b" "name3" "value3an\"value3b\"" "name4" "value4a\\nvalue4b")
(env "name" "\"value value1\"")
(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" "\"''\"")
(env "name" "value" "name1" "value1" "name2" "\"value2a value2b\"" "name3" "\"value3a\\n\\\"value3b\\\"\"" "name4" "\"value4a\\\\nvalue4b\"")
+8 -6
View File
@@ -1,7 +1,6 @@
package parser
import (
"fmt"
"strconv"
"strings"
)
@@ -50,16 +49,19 @@ func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
// splitCommand takes a single line of text and parses out the cmd and args,
// which are used for dispatching to more exact parsing functions.
func splitCommand(line string) (string, string, error) {
cmdline := TOKEN_WHITESPACE.Split(line, 2)
var args string
if len(cmdline) != 2 {
return "", "", fmt.Errorf("We do not understand this file. Please ensure it is a valid Dockerfile. Parser error at %q", line)
// Make sure we get the same results irrespective of leading/trailing spaces
cmdline := TOKEN_WHITESPACE.Split(strings.TrimSpace(line), 2)
cmd := strings.ToLower(cmdline[0])
if len(cmdline) == 2 {
args = strings.TrimSpace(cmdline[1])
}
cmd := strings.ToLower(cmdline[0])
// the cmd should never have whitespace, but it's possible for the args to
// have trailing whitespace.
return cmd, strings.TrimSpace(cmdline[1]), nil
return cmd, args, nil
}
// covers comments and empty lines. Lines should be trimmed before passing to
+209
View File
@@ -0,0 +1,209 @@
package builder
// This will take a single word and an array of env variables and
// process all quotes (" and ') as well as $xxx and ${xxx} env variable
// tokens. Tries to mimic bash shell process.
// It doesn't support all flavors of ${xx:...} formats but new ones can
// be added by adding code to the "special ${} format processing" section
import (
"fmt"
"strings"
"unicode"
)
type shellWord struct {
word string
envs []string
pos int
}
func ProcessWord(word string, env []string) (string, error) {
sw := &shellWord{
word: word,
envs: env,
pos: 0,
}
return sw.process()
}
func (sw *shellWord) process() (string, error) {
return sw.processStopOn('\000')
}
// Process the word, starting at 'pos', and stop when we get to the
// end of the word or the 'stopChar' character
func (sw *shellWord) processStopOn(stopChar rune) (string, error) {
var result string
var charFuncMapping = map[rune]func() (string, error){
'\'': sw.processSingleQuote,
'"': sw.processDoubleQuote,
'$': sw.processDollar,
}
for sw.pos < len(sw.word) {
ch := sw.peek()
if stopChar != '\000' && ch == stopChar {
sw.next()
break
}
if fn, ok := charFuncMapping[ch]; ok {
// Call special processing func for certain chars
tmp, err := fn()
if err != nil {
return "", err
}
result += tmp
} else {
// Not special, just add it to the result
ch = sw.next()
if ch == '\\' {
// '\' escapes, except end of line
ch = sw.next()
if ch == '\000' {
continue
}
}
result += string(ch)
}
}
return result, nil
}
func (sw *shellWord) peek() rune {
if sw.pos == len(sw.word) {
return '\000'
}
return rune(sw.word[sw.pos])
}
func (sw *shellWord) next() rune {
if sw.pos == len(sw.word) {
return '\000'
}
ch := rune(sw.word[sw.pos])
sw.pos++
return ch
}
func (sw *shellWord) processSingleQuote() (string, error) {
// All chars between single quotes are taken as-is
// Note, you can't escape '
var result string
sw.next()
for {
ch := sw.next()
if ch == '\000' || ch == '\'' {
break
}
result += string(ch)
}
return result, nil
}
func (sw *shellWord) processDoubleQuote() (string, error) {
// All chars up to the next " are taken as-is, even ', except any $ chars
// But you can escape " with a \
var result string
sw.next()
for sw.pos < len(sw.word) {
ch := sw.peek()
if ch == '"' {
sw.next()
break
}
if ch == '$' {
tmp, err := sw.processDollar()
if err != nil {
return "", err
}
result += tmp
} else {
ch = sw.next()
if ch == '\\' {
chNext := sw.peek()
if chNext == '\000' {
// Ignore \ at end of word
continue
}
if chNext == '"' || chNext == '$' {
// \" and \$ can be escaped, all other \'s are left as-is
ch = sw.next()
}
}
result += string(ch)
}
}
return result, nil
}
func (sw *shellWord) processDollar() (string, error) {
sw.next()
ch := sw.peek()
if ch == '{' {
sw.next()
name := sw.processName()
ch = sw.peek()
if ch == '}' {
// Normal ${xx} case
sw.next()
return sw.getEnv(name), nil
}
return "", fmt.Errorf("Unsupported ${} substitution: %s", sw.word)
} else {
// $xxx case
name := sw.processName()
if name == "" {
return "$", nil
}
return sw.getEnv(name), nil
}
}
func (sw *shellWord) processName() string {
// Read in a name (alphanumeric or _)
// If it starts with a numeric then just return $#
var name string
for sw.pos < len(sw.word) {
ch := sw.peek()
if len(name) == 0 && unicode.IsDigit(ch) {
ch = sw.next()
return string(ch)
}
if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' {
break
}
ch = sw.next()
name += string(ch)
}
return name
}
func (sw *shellWord) getEnv(name string) string {
for _, env := range sw.envs {
i := strings.Index(env, "=")
if i < 0 {
if name == env {
// Should probably never get here, but just in case treat
// it like "var" and "var=" are the same
return ""
}
continue
}
if name != env[:i] {
continue
}
return env[i+1:]
}
return ""
}
+51
View File
@@ -0,0 +1,51 @@
package builder
import (
"bufio"
"os"
"strings"
"testing"
)
func TestShellParser(t *testing.T) {
file, err := os.Open("words")
if err != nil {
t.Fatalf("Can't open 'words': %s", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
envs := []string{"PWD=/home", "SHELL=bash"}
for scanner.Scan() {
line := scanner.Text()
// Trim comments and blank lines
i := strings.Index(line, "#")
if i >= 0 {
line = line[:i]
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
words := strings.Split(line, "|")
if len(words) != 2 {
t.Fatalf("Error in 'words' - should be 2 words:%q", words)
}
words[0] = strings.TrimSpace(words[0])
words[1] = strings.TrimSpace(words[1])
newWord, err := ProcessWord(words[0], envs)
if err != nil {
newWord = "error"
}
if newWord != words[1] {
t.Fatalf("Error. Src: %s Calc: %s Expected: %s", words[0], newWord, words[1])
}
}
}
-41
View File
@@ -1,50 +1,9 @@
package builder
import (
"regexp"
"strings"
)
var (
// `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line
// `\$` - match literal $
// `[[:alnum:]_]+` - match things like `$SOME_VAR`
// `{[[:alnum:]_]+}` - match things like `${SOME_VAR}`
tokenEnvInterpolation = regexp.MustCompile(`(\\|\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`)
// this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly
)
// handle environment replacement. Used in dispatcher.
func (b *Builder) replaceEnv(str string) string {
for _, match := range tokenEnvInterpolation.FindAllString(str, -1) {
idx := strings.Index(match, "\\$")
if idx != -1 {
if idx+2 >= len(match) {
str = strings.Replace(str, match, "\\$", -1)
continue
}
prefix := match[:idx]
stripped := match[idx+2:]
str = strings.Replace(str, match, prefix+"$"+stripped, -1)
continue
}
match = match[strings.Index(match, "$"):]
matchKey := strings.Trim(match, "${}")
for _, keyval := range b.Config.Env {
tmp := strings.SplitN(keyval, "=", 2)
if tmp[0] == matchKey {
str = strings.Replace(str, match, tmp[1], -1)
break
}
}
}
return str
}
func handleJsonArgs(args []string, attributes map[string]bool) []string {
if len(args) == 0 {
return []string{}
+43
View File
@@ -0,0 +1,43 @@
hello | hello
he'll'o | hello
he'llo | hello
he\'llo | he'llo
he\\'llo | he\llo
abc\tdef | abctdef
"abc\tdef" | abc\tdef
'abc\tdef' | abc\tdef
hello\ | hello
hello\\ | hello\
"hello | hello
"hello\" | hello"
"hel'lo" | hel'lo
'hello | hello
'hello\' | hello\
"''" | ''
$. | $.
$1 |
he$1x | hex
he$.x | he$.x
he$pwd. | he.
he$PWD | he/home
he\$PWD | he$PWD
he\\$PWD | he\/home
he\${} | he${}
he\${}xx | he${}xx
he${} | he
he${}xx | hexx
he${hi} | he
he${hi}xx | hexx
he${PWD} | he/home
he${.} | error
'he${XX}' | he${XX}
"he${PWD}" | he/home
"he'$PWD'" | he'/home'
"$PWD" | /home
'$PWD' | $PWD
'\$PWD' | \$PWD
'"hello"' | "hello"
he\$PWD | he$PWD
"he\$PWD" | he$PWD
'he\$PWD' | he\$PWD
he${PWD | error
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"github.com/docker/docker/api"
apiserver "github.com/docker/docker/api/server"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/engine"
"github.com/docker/docker/events"
"github.com/docker/docker/pkg/parsers/kernel"
+9 -4
View File
@@ -10,7 +10,12 @@ possibleConfigs=(
"/usr/src/linux-$(uname -r)/.config"
'/usr/src/linux/.config'
)
: ${CONFIG:="${possibleConfigs[0]}"}
if [ $# -gt 0 ]; then
CONFIG="$1"
else
: ${CONFIG:="${possibleConfigs[0]}"}
fi
if ! command -v zgrep &> /dev/null; then
zgrep() {
@@ -89,7 +94,7 @@ if [ ! -e "$CONFIG" ]; then
if [ ! -e "$CONFIG" ]; then
wrap_warning "error: cannot find kernel config"
wrap_warning " try running this script again, specifying the kernel config:"
wrap_warning " CONFIG=/path/to/kernel/.config $0"
wrap_warning " CONFIG=/path/to/kernel/.config $0 or $0 /path/to/kernel/.config"
exit 1
fi
fi
@@ -133,7 +138,7 @@ fi
flags=(
NAMESPACES {NET,PID,IPC,UTS}_NS
DEVPTS_MULTIPLE_INSTANCES
CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED
CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED CPUSETS
MACVLAN VETH BRIDGE
NF_NAT_IPV4 IP_NF_FILTER IP_NF_TARGET_MASQUERADE
NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
@@ -169,7 +174,7 @@ echo '- Storage Drivers:'
check_flags BLK_DEV_DM DM_THIN_PROVISIONING EXT4_FS EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
echo '- "'$(wrap_color 'overlay' blue)'":'
check_flags OVERLAY_FS | sed 's/^/ /'
check_flags OVERLAY_FS EXT4_FS_SECURITY EXT4_FS_POSIX_ACL | sed 's/^/ /'
} | sed 's/^/ /'
echo
+351 -110
View File
@@ -58,6 +58,18 @@ __docker_containers_unpauseable() {
__docker_containers_all '.State.Paused'
}
__docker_container_names() {
local containers=( $(__docker_q ps -aq --no-trunc) )
local names=( $(__docker_q inspect --format '{{.Name}}' "${containers[@]}") )
names=( "${names[@]#/}" ) # trim off the leading "/" from the container names
COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") )
}
__docker_container_ids() {
local containers=( $(__docker_q ps -aq) )
COMPREPLY=( $(compgen -W "${containers[*]}" -- "$cur") )
}
__docker_image_repos() {
local repos="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')"
COMPREPLY=( $(compgen -W "$repos" -- "$cur") )
@@ -131,6 +143,7 @@ __docker_capabilities() {
ALL
AUDIT_CONTROL
AUDIT_WRITE
AUDIT_READ
BLOCK_SUSPEND
CHOWN
DAC_OVERRIDE
@@ -169,9 +182,25 @@ __docker_capabilities() {
" -- "$cur" ) )
}
# a selection of the available signals that is most likely of interest in the
# context of docker containers.
__docker_signals() {
local signals=(
SIGCONT
SIGHUP
SIGINT
SIGKILL
SIGQUIT
SIGSTOP
SIGTERM
SIGUSR1
SIGUSR2
)
COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo $cur | tr '[:lower:]' '[:upper:]')" ) )
}
_docker_docker() {
local boolean_options="
--api-enable-cors
--daemon -d
--debug -D
--help -h
@@ -221,7 +250,7 @@ _docker_docker() {
_docker_attach() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--no-stdin --sig-proxy" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--help --no-stdin --sig-proxy" -- "$cur" ) )
;;
*)
local counter="$(__docker_pos_first_nonflag)"
@@ -238,11 +267,15 @@ _docker_build() {
__docker_image_repos_and_tags
return
;;
--file|-f)
_filedir
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--force-rm --no-cache --quiet -q --rm --tag -t" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--file -f --force-rm --help --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) )
;;
*)
local counter="$(__docker_pos_first_nonflag '--tag|-t')"
@@ -255,17 +288,17 @@ _docker_build() {
_docker_commit() {
case "$prev" in
--author|-a|--message|-m|--run)
--author|-a|--change|-c|--message|-m)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--author -a --message -m --run" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--author -a --change -c --help --message -m --pause -p" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag '--author|-a|--message|-m|--run')
local counter=$(__docker_pos_first_nonflag '--author|-a|--change|-c|--message|-m')
if [ $cword -eq $counter ]; then
__docker_containers_all
@@ -282,26 +315,33 @@ _docker_commit() {
}
_docker_cp() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
case "$cur" in
*:)
return
;;
*)
__docker_containers_all
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
compopt -o nospace
return
;;
esac
fi
(( counter++ ))
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
case "$cur" in
*:)
return
;;
*)
__docker_containers_all
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
compopt -o nospace
return
;;
esac
fi
(( counter++ ))
if [ $cword -eq $counter ]; then
_filedir
return
fi
if [ $cword -eq $counter ]; then
_filedir -d
return
fi
;;
esac
}
_docker_create() {
@@ -309,22 +349,53 @@ _docker_create() {
}
_docker_diff() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_all
fi
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_all
fi
;;
esac
}
_docker_events() {
case "$prev" in
--since)
--filter|-f)
COMPREPLY=( $( compgen -S = -W "container event image" -- "$cur" ) )
compopt -o nospace
return
;;
--since|--until)
return
;;
esac
# "=" gets parsed to a word and assigned to either $cur or $prev depending on whether
# it is the last character or not. So we search for "xxx=" in the the last two words.
case "${words[$cword-2]}$prev=" in
*container=*)
cur="${cur#=}"
__docker_containers_all
return
;;
*event=*)
COMPREPLY=( $( compgen -W "create destroy die export kill pause restart start stop unpause" -- "${cur#=}" ) )
return
;;
*image=*)
cur="${cur#=}"
__docker_image_repos_and_tags_and_ids
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--since" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--filter -f --help --since --until" -- "$cur" ) )
;;
esac
}
@@ -332,7 +403,7 @@ _docker_events() {
_docker_exec() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--detach -d --interactive -i -t --tty" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty" -- "$cur" ) )
;;
*)
__docker_containers_running
@@ -341,10 +412,17 @@ _docker_exec() {
}
_docker_export() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_all
fi
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_all
fi
;;
esac
}
_docker_help() {
@@ -357,7 +435,7 @@ _docker_help() {
_docker_history() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--no-trunc --quiet -q" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--help --no-trunc --quiet -q" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
@@ -369,34 +447,65 @@ _docker_history() {
}
_docker_images() {
case "$prev" in
--filter|-f)
COMPREPLY=( $( compgen -W "dangling=true label=" -- "$cur" ) )
if [ "$COMPREPLY" = "label=" ]; then
compopt -o nospace
fi
return
;;
esac
case "${words[$cword-2]}$prev=" in
*dangling=*)
COMPREPLY=( $( compgen -W "true false" -- "${cur#=}" ) )
return
;;
*label=*)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--all -a --no-trunc --quiet -q" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-trunc --quiet -q" -- "$cur" ) )
;;
=)
return
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_image_repos
fi
__docker_image_repos
;;
esac
}
_docker_import() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
return
fi
(( counter++ ))
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
return
fi
(( counter++ ))
if [ $cword -eq $counter ]; then
__docker_image_repos_and_tags
return
fi
if [ $cword -eq $counter ]; then
__docker_image_repos_and_tags
return
fi
;;
esac
}
_docker_info() {
return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
esac
}
_docker_inspect() {
@@ -408,7 +517,7 @@ _docker_inspect() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--format -f" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--format -f --help" -- "$cur" ) )
;;
*)
__docker_containers_and_images
@@ -417,7 +526,21 @@ _docker_inspect() {
}
_docker_kill() {
__docker_containers_running
case "$prev" in
--signal|-s)
__docker_signals
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help --signal -s" -- "$cur" ) )
;;
*)
__docker_containers_running
;;
esac
}
_docker_load() {
@@ -430,7 +553,7 @@ _docker_load() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--input -i" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--help --input -i" -- "$cur" ) )
;;
esac
}
@@ -444,15 +567,57 @@ _docker_login() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--email -e --password -p --username -u" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--email -e --help --password -p --username -u" -- "$cur" ) )
;;
esac
}
_docker_logout() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
esac
}
_docker_logs() {
case "$prev" in
--tail)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--follow -f" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--follow -f --help --tail --timestamps -t" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag '--tail')
if [ $cword -eq $counter ]; then
__docker_containers_all
fi
;;
esac
}
_docker_pause() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_pauseable
fi
;;
esac
}
_docker_port() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
@@ -463,50 +628,52 @@ _docker_logs() {
esac
}
_docker_pause() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_pauseable
fi
}
_docker_port() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_all
fi
}
_docker_ps() {
case "$prev" in
--before|--since)
__docker_containers_all
;;
--filter|-f)
COMPREPLY=( $( compgen -S = -W "exited id label name status" -- "$cur" ) )
compopt -o nospace
return
;;
-n)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--all -a --before --latest -l --no-trunc -n --quiet -q --size -s --since" -- "$cur" ) )
case "${words[$cword-2]}$prev=" in
*id=*)
cur="${cur#=}"
__docker_container_ids
return
;;
esac
}
_docker_pull() {
case "$prev" in
--tag|-t)
*name=*)
cur="${cur#=}"
__docker_container_names
return
;;
*status=*)
COMPREPLY=( $( compgen -W "exited paused restarting running" -- "${cur#=}" ) )
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--tag -t" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--all -a --before --filter -f --help --latest -l -n --no-trunc --quiet -q --size -s --since" -- "$cur" ) )
;;
esac
}
_docker_pull() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--all-tags -a --help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag '--tag|-t')
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_image_repos_and_tags
fi
@@ -515,10 +682,31 @@ _docker_pull() {
}
_docker_push() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_image_repos_and_tags
fi
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_image_repos_and_tags
fi
;;
esac
}
_docker_rename() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_all
fi
;;
esac
}
_docker_restart() {
@@ -530,7 +718,7 @@ _docker_restart() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--time -t" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--help --time -t" -- "$cur" ) )
;;
*)
__docker_containers_all
@@ -541,8 +729,7 @@ _docker_restart() {
_docker_rm() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--force -f --link -l --volumes -v" -- "$cur" ) )
return
COMPREPLY=( $( compgen -W "--force -f --help --link -l --volumes -v" -- "$cur" ) )
;;
*)
for arg in "${COMP_WORDS[@]}"; do
@@ -554,13 +741,19 @@ _docker_rm() {
esac
done
__docker_containers_stopped
return
;;
esac
}
_docker_rmi() {
__docker_image_repos_and_tags_and_ids
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--force -f --help --no-prune" -- "$cur" ) )
;;
*)
__docker_image_repos_and_tags_and_ids
;;
esac
}
_docker_run() {
@@ -569,6 +762,7 @@ _docker_run() {
--attach -a
--cap-add
--cap-drop
--cgroup-parent
--cidfile
--cpuset
--cpu-shares -c
@@ -581,25 +775,33 @@ _docker_run() {
--expose
--hostname -h
--ipc
--label -l
--label-file
--link
--log-driver
--lxc-conf
--mac-address
--memory -m
--memory-swap
--name
--net
--pid
--publish -p
--restart
--security-opt
--user -u
--ulimit
--volumes-from
--volume -v
--workdir -w
"
local all_options="$options_with_args
--help
--interactive -i
--privileged
--publish-all -P
--read-only
--tty -t
"
@@ -628,11 +830,11 @@ _docker_run() {
__docker_capabilities
return
;;
--cidfile|--env-file)
--cidfile|--env-file|--label-file)
_filedir
return
;;
--device|-d|--volume)
--device|--volume|-v)
case "$cur" in
*:*)
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
@@ -680,6 +882,10 @@ _docker_run() {
esac
return
;;
--log-driver)
COMPREPLY=( $( compgen -W "json-file syslog none" -- "$cur") )
return
;;
--net)
case "$cur" in
container:*)
@@ -756,7 +962,7 @@ _docker_save() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-o --output" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--help --output -o" -- "$cur" ) )
;;
*)
__docker_image_repos_and_tags_and_ids
@@ -773,7 +979,7 @@ _docker_search() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--automated --no-trunc --stars -s" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--automated --help --no-trunc --stars -s" -- "$cur" ) )
;;
esac
}
@@ -781,7 +987,7 @@ _docker_search() {
_docker_start() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--attach -a --interactive -i" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--attach -a --help --interactive -i" -- "$cur" ) )
;;
*)
__docker_containers_stopped
@@ -790,7 +996,14 @@ _docker_start() {
}
_docker_stats() {
__docker_containers_running
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
__docker_containers_running
;;
esac
}
_docker_stop() {
@@ -802,7 +1015,7 @@ _docker_stop() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--time -t" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--help --time -t" -- "$cur" ) )
;;
*)
__docker_containers_running
@@ -813,7 +1026,7 @@ _docker_stop() {
_docker_tag() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--force -f" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--force -f --help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
@@ -833,25 +1046,50 @@ _docker_tag() {
}
_docker_unpause() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_unpauseable
fi
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_unpauseable
fi
;;
esac
}
_docker_top() {
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_running
fi
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_containers_running
fi
;;
esac
}
_docker_version() {
return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
esac
}
_docker_wait() {
__docker_containers_all
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
__docker_containers_all
;;
esac
}
_docker() {
@@ -872,17 +1110,18 @@ _docker() {
images
import
info
insert
inspect
kill
load
login
logout
logs
pause
port
ps
pull
push
rename
restart
rm
rmi
@@ -900,8 +1139,10 @@ _docker() {
)
local main_options_with_args="
--api-cors-header
--bip
--bridge -b
--default-ulimit
--dns
--dns-search
--exec-driver -e
+4 -4
View File
@@ -16,7 +16,7 @@
function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand'
for i in (commandline -opc)
if contains -- $i attach build commit cp create diff events exec export history images import info insert inspect kill load login logout logs pause port ps pull push restart rm rmi run save search start stop tag top unpause version wait
if contains -- $i attach build commit cp create diff events exec export history images import info inspect kill load login logout logs pause port ps pull push rename restart rm rmi run save search start stop tag top unpause version wait
return 1
end
end
@@ -43,7 +43,7 @@ function __fish_print_docker_repositories --description 'Print a list of docker
end
# common options
complete -c docker -f -n '__fish_docker_no_subcommand' -l api-enable-cors -d 'Enable CORS headers in the remote API'
complete -c docker -f -n '__fish_docker_no_subcommand' -l api-cors-header -d "Set CORS headers in the remote API. Default is cors disabled"
complete -c docker -f -n '__fish_docker_no_subcommand' -s b -l bridge -d 'Attach containers to a pre-existing network bridge'
complete -c docker -f -n '__fish_docker_no_subcommand' -l bip -d "Use this CIDR notation address for the network bridge's IP, not compatible with -b"
complete -c docker -f -n '__fish_docker_no_subcommand' -s D -l debug -d 'Enable debug mode'
@@ -72,7 +72,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -l registry-mirror -d 'Sp
complete -c docker -f -n '__fish_docker_no_subcommand' -s s -l storage-driver -d 'Force the Docker runtime to use a specific storage driver'
complete -c docker -f -n '__fish_docker_no_subcommand' -l selinux-enabled -d 'Enable selinux support. SELinux does not presently support the BTRFS storage driver'
complete -c docker -f -n '__fish_docker_no_subcommand' -l storage-opt -d 'Set storage driver options'
complete -c docker -f -n '__fish_docker_no_subcommand' -l tls -d 'Use TLS; implied by --tlsverify flag'
complete -c docker -f -n '__fish_docker_no_subcommand' -l tls -d 'Use TLS; implied by --tlsverify'
complete -c docker -f -n '__fish_docker_no_subcommand' -l tlscacert -d 'Trust only remotes providing a certificate signed by the CA given here'
complete -c docker -f -n '__fish_docker_no_subcommand' -l tlscert -d 'Path to TLS certificate file'
complete -c docker -f -n '__fish_docker_no_subcommand' -l tlskey -d 'Path to TLS key file'
@@ -345,7 +345,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from save' -s o -l output -d
complete -c docker -A -f -n '__fish_seen_subcommand_from save' -a '(__fish_print_docker_images)' -d "Image"
# search
complete -c docker -f -n '__fish_docker_no_subcommand' -a search -d 'Search for an image on the Docker Hub'
complete -c docker -f -n '__fish_docker_no_subcommand' -a search -d 'Search for an image on the registry (defaults to the Docker Hub)'
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l automated -d 'Only show automated builds'
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l help -d 'Print usage'
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l no-trunc -d "Don't truncate output"
+3
View File
@@ -0,0 +1,3 @@
Tianon Gravi <admwiggin@gmail.com> (@tianon)
Jessie Frazelle <jess@docker.com> (@jfrazelle)
Vincent Bernat <vincent@bernat.im> (@vincentbernat)
+44 -23
View File
@@ -197,8 +197,10 @@ __docker_subcommand () {
;;
(build)
_arguments \
{-f,--file=-}'[Dockerfile to use]:Dockerfile:_files' \
'--force-rm[Always remove intermediate containers]' \
'--no-cache[Do not use cache when building the image]' \
'--pull[Attempt to pull a newer version of the image]' \
{-q,--quiet}'[Suppress verbose build output]' \
'--rm[Remove intermediate containers after a successful build]' \
{-t,--tag=-}'[Repository, name and tag to be applied]:repository:__docker_repositories_with_tags' \
@@ -209,7 +211,6 @@ __docker_subcommand () {
{-a,--author=-}'[Author]:author: ' \
{-m,--message=-}'[Commit message]:message: ' \
{-p,--pause}'[Pause container during commit]' \
'--run=-[Configuration automatically applied when the image is run]:configuration: ' \
':container:__docker_containers' \
':repository:__docker_repositories_with_tags'
;;
@@ -232,15 +233,28 @@ __docker_subcommand () {
;;
(events)
_arguments \
'*'{-f,--filter=-}'[Filter values]:filter: ' \
'--since=-[Events created since this timestamp]:timestamp: ' \
'--until=-[Events created until this timestamp]:timestamp: '
;;
(exec)
local state ret
_arguments \
{-d,--detach}'[Detached mode: leave the container running in the background]' \
{-i,--interactive}'[Keep stdin open even if not attached]' \
{-t,--tty}'[Allocate a pseudo-tty]' \
':containers:__docker_runningcontainers'
':containers:__docker_runningcontainers' \
'*::command:->anycommand' && ret=0
case $state in
(anycommand)
shift 1 words
(( CURRENT-- ))
_normal
;;
esac
return ret
;;
(history)
_arguments \
@@ -254,15 +268,8 @@ __docker_subcommand () {
'*'{-f,--filter=-}'[Filter values]:filter: ' \
'--no-trunc[Do not truncate output]' \
{-q,--quiet}'[Only show numeric IDs]' \
'--tree[Output graph in tree format]' \
'--viz[Output graph in graphviz format]' \
':repository:__docker_repositories'
;;
(inspect)
_arguments \
{-f,--format=-}'[Format the output using the given go template]:template: ' \
'*:containers:__docker_containers'
;;
(import)
_arguments \
':URL:(- http:// file://)' \
@@ -270,15 +277,10 @@ __docker_subcommand () {
;;
(info)
;;
(import)
(inspect)
_arguments \
':URL:(- http:// file://)' \
':repository:__docker_repositories_with_tags'
;;
(insert)
_arguments '1:containers:__docker_containers' \
'2:URL:(http:// file://)' \
'3:file:_files'
{-f,--format=-}'[Format the output using the given go template]:template: ' \
'*:containers:__docker_containers'
;;
(kill)
_arguments \
@@ -287,7 +289,7 @@ __docker_subcommand () {
;;
(load)
_arguments \
{-i,--input=-}'[Read from tar archive file]:tar:_files'
{-i,--input=-}'[Read from tar archive file]:archive file:_files -g "*.((tar|TAR)(.gz|.GZ|.Z|.bz2|.lzma|.xz|)|(tbz|tgz|txz))(-.)"'
;;
(login)
_arguments \
@@ -304,6 +306,7 @@ __docker_subcommand () {
_arguments \
{-f,--follow}'[Follow log output]' \
{-t,--timestamps}'[Show timestamps]' \
'--tail=-[Output the last K lines]:lines:(1 10 20 50 all)' \
'*:containers:__docker_containers'
;;
(port)
@@ -321,6 +324,10 @@ __docker_subcommand () {
{-i,--interactive}'[Attach container'"'"'s stding]' \
'*:containers:__docker_stoppedcontainers'
;;
(stats)
_arguments \
'*:containers:__docker_runningcontainers'
;;
(rm)
_arguments \
{-f,--force}'[Force removal]' \
@@ -391,7 +398,7 @@ __docker_subcommand () {
'*--lxc-conf=-[Add custom lxc options]:lxc options: ' \
'-m[Memory limit (in bytes)]:limit: ' \
'--name=-[Container name]:name: ' \
'--net=-[Network mode]:network mode:(bridge none container: host)' \
'--net=-[Network mode]:network mode:(bridge none container host)' \
{-P,--publish-all}'[Publish all exposed ports]' \
'*'{-p,--publish=-}'[Expose a container'"'"'s port to the host]:port:_ports' \
'--privileged[Give extended privileges to this container]' \
@@ -419,19 +426,33 @@ __docker_subcommand () {
esac
;;
(pull|search)
_arguments ':name:__docker_search'
(pull)
_arguments \
{-a,--all-tags}'[Download all tagged images]' \
':name:__docker_search'
;;
(push)
_arguments ':images:__docker_images'
;;
(rename)
_arguments \
':old name:__docker_containers' \
':new name: '
;;
(save)
_arguments \
{-o,--output=-}'[Write to file]:file:_files' \
':images:__docker_images'
'*:images:__docker_images'
;;
(search)
_arguments \
'--automated[Only show automated builds]' \
'--no-trunc[Do not truncate output]' \
{-s,--stars=-}'[Only display with at least X stars]:stars:(0 10 100 1000)' \
':term: '
;;
(wait)
_arguments ':containers:__docker_runningcontainers'
_arguments '*:containers:__docker_runningcontainers'
;;
(help)
_arguments ':subcommand:__docker_commands'
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
set -e
# hello-world latest ef872312fe1b 3 months ago 910 B
# hello-world latest ef872312fe1bbc5e05aae626791a47ee9b032efa8f3bda39cc0be7b56bfe59b9 3 months ago 910 B
# debian latest f6fab3b798be 10 weeks ago 85.1 MB
# debian latest f6fab3b798be3174f45aa1eb731f8182705555f89c9026d8c1ef230cbf8301dd 10 weeks ago 85.1 MB
if ! command -v curl &> /dev/null; then
echo >&2 'error: "curl" not found!'
exit 1
fi
usage() {
echo "usage: $0 dir image[:tag][@image-id] ..."
echo " ie: $0 /tmp/hello-world hello-world"
echo " $0 /tmp/debian-jessie debian:jessie"
echo " $0 /tmp/old-hello-world hello-world@ef872312fe1bbc5e05aae626791a47ee9b032efa8f3bda39cc0be7b56bfe59b9"
echo " $0 /tmp/old-debian debian:latest@f6fab3b798be3174f45aa1eb731f8182705555f89c9026d8c1ef230cbf8301dd"
[ -z "$1" ] || exit "$1"
}
dir="$1" # dir for building tar in
shift || usage 1 >&2
[ $# -gt 0 -a "$dir" ] || usage 2 >&2
mkdir -p "$dir"
# hacky workarounds for Bash 3 support (no associative arrays)
images=()
rm -f "$dir"/tags-*.tmp
# repositories[busybox]='"latest": "...", "ubuntu-14.04": "..."'
while [ $# -gt 0 ]; do
imageTag="$1"
shift
image="${imageTag%%[:@]*}"
tag="${imageTag#*:}"
imageId="${tag##*@}"
[ "$imageId" != "$tag" ] || imageId=
[ "$tag" != "$imageTag" ] || tag='latest'
tag="${tag%@*}"
token="$(curl -sSL -o /dev/null -D- -H 'X-Docker-Token: true' "https://index.docker.io/v1/repositories/$image/images" | tr -d '\r' | awk -F ': *' '$1 == "X-Docker-Token" { print $2 }')"
if [ -z "$imageId" ]; then
imageId="$(curl -sSL -H "Authorization: Token $token" "https://registry-1.docker.io/v1/repositories/$image/tags/$tag")"
imageId="${imageId//\"/}"
fi
ancestryJson="$(curl -sSL -H "Authorization: Token $token" "https://registry-1.docker.io/v1/images/$imageId/ancestry")"
if [ "${ancestryJson:0:1}" != '[' ]; then
echo >&2 "error: /v1/images/$imageId/ancestry returned something unexpected:"
echo >&2 " $ancestryJson"
exit 1
fi
IFS=','
ancestry=( ${ancestryJson//[\[\] \"]/} )
unset IFS
if [ -s "$dir/tags-$image.tmp" ]; then
echo -n ', ' >> "$dir/tags-$image.tmp"
else
images=( "${images[@]}" "$image" )
fi
echo -n '"'"$tag"'": "'"$imageId"'"' >> "$dir/tags-$image.tmp"
echo "Downloading '$imageTag' (${#ancestry[@]} layers)..."
for imageId in "${ancestry[@]}"; do
mkdir -p "$dir/$imageId"
echo '1.0' > "$dir/$imageId/VERSION"
curl -sSL -H "Authorization: Token $token" "https://registry-1.docker.io/v1/images/$imageId/json" -o "$dir/$imageId/json"
# TODO figure out why "-C -" doesn't work here
# "curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume."
# "HTTP/1.1 416 Requested Range Not Satisfiable"
if [ -f "$dir/$imageId/layer.tar" ]; then
# TODO hackpatch for no -C support :'(
echo "skipping existing ${imageId:0:12}"
continue
fi
curl -SL --progress -H "Authorization: Token $token" "https://registry-1.docker.io/v1/images/$imageId/layer" -o "$dir/$imageId/layer.tar" # -C -
done
echo
done
echo -n '{' > "$dir/repositories"
firstImage=1
for image in "${images[@]}"; do
[ "$firstImage" ] || echo -n ',' >> "$dir/repositories"
firstImage=
echo -n $'\n\t' >> "$dir/repositories"
echo -n '"'"$image"'": { '"$(cat "$dir/tags-$image.tmp")"' }' >> "$dir/repositories"
done
echo -n $'\n}\n' >> "$dir/repositories"
rm -f "$dir"/tags-*.tmp
echo "Download of images into '$dir' complete."
echo "Use something like the following to load the result into a Docker daemon:"
echo " tar -cC '$dir' . | docker load"
+4
View File
@@ -0,0 +1,4 @@
FROM busybox
EXPOSE 80/tcp
COPY httpserver .
CMD ["./httpserver"]
+12
View File
@@ -0,0 +1,12 @@
package main
import (
"log"
"net/http"
)
func main() {
fs := http.FileServer(http.Dir("/static"))
http.Handle("/", fs)
log.Panic(http.ListenAndServe(":80", nil))
}
+1
View File
@@ -9,6 +9,7 @@ ExecStart=/usr/bin/docker -d -H fd://
MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
[Install]
WantedBy=multi-user.target
+9
View File
@@ -43,6 +43,8 @@ prestart() {
start() {
[ -x $exec ] || exit 5
check_for_cleanup
if ! [ -f $pidfile ]; then
prestart
printf "Starting $prog:\t"
@@ -97,6 +99,13 @@ rh_status_q() {
rh_status >/dev/null 2>&1
}
check_for_cleanup() {
if [ -f ${pidfile} ]; then
/bin/ps -fp $(cat ${pidfile}) > /dev/null || rm ${pidfile}
fi
}
case "$1" in
start)
rh_status_q && exit 0
+1 -1
View File
@@ -49,7 +49,7 @@ post-start script
fi
if ! printf "%s" "$DOCKER_OPTS" | grep -qE -e '-H|--host'; then
while ! [ -e /var/run/docker.sock ]; do
initctl status $UPSTART_JOB | grep -q "stop/" && exit 1
initctl status $UPSTART_JOB | grep -qE "(stop|respawn)/" && exit 1
echo "Waiting for /var/run/docker.sock"
sleep 0.1
done
+1 -1
View File
@@ -72,7 +72,7 @@ REL=${REL:-edge}
MIRROR=${MIRROR:-http://nl.alpinelinux.org/alpine}
SAVE=${SAVE:-0}
REPO=$MIRROR/$REL/main
ARCH=$(uname -m)
ARCH=${ARCH:-$(uname -m)}
tmp
getapk
+1 -1
View File
@@ -87,5 +87,5 @@ mknod -m 666 $DEV/ptmx c 5 2
ln -sf /proc/self/fd $DEV/fd
tar --numeric-owner --xattrs --acls -C $ROOTFS -c . | docker import - archlinux
docker run -i -t archlinux echo Success.
docker run -t archlinux echo Success.
rm -rf $ROOTFS
+13 -3
View File
@@ -57,6 +57,12 @@ mknod -m 666 "$target"/dev/tty0 c 4 0
mknod -m 666 "$target"/dev/urandom c 1 9
mknod -m 666 "$target"/dev/zero c 1 5
# amazon linux yum will fail without vars set
if [ -d /etc/yum/vars ]; then
mkdir -p -m 755 "$target"/etc/yum
cp -a /etc/yum/vars "$target"/etc/yum/
fi
yum -c "$yum_config" --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \
--setopt=group_package_types=mandatory -y groupinstall Core
yum -c "$yum_config" --installroot="$target" -y clean all
@@ -83,9 +89,13 @@ rm -rf "$target"/etc/ld.so.cache
rm -rf "$target"/var/cache/ldconfig/*
version=
if [ -r "$target"/etc/redhat-release ]; then
version="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' "$target"/etc/redhat-release)"
fi
for file in "$target"/etc/{redhat,system}-release
do
if [ -r "$file" ]; then
version="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' "$file")"
break
fi
done
if [ -z "$version" ]; then
echo >&2 "warning: cannot autodetect OS version, using '$name' as tag"
+57 -26
View File
@@ -15,6 +15,16 @@ done
suite="$1"
shift
# get path to "chroot" in our current PATH
chrootPath="$(type -P chroot)"
rootfs_chroot() {
# "chroot" doesn't set PATH, so we need to set it explicitly to something our new debootstrap chroot can use appropriately!
# set PATH and chroot away!
PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \
"$chrootPath" "$rootfsDir" "$@"
}
# allow for DEBOOTSTRAP=qemu-debootstrap ./mkimage.sh ...
: ${DEBOOTSTRAP:=debootstrap}
@@ -28,26 +38,26 @@ shift
# prevent init scripts from running during install/update
echo >&2 "+ echo exit 101 > '$rootfsDir/usr/sbin/policy-rc.d'"
cat > "$rootfsDir/usr/sbin/policy-rc.d" <<'EOF'
#!/bin/sh
#!/bin/sh
# For most Docker users, "apt-get install" only happens during "docker build",
# where starting services doesn't work and often fails in humorous ways. This
# prevents those failures by stopping the services from attempting to start.
# For most Docker users, "apt-get install" only happens during "docker build",
# where starting services doesn't work and often fails in humorous ways. This
# prevents those failures by stopping the services from attempting to start.
exit 101
exit 101
EOF
chmod +x "$rootfsDir/usr/sbin/policy-rc.d"
# prevent upstart scripts from running during install/update
(
set -x
chroot "$rootfsDir" dpkg-divert --local --rename --add /sbin/initctl
rootfs_chroot dpkg-divert --local --rename --add /sbin/initctl
cp -a "$rootfsDir/usr/sbin/policy-rc.d" "$rootfsDir/sbin/initctl"
sed -i 's/^exit.*/exit 0/' "$rootfsDir/sbin/initctl"
)
# shrink a little, since apt makes us cache-fat (wheezy: ~157.5MB vs ~120MB)
( set -x; chroot "$rootfsDir" apt-get clean )
( set -x; rootfs_chroot apt-get clean )
# this file is one APT creates to make sure we don't "autoremove" our currently
# in-use kernel, which doesn't really apply to debootstraps/Docker images that
@@ -59,12 +69,12 @@ if strings "$rootfsDir/usr/bin/dpkg" | grep -q unsafe-io; then
# force dpkg not to call sync() after package extraction (speeding up installs)
echo >&2 "+ echo force-unsafe-io > '$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup'"
cat > "$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup" <<-'EOF'
# For most Docker users, package installs happen during "docker build", which
# doesn't survive power loss and gets restarted clean afterwards anyhow, so
# this minor tweak gives us a nice speedup (much nicer on spinning disks,
# obviously).
# For most Docker users, package installs happen during "docker build", which
# doesn't survive power loss and gets restarted clean afterwards anyhow, so
# this minor tweak gives us a nice speedup (much nicer on spinning disks,
# obviously).
force-unsafe-io
force-unsafe-io
EOF
fi
@@ -97,26 +107,47 @@ if [ -d "$rootfsDir/etc/apt/apt.conf.d" ]; then
# remove apt-cache translations for fast "apt-get update"
echo >&2 "+ echo Acquire::Languages 'none' > '$rootfsDir/etc/apt/apt.conf.d/docker-no-languages'"
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-no-languages" <<-'EOF'
# In Docker, we don't often need the "Translations" files, so we're just wasting
# time and space by downloading them, and this inhibits that. For users that do
# need them, it's a simple matter to delete this file and "apt-get update". :)
# In Docker, we don't often need the "Translations" files, so we're just wasting
# time and space by downloading them, and this inhibits that. For users that do
# need them, it's a simple matter to delete this file and "apt-get update". :)
Acquire::Languages "none";
Acquire::Languages "none";
EOF
echo >&2 "+ echo Acquire::GzipIndexes 'true' > '$rootfsDir/etc/apt/apt.conf.d/docker-gzip-indexes'"
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-gzip-indexes" <<-'EOF'
# Since Docker users using "RUN apt-get update && apt-get install -y ..." in
# their Dockerfiles don't go delete the lists files afterwards, we want them to
# be as small as possible on-disk, so we explicitly request "gz" versions and
# tell Apt to keep them gzipped on-disk.
# Since Docker users using "RUN apt-get update && apt-get install -y ..." in
# their Dockerfiles don't go delete the lists files afterwards, we want them to
# be as small as possible on-disk, so we explicitly request "gz" versions and
# tell Apt to keep them gzipped on-disk.
# For comparison, an "apt-get update" layer without this on a pristine
# "debian:wheezy" base image was "29.88 MB", where with this it was only
# "8.273 MB".
# For comparison, an "apt-get update" layer without this on a pristine
# "debian:wheezy" base image was "29.88 MB", where with this it was only
# "8.273 MB".
Acquire::GzipIndexes "true";
Acquire::CompressionTypes::Order:: "gz";
Acquire::GzipIndexes "true";
Acquire::CompressionTypes::Order:: "gz";
EOF
# update "autoremove" configuration to be aggressive about removing suggests deps that weren't manually installed
echo >&2 "+ echo Apt::AutoRemove::SuggestsImportant 'false' > '$rootfsDir/etc/apt/apt.conf.d/docker-autoremove-suggests'"
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-autoremove-suggests" <<-'EOF'
# Since Docker users are looking for the smallest possible final images, the
# following emerges as a very common pattern:
# RUN apt-get update \
# && apt-get install -y <packages> \
# && <do some compilation work> \
# && apt-get purge -y --auto-remove <packages>
# By default, APT will actually _keep_ packages installed via Recommends or
# Depends if another package Suggests them, even and including if the package
# that originally caused them to be installed is removed. Setting this to
# "false" ensures that APT is appropriately aggressive about removing the
# packages it added.
# https://aptitude.alioth.debian.org/doc/en/ch02s05s05.html#configApt-AutoRemove-SuggestsImportant
Apt::AutoRemove::SuggestsImportant "false";
EOF
fi
@@ -191,7 +222,7 @@ fi
set -x
# make sure we're fully up-to-date
chroot "$rootfsDir" bash -c 'apt-get update && apt-get dist-upgrade -y'
rootfs_chroot sh -xc 'apt-get update && apt-get dist-upgrade -y'
# delete all the apt list files since they're big and get stale quickly
rm -rf "$rootfsDir/var/lib/apt/lists"/*
+1
View File
@@ -22,6 +22,7 @@
<item> CMD </item>
<item> WORKDIR </item>
<item> USER </item>
<item> LABEL </item>
</list>
<contexts>
@@ -12,7 +12,7 @@
<array>
<dict>
<key>match</key>
<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY)\s</string>
<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|LABEL|WORKDIR|COPY)\s</string>
<key>captures</key>
<dict>
<key>0</key>
+1 -1
View File
@@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile"
syntax case ignore
syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|VOLUME|WORKDIR|COPY)\s/
syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY)\s/
highlight link dockerfileKeyword Keyword
syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/
-7
View File
@@ -1,7 +0,0 @@
Solomon Hykes <solomon@docker.com> (@shykes)
Victor Vieux <vieux@docker.com> (@vieux)
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
Cristian Staretu <cristian.staretu@gmail.com> (@unclejack)
Tibor Vass <teabee89@gmail.com> (@tiborvass)
Vishnu Kannan <vishnuk@google.com> (@vishh)
volumes.go: Brian Goff <cpuguy83@gmail.com> (@cpuguy83)
+5 -5
View File
@@ -28,9 +28,9 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
stderr = job.GetenvBool("stderr")
)
container := daemon.Get(name)
if container == nil {
return job.Errorf("No such container: %s", name)
container, err := daemon.Get(name)
if err != nil {
return job.Error(err)
}
//logs
@@ -101,7 +101,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
cStderr = job.Stderr
}
<-daemon.attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdout, cStderr)
<-daemon.Attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdout, cStderr)
// If we are in stdinonce mode, wait for the process to end
// otherwise, simply return
if container.Config.StdinOnce && !container.Config.Tty {
@@ -111,7 +111,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
return engine.StatusOK
}
func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
var (
cStdout, cStderr io.ReadCloser
cStdin io.WriteCloser
+23 -18
View File
@@ -9,24 +9,29 @@ func (daemon *Daemon) ContainerChanges(job *engine.Job) engine.Status {
return job.Errorf("Usage: %s CONTAINER", job.Name)
}
name := job.Args[0]
if container := daemon.Get(name); container != nil {
outs := engine.NewTable("", 0)
changes, err := container.Changes()
if err != nil {
return job.Error(err)
}
for _, change := range changes {
out := &engine.Env{}
if err := out.Import(change); err != nil {
return job.Error(err)
}
outs.Add(out)
}
if _, err := outs.WriteListTo(job.Stdout); err != nil {
return job.Error(err)
}
} else {
return job.Errorf("No such container: %s", name)
container, error := daemon.Get(name)
if error != nil {
return job.Error(error)
}
outs := engine.NewTable("", 0)
changes, err := container.Changes()
if err != nil {
return job.Error(err)
}
for _, change := range changes {
out := &engine.Env{}
if err := out.Import(change); err != nil {
return job.Error(err)
}
outs.Add(out)
}
if _, err := outs.WriteListTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
}
+20 -7
View File
@@ -1,6 +1,9 @@
package daemon
import (
"bytes"
"encoding/json"
"github.com/docker/docker/engine"
"github.com/docker/docker/image"
"github.com/docker/docker/runconfig"
@@ -12,17 +15,27 @@ func (daemon *Daemon) ContainerCommit(job *engine.Job) engine.Status {
}
name := job.Args[0]
container := daemon.Get(name)
if container == nil {
return job.Errorf("No such container: %s", name)
container, err := daemon.Get(name)
if err != nil {
return job.Error(err)
}
var (
config = container.Config
newConfig runconfig.Config
config = container.Config
stdoutBuffer = bytes.NewBuffer(nil)
newConfig runconfig.Config
)
if err := job.GetenvJson("config", &newConfig); err != nil {
buildConfigJob := daemon.eng.Job("build_config")
buildConfigJob.Stdout.Add(stdoutBuffer)
buildConfigJob.Setenv("changes", job.Getenv("changes"))
// FIXME this should be remove when we remove deprecated config param
buildConfigJob.Setenv("config", job.Getenv("config"))
if err := buildConfigJob.Run(); err != nil {
return job.Error(err)
}
if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil {
return job.Error(err)
}
@@ -41,7 +54,7 @@ func (daemon *Daemon) ContainerCommit(job *engine.Job) engine.Status {
// Commit creates a new filesystem image from the current state of a container.
// The image can optionally be tagged into a repository
func (daemon *Daemon) Commit(container *Container, repository, tag, comment, author string, pause bool, config *runconfig.Config) (*image.Image, error) {
if pause {
if pause && !container.IsPaused() {
container.Pause()
defer container.Unpause()
}
+30 -17
View File
@@ -6,6 +6,8 @@ import (
"github.com/docker/docker/daemon/networkdriver"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/runconfig"
)
const (
@@ -37,11 +39,16 @@ type Config struct {
GraphOptions []string
ExecDriver string
Mtu int
SocketGroup string
EnableCors bool
CorsHeaders string
DisableNetwork bool
EnableSelinuxSupport bool
Context map[string][]string
TrustKeyPath string
Labels []string
Ulimits map[string]*ulimit.Ulimit
LogConfig runconfig.LogConfig
}
// InstallFlags adds command-line options to the top-level flag parser for
@@ -50,27 +57,33 @@ type Config struct {
// from the command-line.
func (config *Config) InstallFlags() {
flag.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file")
flag.StringVar(&config.Root, []string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the Docker runtime")
flag.StringVar(&config.Root, []string{"g", "-graph"}, "/var/lib/docker", "Root of the Docker runtime")
flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
flag.BoolVar(&config.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules")
flag.BoolVar(&config.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward and IPv6 forwarding if --fixed-cidr-v6 is defined. IPv6 forwarding may interfere with your existing IPv6 configuration when using Router Advertisement.")
flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range")
flag.BoolVar(&config.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable addition of iptables rules")
flag.BoolVar(&config.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward")
flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading")
flag.BoolVar(&config.EnableIPv6, []string{"-ipv6"}, false, "Enable IPv6 networking")
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (e.g. 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
flag.StringVar(&config.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs (e.g.: 2001:a02b/48)")
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Allow unrestricted inter-container and Docker daemon host communication")
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support. SELinux does not presently support the BTRFS storage driver")
flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU\nif no value is provided: default to the default route MTU or 1500 if no default route is available")
opts.IPVar(&config.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Specify network bridge IP")
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge")
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs")
flag.StringVar(&config.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs")
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Storage driver to use")
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Exec driver to use")
flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support")
flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU")
flag.StringVar(&config.SocketGroup, []string{"G", "-group"}, "docker", "Group for the unix socket")
flag.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header")
flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API")
opts.IPVar(&config.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports")
opts.ListVar(&config.GraphOptions, []string{"-storage-opt"}, "Set storage driver options")
// FIXME: why the inconsistency between "hosts" and "sockets"?
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon (displayed in `docker info`)")
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
config.Ulimits = make(map[string]*ulimit.Ulimit)
opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver")
}
func getDefaultNetworkMtu() int {
+144 -45
View File
@@ -14,22 +14,30 @@ import (
"syscall"
"time"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/devices"
"github.com/docker/libcontainer/label"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/docker/docker/daemon/logger/syslog"
"github.com/docker/docker/engine"
"github.com/docker/docker/image"
"github.com/docker/docker/links"
"github.com/docker/docker/nat"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/broadcastwriter"
"github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/networkfs/etchosts"
"github.com/docker/docker/pkg/networkfs/resolvconf"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
@@ -70,6 +78,7 @@ type Container struct {
ResolvConfPath string
HostnamePath string
HostsPath string
LogPath string
Name string
Driver string
ExecDriver string
@@ -92,9 +101,12 @@ type Container struct {
VolumesRW map[string]bool
hostConfig *runconfig.HostConfig
activeLinks map[string]*links.Link
monitor *containerMonitor
execCommands *execStore
activeLinks map[string]*links.Link
monitor *containerMonitor
execCommands *execStore
// logDriver for closing
logDriver logger.Logger
logCopier *logger.Copier
AppliedVolumesFrom map[string]struct{}
}
@@ -255,18 +267,18 @@ func populateCommand(c *Container, env []string) error {
pid.HostPid = c.hostConfig.PidMode.IsHost()
// Build lists of devices allowed and created within the container.
userSpecifiedDevices := make([]*devices.Device, len(c.hostConfig.Devices))
userSpecifiedDevices := make([]*configs.Device, len(c.hostConfig.Devices))
for i, deviceMapping := range c.hostConfig.Devices {
device, err := devices.GetDevice(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions)
device, err := devices.DeviceFromPath(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions)
if err != nil {
return fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err)
}
device.Path = deviceMapping.PathInContainer
userSpecifiedDevices[i] = device
}
allowedDevices := append(devices.DefaultAllowedDevices, userSpecifiedDevices...)
allowedDevices := append(configs.DefaultAllowedDevices, userSpecifiedDevices...)
autoCreatedDevices := append(devices.DefaultAutoCreatedDevices, userSpecifiedDevices...)
autoCreatedDevices := append(configs.DefaultAutoCreatedDevices, userSpecifiedDevices...)
// TODO: this can be removed after lxc-conf is fully deprecated
lxcConfig, err := mergeLxcConfIntoOptions(c.hostConfig)
@@ -274,11 +286,34 @@ func populateCommand(c *Container, env []string) error {
return err
}
var rlimits []*ulimit.Rlimit
ulimits := c.hostConfig.Ulimits
// Merge ulimits with daemon defaults
ulIdx := make(map[string]*ulimit.Ulimit)
for _, ul := range ulimits {
ulIdx[ul.Name] = ul
}
for name, ul := range c.daemon.config.Ulimits {
if _, exists := ulIdx[name]; !exists {
ulimits = append(ulimits, ul)
}
}
for _, limit := range ulimits {
rl, err := limit.GetRlimit()
if err != nil {
return err
}
rlimits = append(rlimits, rl)
}
resources := &execdriver.Resources{
Memory: c.Config.Memory,
MemorySwap: c.Config.MemorySwap,
CpuShares: c.Config.CpuShares,
Cpuset: c.Config.Cpuset,
Memory: c.hostConfig.Memory,
MemorySwap: c.hostConfig.MemorySwap,
CpuShares: c.hostConfig.CpuShares,
CpusetCpus: c.hostConfig.CpusetCpus,
Rlimits: rlimits,
}
processConfig := execdriver.ProcessConfig{
@@ -311,6 +346,7 @@ func populateCommand(c *Container, env []string) error {
MountLabel: c.GetMountLabel(),
LxcConfig: lxcConfig,
AppArmorProfile: c.AppArmorProfile,
CgroupParent: c.hostConfig.CgroupParent,
}
return nil
@@ -324,7 +360,11 @@ func (container *Container) Start() (err error) {
return nil
}
// if we encounter and error during start we need to ensure that any other
if container.removalInProgress || container.Dead {
return fmt.Errorf("Container is marked for removal and cannot be started.")
}
// if we encounter an error during start we need to ensure that any other
// setup has been cleaned up properly
defer func() {
if err != nil {
@@ -457,11 +497,18 @@ func (container *Container) buildHostsFiles(IP string) error {
for linkAlias, child := range children {
_, alias := path.Split(linkAlias)
extraContent = append(extraContent, etchosts.Record{Hosts: alias, IP: child.NetworkSettings.IPAddress})
// allow access to the linked container via the alias, real name, and container hostname
aliasList := alias + " " + child.Config.Hostname
// only add the name if alias isn't equal to the name
if alias != child.Name[1:] {
aliasList = aliasList + " " + child.Name[1:]
}
extraContent = append(extraContent, etchosts.Record{Hosts: aliasList, IP: child.NetworkSettings.IPAddress})
}
for _, extraHost := range container.hostConfig.ExtraHosts {
parts := strings.Split(extraHost, ":")
// allow IPv6 addresses in extra hosts; only split on first ":"
parts := strings.SplitN(extraHost, ":", 2)
extraContent = append(extraContent, etchosts.Record{Hosts: parts[0], IP: parts[1]})
}
@@ -652,6 +699,16 @@ func (container *Container) KillSig(sig int) error {
return container.daemon.Kill(container, sig)
}
// Wrapper aroung KillSig() suppressing "no such process" error.
func (container *Container) killPossiblyDeadProcess(sig int) error {
err := container.KillSig(sig)
if err == syscall.ESRCH {
log.Debugf("Cannot kill process (pid=%d) with signal %d: no such process.", container.GetPid(), sig)
return nil
}
return err
}
func (container *Container) Pause() error {
if container.IsPaused() {
return fmt.Errorf("Container %s is already paused", container.ID)
@@ -678,7 +735,7 @@ func (container *Container) Kill() error {
}
// 1. Send SIGKILL
if err := container.KillSig(9); err != nil {
if err := container.killPossiblyDeadProcess(9); err != nil {
return err
}
@@ -686,9 +743,12 @@ func (container *Container) Kill() error {
if _, err := container.WaitStop(10 * time.Second); err != nil {
// Ensure that we don't kill ourselves
if pid := container.GetPid(); pid != 0 {
log.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID))
log.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", common.TruncateID(container.ID))
if err := syscall.Kill(pid, 9); err != nil {
return err
if err != syscall.ESRCH {
return err
}
log.Debugf("Cannot kill process (pid=%d) with signal 9: no such process.", pid)
}
}
}
@@ -703,9 +763,9 @@ func (container *Container) Stop(seconds int) error {
}
// 1. Send a SIGTERM
if err := container.KillSig(15); err != nil {
if err := container.killPossiblyDeadProcess(15); err != nil {
log.Infof("Failed to send SIGTERM to the process, force killing")
if err := container.KillSig(9); err != nil {
if err := container.killPossiblyDeadProcess(9); err != nil {
return err
}
}
@@ -848,7 +908,7 @@ func (container *Container) GetSize() (int64, int64) {
)
if err := container.Mount(); err != nil {
log.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err)
log.Errorf("Failed to compute size of container rootfs %s: %s", container.ID, err)
return sizeRw, sizeRootfs
}
defer container.Unmount()
@@ -856,14 +916,14 @@ func (container *Container) GetSize() (int64, int64) {
initID := fmt.Sprintf("%s-init", container.ID)
sizeRw, err = driver.DiffSize(container.ID, initID)
if err != nil {
log.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
log.Errorf("Driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
// FIXME: GetSize should return an error. Not changing it now in case
// there is a side-effect.
sizeRw = -1
}
if _, err = os.Stat(container.basefs); err != nil {
if sizeRootfs, err = utils.TreeSize(container.basefs); err != nil {
if sizeRootfs, err = directory.Size(container.basefs); err != nil {
sizeRootfs = -1
}
}
@@ -925,7 +985,7 @@ func (container *Container) Exposes(p nat.Port) bool {
return exists
}
func (container *Container) GetPtyMaster() (*os.File, error) {
func (container *Container) GetPtyMaster() (libcontainer.Console, error) {
ttyConsole, ok := container.command.ProcessConfig.Terminal.(execdriver.TtyTerminal)
if !ok {
return nil, ErrNoTTY
@@ -1113,7 +1173,12 @@ func (container *Container) updateParentsHosts() error {
if ref.ParentID == "0" {
continue
}
c := container.daemon.Get(ref.ParentID)
c, err := container.daemon.Get(ref.ParentID)
if err != nil {
log.Error(err)
}
if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() {
log.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress)
if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, ref.Name); err != nil {
@@ -1163,6 +1228,7 @@ func (container *Container) initializeNetworking() error {
if err != nil {
return err
}
container.HostnamePath = nc.HostnamePath
container.HostsPath = nc.HostsPath
container.ResolvConfPath = nc.ResolvConfPath
container.Config.Hostname = nc.Config.Hostname
@@ -1182,15 +1248,15 @@ func (container *Container) initializeNetworking() error {
// Make sure the config is compatible with the current kernel
func (container *Container) verifyDaemonSettings() {
if container.Config.Memory > 0 && !container.daemon.sysInfo.MemoryLimit {
log.Infof("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
log.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.")
container.Config.Memory = 0
}
if container.Config.Memory > 0 && !container.daemon.sysInfo.SwapLimit {
log.Infof("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
log.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.")
container.Config.MemorySwap = -1
}
if container.daemon.sysInfo.IPv4ForwardingDisabled {
log.Infof("WARNING: IPv4 forwarding is disabled. Networking will not work")
log.Warnf("IPv4 forwarding is disabled. Networking will not work")
}
}
@@ -1208,7 +1274,7 @@ func (container *Container) setupLinkedContainers() ([]string, error) {
container.activeLinks = make(map[string]*links.Link, len(children))
// If we encounter an error make sure that we rollback any network
// config and ip table changes
// config and iptables changes
rollback := func() {
for _, link := range container.activeLinks {
link.Disable()
@@ -1301,20 +1367,44 @@ func (container *Container) setupWorkingDirectory() error {
return nil
}
func (container *Container) startLoggingToDisk() error {
// Setup logging of stdout and stderr to disk
pth, err := container.logPath("json")
func (container *Container) startLogging() error {
cfg := container.hostConfig.LogConfig
if cfg.Type == "" {
cfg = container.daemon.defaultLogConfig
}
var l logger.Logger
switch cfg.Type {
case "json-file":
pth, err := container.logPath("json")
if err != nil {
return err
}
container.LogPath = pth
dl, err := jsonfilelog.New(pth)
if err != nil {
return err
}
l = dl
case "syslog":
dl, err := syslog.New(container.ID[:12])
if err != nil {
return err
}
l = dl
case "none":
return nil
default:
return fmt.Errorf("Unknown logging driver: %s", cfg.Type)
}
copier, err := logger.NewCopier(container.ID, map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l)
if err != nil {
return err
}
if err := container.daemon.LogToDisk(container.stdout, pth, "stdout"); err != nil {
return err
}
if err := container.daemon.LogToDisk(container.stderr, pth, "stderr"); err != nil {
return err
}
container.logCopier = copier
copier.Run()
container.logDriver = l
return nil
}
@@ -1382,9 +1472,9 @@ func (container *Container) GetMountLabel() string {
func (container *Container) getIpcContainer() (*Container, error) {
containerID := container.hostConfig.IpcMode.Container()
c := container.daemon.Get(containerID)
if c == nil {
return nil, fmt.Errorf("no such container to join IPC: %s", containerID)
c, err := container.daemon.Get(containerID)
if err != nil {
return nil, err
}
if !c.IsRunning() {
return nil, fmt.Errorf("cannot join IPC of a non running container: %s", containerID)
@@ -1399,9 +1489,9 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
if len(parts) != 2 {
return nil, fmt.Errorf("no container specified to join network")
}
nc := container.daemon.Get(parts[1])
if nc == nil {
return nil, fmt.Errorf("no such container to join network: %s", parts[1])
nc, err := container.daemon.Get(parts[1])
if err != nil {
return nil, err
}
if !nc.IsRunning() {
return nil, fmt.Errorf("cannot join network of a non running container: %s", parts[1])
@@ -1415,3 +1505,12 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
func (container *Container) Stats() (*execdriver.ResourceStats, error) {
return container.daemon.Stats(container)
}
func (c *Container) LogDriverType() string {
c.Lock()
defer c.Unlock()
if c.hostConfig.LogConfig.Type == "" {
return c.daemon.defaultLogConfig.Type
}
return c.hostConfig.LogConfig.Type
}
+14 -13
View File
@@ -16,18 +16,19 @@ func (daemon *Daemon) ContainerCopy(job *engine.Job) engine.Status {
resource = job.Args[1]
)
if container := daemon.Get(name); container != nil {
data, err := container.Copy(resource)
if err != nil {
return job.Error(err)
}
defer data.Close()
if _, err := io.Copy(job.Stdout, data); err != nil {
return job.Error(err)
}
return engine.StatusOK
container, err := daemon.Get(name)
if err != nil {
return job.Error(err)
}
return job.Errorf("No such container: %s", name)
data, err := container.Copy(resource)
if err != nil {
return job.Error(err)
}
defer data.Close()
if _, err := io.Copy(job.Stdout, data); err != nil {
return job.Error(err)
}
return engine.StatusOK
}
+22 -17
View File
@@ -2,6 +2,7 @@ package daemon
import (
"fmt"
"strings"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
@@ -18,28 +19,29 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
} else if len(job.Args) > 1 {
return job.Errorf("Usage: %s", job.Name)
}
config := runconfig.ContainerConfigFromJob(job)
if config.Memory != 0 && config.Memory < 4194304 {
hostConfig := runconfig.ContainerHostConfigFromJob(job)
if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
return job.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name())
}
if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 {
return job.Errorf("Minimum memory limit allowed is 4MB")
}
if config.Memory > 0 && !daemon.SystemConfig().MemoryLimit {
if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit {
job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n")
config.Memory = 0
hostConfig.Memory = 0
}
if config.Memory > 0 && !daemon.SystemConfig().SwapLimit {
if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit {
job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config.MemorySwap = -1
hostConfig.MemorySwap = -1
}
if config.Memory > 0 && config.MemorySwap > 0 && config.MemorySwap < config.Memory {
if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory {
return job.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n")
}
var hostConfig *runconfig.HostConfig
if job.EnvExists("HostConfig") {
hostConfig = runconfig.ContainerHostConfigFromJob(job)
} else {
// Older versions of the API don't provide a HostConfig.
hostConfig = nil
if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 {
return job.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n")
}
container, buildWarnings, err := daemon.Create(config, hostConfig, name)
@@ -91,7 +93,10 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
return nil, nil, err
}
if hostConfig != nil && hostConfig.SecurityOpt == nil {
if hostConfig == nil {
hostConfig = &runconfig.HostConfig{}
}
if hostConfig.SecurityOpt == nil {
hostConfig.SecurityOpt, err = daemon.GenerateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode)
if err != nil {
return nil, nil, err
@@ -129,9 +134,9 @@ func (daemon *Daemon) GenerateSecurityOpt(ipcMode runconfig.IpcMode, pidMode run
return label.DisableSecOpt(), nil
}
if ipcContainer := ipcMode.Container(); ipcContainer != "" {
c := daemon.Get(ipcContainer)
if c == nil {
return nil, fmt.Errorf("no such container to join IPC: %s", ipcContainer)
c, err := daemon.Get(ipcContainer)
if err != nil {
return nil, err
}
if !c.IsRunning() {
return nil, fmt.Errorf("cannot join IPC of a non running container: %s", ipcContainer)
+130 -108
View File
@@ -18,19 +18,19 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/execdrivers"
"github.com/docker/docker/daemon/execdriver/lxc"
"github.com/docker/docker/daemon/graphdriver"
_ "github.com/docker/docker/daemon/graphdriver/vfs"
_ "github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/daemon/networkdriver/portallocator"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/broadcastwriter"
"github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/namesgenerator"
@@ -88,23 +88,24 @@ func (c *contStore) List() []*Container {
}
type Daemon struct {
ID string
repository string
sysInitPath string
containers *contStore
execCommands *execStore
graph *graph.Graph
repositories *graph.TagStore
idIndex *truncindex.TruncIndex
sysInfo *sysinfo.SysInfo
volumes *volumes.Repository
eng *engine.Engine
config *Config
containerGraph *graphdb.Database
driver graphdriver.Driver
execDriver execdriver.Driver
trustStore *trust.TrustStore
statsCollector *statsCollector
ID string
repository string
sysInitPath string
containers *contStore
execCommands *execStore
graph *graph.Graph
repositories *graph.TagStore
idIndex *truncindex.TruncIndex
sysInfo *sysinfo.SysInfo
volumes *volumes.Repository
eng *engine.Engine
config *Config
containerGraph *graphdb.Database
driver graphdriver.Driver
execDriver execdriver.Driver
trustStore *trust.TrustStore
statsCollector *statsCollector
defaultLogConfig runconfig.LogConfig
}
// Install installs daemon capabilities to eng.
@@ -155,28 +156,40 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
return nil
}
// Get looks for a container by the specified ID or name, and returns it.
// If the container is not found, or if an error occurs, nil is returned.
func (daemon *Daemon) Get(name string) *Container {
id, err := daemon.idIndex.Get(name)
if err == nil {
return daemon.containers.Get(id)
// Get looks for a container using the provided information, which could be
// one of the following inputs from the caller:
// - A full container ID, which will exact match a container in daemon's list
// - A container name, which will only exact match via the GetByName() function
// - A partial container ID prefix (e.g. short ID) of any length that is
// unique enough to only return a single container object
// If none of these searches succeed, an error is returned
func (daemon *Daemon) Get(prefixOrName string) (*Container, error) {
if containerByID := daemon.containers.Get(prefixOrName); containerByID != nil {
// prefix is an exact match to a full container ID
return containerByID, nil
}
if c, _ := daemon.GetByName(name); c != nil {
return c
// GetByName will match only an exact name provided; we ignore errors
containerByName, _ := daemon.GetByName(prefixOrName)
containerId, indexError := daemon.idIndex.Get(prefixOrName)
if containerByName != nil {
// prefix is an exact match to a full container Name
return containerByName, nil
}
if err == truncindex.ErrDuplicateID {
log.Errorf("Short ID %s is ambiguous: please retry with more characters or use the full ID.\n", name)
if containerId != "" {
// prefix is a fuzzy match to a container ID
return daemon.containers.Get(containerId), nil
}
return nil
return nil, indexError
}
// Exists returns a true if a container of the specified ID or name exists,
// false otherwise.
func (daemon *Daemon) Exists(id string) bool {
return daemon.Get(id) != nil
c, _ := daemon.Get(id)
return c != nil
}
func (daemon *Daemon) containerRoot(id string) string {
@@ -274,19 +287,8 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
if err := container.ToDisk(); err != nil {
log.Debugf("saving stopped state to disk %s", err)
}
info := daemon.execDriver.Info(container.ID)
if !info.IsRunning() {
log.Debugf("Container %s was supposed to be running but is not.", container.ID)
log.Debugf("Marking as stopped")
container.SetStopped(&execdriver.ExitStatus{ExitCode: -127})
if err := container.ToDisk(); err != nil {
return err
}
}
}
return nil
}
@@ -332,7 +334,7 @@ func (daemon *Daemon) restore() error {
for _, v := range dir {
id := v.Name()
container, err := daemon.load(id)
if !debug {
if !debug && log.GetLevel() == log.InfoLevel {
fmt.Print(".")
}
if err != nil {
@@ -354,7 +356,7 @@ func (daemon *Daemon) restore() error {
if entities := daemon.containerGraph.List("/", -1); entities != nil {
for _, p := range entities.Paths() {
if !debug {
if !debug && log.GetLevel() == log.InfoLevel {
fmt.Print(".")
}
@@ -406,7 +408,9 @@ func (daemon *Daemon) restore() error {
}
if !debug {
fmt.Println()
if log.GetLevel() == log.InfoLevel {
fmt.Println()
}
log.Infof("Loading containers: done.")
}
@@ -428,7 +432,9 @@ func (daemon *Daemon) setupResolvconfWatcher() error {
for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
if event.Name == "/etc/resolv.conf" &&
(event.Op&fsnotify.Write == fsnotify.Write ||
event.Op&fsnotify.Create == fsnotify.Create) {
// verify a real change happened before we go further--a file write may have happened
// without an actual change to the file
updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged()
@@ -461,7 +467,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error {
}
}()
if err := watcher.Add("/etc/resolv.conf"); err != nil {
if err := watcher.Add("/etc"); err != nil {
return err
}
return nil
@@ -499,7 +505,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.
func (daemon *Daemon) generateIdAndName(name string) (string, string, error) {
var (
err error
id = utils.GenerateRandomID()
id = common.GenerateRandomID()
)
if name == "" {
@@ -544,7 +550,7 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) {
nameAsKnownByUser := strings.TrimPrefix(name, "/")
return "", fmt.Errorf(
"Conflict. The name %q is already in use by container %s. You have to delete (or rename) that container to be able to reuse that name.", nameAsKnownByUser,
utils.TruncateID(conflictingContainer.ID))
common.TruncateID(conflictingContainer.ID))
}
}
return name, nil
@@ -567,7 +573,7 @@ func (daemon *Daemon) generateNewName(id string) (string, error) {
return name, nil
}
name = "/" + utils.TruncateID(id)
name = "/" + common.TruncateID(id)
if _, err := daemon.containerGraph.Set(name, id); err != nil {
return "", err
}
@@ -715,9 +721,9 @@ func (daemon *Daemon) Children(name string) (map[string]*Container, error) {
children := make(map[string]*Container)
err = daemon.containerGraph.Walk(name, func(p string, e *graphdb.Entity) error {
c := daemon.Get(e.ID())
if c == nil {
return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p)
c, err := daemon.Get(e.ID())
if err != nil {
return err
}
children[p] = c
return nil
@@ -754,10 +760,18 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig.
if err != nil {
return err
}
child := daemon.Get(parts["name"])
if child == nil {
child, err := daemon.Get(parts["name"])
if err != nil {
//An error from daemon.Get() means this name could not be found
return fmt.Errorf("Could not get container for %s", parts["name"])
}
for child.hostConfig.NetworkMode.IsContainer() {
parts := strings.SplitN(string(child.hostConfig.NetworkMode), ":", 2)
child, err = daemon.Get(parts[1])
if err != nil {
return fmt.Errorf("Could not get container for %s", parts[1])
}
}
if child.hostConfig.NetworkMode.IsHost() {
return runconfig.ErrConflictHostNetworkAndLinks
}
@@ -834,9 +848,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
return nil, fmt.Errorf("Unable to get the full path to the TempDir (%s): %s", tmp, err)
}
os.Setenv("TMPDIR", realTmp)
if !config.EnableSelinuxSupport {
selinuxSetDisabled()
}
// get the canonical path to the Docker root directory
var realRoot string
@@ -860,13 +871,28 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
// Load storage driver
driver, err := graphdriver.New(config.Root, config.GraphOptions)
if err != nil {
return nil, err
return nil, fmt.Errorf("error intializing graphdriver: %v", err)
}
log.Debugf("Using graph driver %s", driver)
// register cleanup for graph driver
eng.OnShutdown(func() {
if err := driver.Cleanup(); err != nil {
log.Errorf("Error during graph storage driver.Cleanup(): %v", err)
}
})
// As Docker on btrfs and SELinux are incompatible at present, error on both being enabled
if selinuxEnabled() && config.EnableSelinuxSupport && driver.String() == "btrfs" {
return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver!")
if config.EnableSelinuxSupport {
if selinuxEnabled() {
// As Docker on btrfs and SELinux are incompatible at present, error on both being enabled
if driver.String() == "btrfs" {
return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver")
}
log.Debug("SELinux enabled successfully")
} else {
log.Warn("Docker could not enable SELinux on the host system")
}
} else {
selinuxSetDisabled()
}
daemonRepo := path.Join(config.Root, "containers")
@@ -940,6 +966,12 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
if err != nil {
return nil, err
}
// register graph close on shutdown
eng.OnShutdown(func() {
if err := graph.Close(); err != nil {
log.Errorf("Error during container graph.Close(): %v", err)
}
})
localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION))
sysInitPath := utils.DockerInitPath(localCopy)
@@ -962,30 +994,39 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
}
sysInfo := sysinfo.New(false)
ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo)
const runDir = "/var/run/docker"
ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, config.Root, sysInitPath, sysInfo)
if err != nil {
return nil, err
}
daemon := &Daemon{
ID: trustKey.PublicKey().KeyID(),
repository: daemonRepo,
containers: &contStore{s: make(map[string]*Container)},
execCommands: newExecStore(),
graph: g,
repositories: repositories,
idIndex: truncindex.NewTruncIndex([]string{}),
sysInfo: sysInfo,
volumes: volumes,
config: config,
containerGraph: graph,
driver: driver,
sysInitPath: sysInitPath,
execDriver: ed,
eng: eng,
trustStore: t,
statsCollector: newStatsCollector(1 * time.Second),
ID: trustKey.PublicKey().KeyID(),
repository: daemonRepo,
containers: &contStore{s: make(map[string]*Container)},
execCommands: newExecStore(),
graph: g,
repositories: repositories,
idIndex: truncindex.NewTruncIndex([]string{}),
sysInfo: sysInfo,
volumes: volumes,
config: config,
containerGraph: graph,
driver: driver,
sysInitPath: sysInitPath,
execDriver: ed,
eng: eng,
trustStore: t,
statsCollector: newStatsCollector(1 * time.Second),
defaultLogConfig: config.LogConfig,
}
eng.OnShutdown(func() {
if err := daemon.shutdown(); err != nil {
log.Errorf("Error during daemon.shutdown(): %v", err)
}
})
if err := daemon.restore(); err != nil {
return nil, err
}
@@ -995,25 +1036,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
return nil, err
}
// Setup shutdown handlers
// FIXME: can these shutdown handlers be registered closer to their source?
eng.OnShutdown(func() {
// FIXME: if these cleanup steps can be called concurrently, register
// them as separate handlers to speed up total shutdown time
if err := daemon.shutdown(); err != nil {
log.Errorf("daemon.shutdown(): %s", err)
}
if err := portallocator.ReleaseAll(); err != nil {
log.Errorf("portallocator.ReleaseAll(): %s", err)
}
if err := daemon.driver.Cleanup(); err != nil {
log.Errorf("daemon.driver.Cleanup(): %s", err.Error())
}
if err := daemon.containerGraph.Close(); err != nil {
log.Errorf("daemon.containerGraph.Close(): %s", err.Error())
}
})
return daemon, nil
}
@@ -1100,18 +1122,18 @@ func (daemon *Daemon) Stats(c *Container) (*execdriver.ResourceStats, error) {
}
func (daemon *Daemon) SubscribeToContainerStats(name string) (chan interface{}, error) {
c := daemon.Get(name)
if c == nil {
return nil, fmt.Errorf("no such container")
c, err := daemon.Get(name)
if err != nil {
return nil, err
}
ch := daemon.statsCollector.collect(c)
return ch, nil
}
func (daemon *Daemon) UnsubscribeToContainerStats(name string, ch chan interface{}) error {
c := daemon.Get(name)
if c == nil {
return fmt.Errorf("no such container")
c, err := daemon.Get(name)
if err != nil {
return err
}
daemon.statsCollector.unsubscribe(c, ch)
return nil
@@ -1214,11 +1236,11 @@ func checkKernel() error {
// the circumstances of pre-3.8 crashes are clearer.
// For details see http://github.com/docker/docker/issues/407
if k, err := kernel.GetKernelVersion(); err != nil {
log.Infof("WARNING: %s", err)
log.Warnf("%s", err)
} else {
if kernel.CompareKernelVersion(k, &kernel.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" {
log.Infof("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
log.Warnf("You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
}
}
}
+101
View File
@@ -0,0 +1,101 @@
package daemon
import (
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/truncindex"
"os"
"path"
"testing"
)
//
// https://github.com/docker/docker/issues/8069
//
func TestGet(t *testing.T) {
c1 := &Container{
ID: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
Name: "tender_bardeen",
}
c2 := &Container{
ID: "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de",
Name: "drunk_hawking",
}
c3 := &Container{
ID: "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57",
Name: "3cdbd1aa",
}
c4 := &Container{
ID: "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5",
Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
}
c5 := &Container{
ID: "d22d69a2b8960bf7fafdcba06e72d2febdba960bf7fafdcba06e72d2f9008b060b",
Name: "d22d69a2b896",
}
store := &contStore{
s: map[string]*Container{
c1.ID: c1,
c2.ID: c2,
c3.ID: c3,
c4.ID: c4,
c5.ID: c5,
},
}
index := truncindex.NewTruncIndex([]string{})
index.Add(c1.ID)
index.Add(c2.ID)
index.Add(c3.ID)
index.Add(c4.ID)
index.Add(c5.ID)
daemonTestDbPath := path.Join(os.TempDir(), "daemon_test.db")
graph, err := graphdb.NewSqliteConn(daemonTestDbPath)
if err != nil {
t.Fatalf("Failed to create daemon test sqlite database at %s", daemonTestDbPath)
}
graph.Set(c1.Name, c1.ID)
graph.Set(c2.Name, c2.ID)
graph.Set(c3.Name, c3.ID)
graph.Set(c4.Name, c4.ID)
graph.Set(c5.Name, c5.ID)
daemon := &Daemon{
containers: store,
idIndex: index,
containerGraph: graph,
}
if container, _ := daemon.Get("3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de"); container != c2 {
t.Fatal("Should explicitly match full container IDs")
}
if container, _ := daemon.Get("75fb0b8009"); container != c4 {
t.Fatal("Should match a partial ID")
}
if container, _ := daemon.Get("drunk_hawking"); container != c2 {
t.Fatal("Should match a full name")
}
// c3.Name is a partial match for both c3.ID and c2.ID
if c, _ := daemon.Get("3cdbd1aa"); c != c3 {
t.Fatal("Should match a full name even though it collides with another container's ID")
}
if container, _ := daemon.Get("d22d69a2b896"); container != c5 {
t.Fatal("Should match a container where the provided prefix is an exact match to the it's name, and is also a prefix for it's ID")
}
if _, err := daemon.Get("3cdbd1"); err == nil {
t.Fatal("Should return an error when provided a prefix that partially matches multiple container ID's")
}
if _, err := daemon.Get("nothing"); err == nil {
t.Fatal("Should return an error when provided a prefix that is neither a name or a partial match to an ID")
}
os.Remove(daemonTestDbPath)
}
+52 -15
View File
@@ -17,10 +17,10 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
removeVolume := job.GetenvBool("removeVolume")
removeLink := job.GetenvBool("removeLink")
forceRemove := job.GetenvBool("forceRemove")
container := daemon.Get(name)
if container == nil {
return job.Errorf("No such container: %s", name)
container, err := daemon.Get(name)
if err != nil {
return job.Error(err)
}
if removeLink {
@@ -36,7 +36,7 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
if pe == nil {
return job.Errorf("Cannot get parent %s for name %s", parent, name)
}
parentContainer := daemon.Get(pe.ID())
parentContainer, _ := daemon.Get(pe.ID())
if parentContainer != nil {
parentContainer.DisableLink(n)
@@ -61,8 +61,15 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
return job.Errorf("Conflict, You cannot remove a running container. Stop the container before attempting removal or use -f")
}
}
if err := daemon.Destroy(container); err != nil {
return job.Errorf("Cannot destroy container %s: %s", name, err)
if forceRemove {
if err := daemon.ForceRm(container); err != nil {
log.Errorf("Cannot destroy container %s: %v", name, err)
}
} else {
if err := daemon.Rm(container); err != nil {
return job.Errorf("Cannot destroy container %s: %v", name, err)
}
}
container.LogEvent("destroy")
if removeVolume {
@@ -81,9 +88,16 @@ func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) {
}
}
func (daemon *Daemon) Rm(container *Container) (err error) {
return daemon.commonRm(container, false)
}
func (daemon *Daemon) ForceRm(container *Container) (err error) {
return daemon.commonRm(container, true)
}
// Destroy unregisters a container from the daemon and cleanly removes its contents from the filesystem.
// FIXME: rename to Rm for consistency with the CLI command
func (daemon *Daemon) Destroy(container *Container) error {
func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err error) {
if container == nil {
return fmt.Errorf("The given container is <nil>")
}
@@ -93,19 +107,40 @@ func (daemon *Daemon) Destroy(container *Container) error {
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID)
}
if err := container.Stop(3); err != nil {
// Container state RemovalInProgress should be used to avoid races.
if err = container.SetRemovalInProgress(); err != nil {
return fmt.Errorf("Failed to set container state to RemovalInProgress: %s", err)
}
defer container.ResetRemovalInProgress()
if err = container.Stop(3); err != nil {
return err
}
// Deregister the container before removing its directory, to avoid race conditions
daemon.idIndex.Delete(container.ID)
daemon.containers.Delete(container.ID)
// Mark container dead. We don't want anybody to be restarting it.
container.SetDead()
// Save container state to disk. So that if error happens before
// container meta file got removed from disk, then a restart of
// docker should not make a dead container alive.
container.ToDisk()
// If force removal is required, delete container from various
// indexes even if removal failed.
defer func() {
if err != nil && forceRemove {
daemon.idIndex.Delete(container.ID)
daemon.containers.Delete(container.ID)
}
}()
container.derefVolumes()
if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
log.Debugf("Unable to remove container from link graph: %s", err)
}
if err := daemon.driver.Remove(container.ID); err != nil {
if err = daemon.driver.Remove(container.ID); err != nil {
return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.driver, container.ID, err)
}
@@ -114,15 +149,17 @@ func (daemon *Daemon) Destroy(container *Container) error {
return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", daemon.driver, initID, err)
}
if err := os.RemoveAll(container.root); err != nil {
if err = os.RemoveAll(container.root); err != nil {
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
}
if err := daemon.execDriver.Clean(container.ID); err != nil {
if err = daemon.execDriver.Clean(container.ID); err != nil {
return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err)
}
selinuxFreeLxcContexts(container.ProcessLabel)
daemon.idIndex.Delete(container.ID)
daemon.containers.Delete(container.ID)
return nil
}
+7 -8
View File
@@ -12,10 +12,10 @@ import (
"github.com/docker/docker/daemon/execdriver/lxc"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/broadcastwriter"
"github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
type execConfig struct {
@@ -74,7 +74,7 @@ func (execConfig *execConfig) Resize(h, w int) error {
}
func (d *Daemon) registerExecCommand(execConfig *execConfig) {
// Storing execs in container inorder to kill them gracefully whenever the container is stopped or removed.
// Storing execs in container in order to kill them gracefully whenever the container is stopped or removed.
execConfig.Container.execCommands.Add(execConfig.ID, execConfig)
// Storing execs in daemon for easy access via remote API.
d.execCommands.Add(execConfig.ID, execConfig)
@@ -97,10 +97,9 @@ func (d *Daemon) unregisterExecCommand(execConfig *execConfig) {
}
func (d *Daemon) getActiveContainer(name string) (*Container, error) {
container := d.Get(name)
if container == nil {
return nil, fmt.Errorf("No such container: %s", name)
container, err := d.Get(name)
if err != nil {
return nil, err
}
if !container.IsRunning() {
@@ -142,7 +141,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status {
}
execConfig := &execConfig{
ID: utils.GenerateRandomID(),
ID: common.GenerateRandomID(),
OpenStdin: config.AttachStdin,
OpenStdout: config.AttachStdout,
OpenStderr: config.AttachStderr,
@@ -219,7 +218,7 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
}
attachErr := d.attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr)
attachErr := d.Attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr)
execErr := make(chan error)
-2
View File
@@ -1,2 +0,0 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
Victor Vieux <vieux@docker.com> (@vieux)
+158 -9
View File
@@ -1,14 +1,22 @@
package execdriver
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/docker/docker/daemon/execdriver/native/template"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/devices"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/configs"
)
// Context is a generic key value pair that allows
@@ -39,7 +47,7 @@ type Terminal interface {
}
type TtyTerminal interface {
Master() *os.File
Master() libcontainer.Console
}
// ExitStatus provides exit reasons for a container.
@@ -98,14 +106,15 @@ type NetworkInterface struct {
}
type Resources struct {
Memory int64 `json:"memory"`
MemorySwap int64 `json:"memory_swap"`
CpuShares int64 `json:"cpu_shares"`
Cpuset string `json:"cpuset"`
Memory int64 `json:"memory"`
MemorySwap int64 `json:"memory_swap"`
CpuShares int64 `json:"cpu_shares"`
CpusetCpus string `json:"cpuset_cpus"`
Rlimits []*ulimit.Rlimit `json:"rlimits"`
}
type ResourceStats struct {
*libcontainer.ContainerStats
*libcontainer.Stats
Read time.Time `json:"read"`
MemoryLimit int64 `json:"memory_limit"`
SystemUsage uint64 `json:"system_usage"`
@@ -145,8 +154,8 @@ type Command struct {
Pid *Pid `json:"pid"`
Resources *Resources `json:"resources"`
Mounts []Mount `json:"mounts"`
AllowedDevices []*devices.Device `json:"allowed_devices"`
AutoCreatedDevices []*devices.Device `json:"autocreated_devices"`
AllowedDevices []*configs.Device `json:"allowed_devices"`
AutoCreatedDevices []*configs.Device `json:"autocreated_devices"`
CapAdd []string `json:"cap_add"`
CapDrop []string `json:"cap_drop"`
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
@@ -155,4 +164,144 @@ type Command struct {
MountLabel string `json:"mount_label"`
LxcConfig []string `json:"lxc_config"`
AppArmorProfile string `json:"apparmor_profile"`
CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command.
}
func InitContainer(c *Command) *configs.Config {
container := template.New()
container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env)
container.Cgroups.Name = c.ID
container.Cgroups.AllowedDevices = c.AllowedDevices
container.Readonlyfs = c.ReadonlyRootfs
container.Devices = c.AutoCreatedDevices
container.Rootfs = c.Rootfs
container.Readonlyfs = c.ReadonlyRootfs
// check to see if we are running in ramdisk to disable pivot root
container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
// Default parent cgroup is "docker". Override if required.
if c.CgroupParent != "" {
container.Cgroups.Parent = c.CgroupParent
}
return container
}
func getEnv(key string, env []string) string {
for _, pair := range env {
parts := strings.Split(pair, "=")
if parts[0] == key {
return parts[1]
}
}
return ""
}
func SetupCgroups(container *configs.Config, c *Command) error {
if c.Resources != nil {
container.Cgroups.CpuShares = c.Resources.CpuShares
container.Cgroups.Memory = c.Resources.Memory
container.Cgroups.MemoryReservation = c.Resources.Memory
container.Cgroups.MemorySwap = c.Resources.MemorySwap
container.Cgroups.CpusetCpus = c.Resources.CpusetCpus
}
return nil
}
// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo.
func getNetworkInterfaceStats(interfaceName string) (*libcontainer.NetworkInterface, error) {
out := &libcontainer.NetworkInterface{Name: interfaceName}
// This can happen if the network runtime information is missing - possible if the
// container was created by an old version of libcontainer.
if interfaceName == "" {
return out, nil
}
type netStatsPair struct {
// Where to write the output.
Out *uint64
// The network stats file to read.
File string
}
// Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
netStats := []netStatsPair{
{Out: &out.RxBytes, File: "tx_bytes"},
{Out: &out.RxPackets, File: "tx_packets"},
{Out: &out.RxErrors, File: "tx_errors"},
{Out: &out.RxDropped, File: "tx_dropped"},
{Out: &out.TxBytes, File: "rx_bytes"},
{Out: &out.TxPackets, File: "rx_packets"},
{Out: &out.TxErrors, File: "rx_errors"},
{Out: &out.TxDropped, File: "rx_dropped"},
}
for _, netStat := range netStats {
data, err := readSysfsNetworkStats(interfaceName, netStat.File)
if err != nil {
return nil, err
}
*(netStat.Out) = data
}
return out, nil
}
// Reads the specified statistics available under /sys/class/net/<EthInterface>/statistics
func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) {
data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile))
if err != nil {
return 0, err
}
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
}
func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) {
f, err := os.Open(filepath.Join(containerDir, "state.json"))
if err != nil {
return nil, err
}
defer f.Close()
type network struct {
Type string
HostInterfaceName string
}
state := struct {
CgroupPaths map[string]string `json:"cgroup_paths"`
Networks []network
}{}
if err := json.NewDecoder(f).Decode(&state); err != nil {
return nil, err
}
now := time.Now()
mgr := fs.Manager{Paths: state.CgroupPaths}
cstats, err := mgr.GetStats()
if err != nil {
return nil, err
}
stats := &libcontainer.Stats{CgroupStats: cstats}
// if the container does not have any memory limit specified set the
// limit to the machines memory
memoryLimit := containerMemoryLimit
if memoryLimit == 0 {
memoryLimit = machineMemory
}
for _, iface := range state.Networks {
switch iface.Type {
case "veth":
istats, err := getNetworkInterfaceStats(iface.HostInterfaceName)
if err != nil {
return nil, err
}
stats.Interfaces = append(stats.Interfaces, istats)
}
}
return &ResourceStats{
Stats: stats,
Read: now,
MemoryLimit: memoryLimit,
}, nil
}
+2 -2
View File
@@ -10,13 +10,13 @@ import (
"github.com/docker/docker/pkg/sysinfo"
)
func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
switch name {
case "lxc":
// we want to give the lxc driver the full docker root because it needs
// to access and write config and template files in /var/lib/docker/containers/*
// to be backwards compatible
return lxc.NewDriver(root, initPath, sysInfo.AppArmor)
return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor)
case "native":
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
}
-2
View File
@@ -1,2 +0,0 @@
# the LXC exec driver needs more maintainers and contributions
Dinesh Subhraveti <dineshs@altiscale.com> (@dineshs-altiscale)
+301 -32
View File
@@ -12,17 +12,22 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/kr/pty"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
sysinfo "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/utils"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/mount/nodes"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/system"
"github.com/docker/libcontainer/user"
"github.com/kr/pty"
)
const DriverName = "lxc"
@@ -30,23 +35,41 @@ const DriverName = "lxc"
var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver")
type driver struct {
root string // root path for the driver to use
initPath string
apparmor bool
sharedRoot bool
root string // root path for the driver to use
libPath string
initPath string
apparmor bool
sharedRoot bool
activeContainers map[string]*activeContainer
machineMemory int64
sync.Mutex
}
func NewDriver(root, initPath string, apparmor bool) (*driver, error) {
type activeContainer struct {
container *configs.Config
cmd *exec.Cmd
}
func NewDriver(root, libPath, initPath string, apparmor bool) (*driver, error) {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, err
}
// setup unconfined symlink
if err := linkLxcStart(root); err != nil {
return nil, err
}
meminfo, err := sysinfo.ReadMemInfo()
if err != nil {
return nil, err
}
return &driver{
apparmor: apparmor,
root: root,
initPath: initPath,
sharedRoot: rootIsShared(),
apparmor: apparmor,
root: root,
libPath: libPath,
initPath: initPath,
sharedRoot: rootIsShared(),
activeContainers: make(map[string]*activeContainer),
machineMemory: meminfo.MemTotal,
}, nil
}
@@ -57,8 +80,9 @@ func (d *driver) Name() string {
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
var (
term execdriver.Terminal
err error
term execdriver.Terminal
err error
dataPath = d.containerDir(c.ID)
)
if c.ProcessConfig.Tty {
@@ -67,6 +91,16 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
}
c.ProcessConfig.Terminal = term
container, err := d.createContainer(c)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
d.Lock()
d.activeContainers[c.ID] = &activeContainer{
container: container,
cmd: &c.ProcessConfig.Cmd,
}
d.Unlock()
c.Mounts = append(c.Mounts, execdriver.Mount{
Source: d.initPath,
@@ -87,11 +121,29 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
"-n", c.ID,
"-f", configPath,
}
// From lxc>=1.1 the default behavior is to daemonize containers after start
lxcVersion := version.Version(d.version())
if lxcVersion.GreaterThanOrEqualTo(version.Version("1.1")) {
params = append(params, "-F")
}
if c.Network.ContainerID != "" {
params = append(params,
"--share-net", c.Network.ContainerID,
)
}
if c.Ipc != nil {
if c.Ipc.ContainerID != "" {
params = append(params,
"--share-ipc", c.Ipc.ContainerID,
)
} else if c.Ipc.HostIpc {
params = append(params,
"--share-ipc", "1",
)
}
}
params = append(params,
"--",
@@ -141,7 +193,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
"unshare", "-m", "--", "/bin/sh", "-c", shellString,
}
}
log.Debugf("lxc params %s", params)
var (
name = params[0]
arg = params[1:]
@@ -153,7 +205,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
c.ProcessConfig.Path = aname
c.ProcessConfig.Args = append([]string{name}, arg...)
if err := nodes.CreateDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
if err := createDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
@@ -175,25 +227,228 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
close(waitLock)
}()
// Poll lxc for RUNNING status
pid, err := d.waitForStart(c, waitLock)
if err != nil {
terminate := func(terr error) (execdriver.ExitStatus, error) {
if c.ProcessConfig.Process != nil {
c.ProcessConfig.Process.Kill()
c.ProcessConfig.Wait()
}
return execdriver.ExitStatus{ExitCode: -1}, err
return execdriver.ExitStatus{ExitCode: -1}, terr
}
// Poll lxc for RUNNING status
pid, err := d.waitForStart(c, waitLock)
if err != nil {
return terminate(err)
}
cgroupPaths, err := cgroupPaths(c.ID)
if err != nil {
return terminate(err)
}
state := &libcontainer.State{
InitProcessPid: pid,
CgroupPaths: cgroupPaths,
}
f, err := os.Create(filepath.Join(dataPath, "state.json"))
if err != nil {
return terminate(err)
}
defer f.Close()
if err := json.NewEncoder(f).Encode(state); err != nil {
return terminate(err)
}
c.ContainerPid = pid
if startCallback != nil {
log.Debugf("Invoking startCallback")
startCallback(&c.ProcessConfig, pid)
}
oomKill := false
oomKillNotification, err := notifyOnOOM(cgroupPaths)
<-waitLock
return execdriver.ExitStatus{ExitCode: getExitCode(c)}, waitErr
if err == nil {
_, oomKill = <-oomKillNotification
log.Debugf("oomKill error %s waitErr %s", oomKill, waitErr)
} else {
log.Warnf("Your kernel does not support OOM notifications: %s", err)
}
// check oom error
exitCode := getExitCode(c)
if oomKill {
exitCode = 137
}
return execdriver.ExitStatus{ExitCode: exitCode, OOMKilled: oomKill}, waitErr
}
// copy from libcontainer
func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
dir := paths["memory"]
if dir == "" {
return nil, fmt.Errorf("There is no path for %q in state", "memory")
}
oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control"))
if err != nil {
return nil, err
}
fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
if syserr != 0 {
oomControl.Close()
return nil, syserr
}
eventfd := os.NewFile(fd, "eventfd")
eventControlPath := filepath.Join(dir, "cgroup.event_control")
data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd())
if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil {
eventfd.Close()
oomControl.Close()
return nil, err
}
ch := make(chan struct{})
go func() {
defer func() {
close(ch)
eventfd.Close()
oomControl.Close()
}()
buf := make([]byte, 8)
for {
if _, err := eventfd.Read(buf); err != nil {
return
}
// When a cgroup is destroyed, an event is sent to eventfd.
// So if the control path is gone, return instead of notifying.
if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) {
return
}
ch <- struct{}{}
}
}()
return ch, nil
}
// createContainer populates and configures the container type with the
// data provided by the execdriver.Command
func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) {
container := execdriver.InitContainer(c)
if err := execdriver.SetupCgroups(container, c); err != nil {
return nil, err
}
return container, nil
}
// Return an map of susbystem -> container cgroup
func cgroupPaths(containerId string) (map[string]string, error) {
subsystems, err := cgroups.GetAllSubsystems()
if err != nil {
return nil, err
}
log.Debugf("subsystems: %s", subsystems)
paths := make(map[string]string)
for _, subsystem := range subsystems {
cgroupRoot, cgroupDir, err := findCgroupRootAndDir(subsystem)
log.Debugf("cgroup path %s %s", cgroupRoot, cgroupDir)
if err != nil {
//unsupported subystem
continue
}
path := filepath.Join(cgroupRoot, cgroupDir, "lxc", containerId)
paths[subsystem] = path
}
return paths, nil
}
// this is copy from old libcontainer nodes.go
func createDeviceNodes(rootfs string, nodesToCreate []*configs.Device) error {
oldMask := syscall.Umask(0000)
defer syscall.Umask(oldMask)
for _, node := range nodesToCreate {
if err := createDeviceNode(rootfs, node); err != nil {
return err
}
}
return nil
}
// Creates the device node in the rootfs of the container.
func createDeviceNode(rootfs string, node *configs.Device) error {
var (
dest = filepath.Join(rootfs, node.Path)
parent = filepath.Dir(dest)
)
if err := os.MkdirAll(parent, 0755); err != nil {
return err
}
fileMode := node.FileMode
switch node.Type {
case 'c':
fileMode |= syscall.S_IFCHR
case 'b':
fileMode |= syscall.S_IFBLK
default:
return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path)
}
if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil && !os.IsExist(err) {
return fmt.Errorf("mknod %s %s", node.Path, err)
}
if err := syscall.Chown(dest, int(node.Uid), int(node.Gid)); err != nil {
return fmt.Errorf("chown %s to %d:%d", node.Path, node.Uid, node.Gid)
}
return nil
}
// setupUser changes the groups, gid, and uid for the user inside the container
// copy from libcontainer, cause not it's private
func setupUser(userSpec string) error {
// Set up defaults.
defaultExecUser := user.ExecUser{
Uid: syscall.Getuid(),
Gid: syscall.Getgid(),
Home: "/",
}
passwdPath, err := user.GetPasswdPath()
if err != nil {
return err
}
groupPath, err := user.GetGroupPath()
if err != nil {
return err
}
execUser, err := user.GetExecUserPath(userSpec, &defaultExecUser, passwdPath, groupPath)
if err != nil {
return err
}
if err := syscall.Setgroups(execUser.Sgids); err != nil {
return err
}
if err := system.Setgid(execUser.Gid); err != nil {
return err
}
if err := system.Setuid(execUser.Uid); err != nil {
return err
}
// if we didn't get HOME already, set it based on the user's HOME
if envHome := os.Getenv("HOME"); envHome == "" {
if err := os.Setenv("HOME", execUser.Home); err != nil {
return err
}
}
return nil
}
/// Return the exit code of the process
@@ -337,17 +592,25 @@ func (d *driver) Info(id string) execdriver.Info {
}
}
func findCgroupRootAndDir(subsystem string) (string, string, error) {
cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem)
if err != nil {
return "", "", err
}
cgroupDir, err := cgroups.GetThisCgroupDir(subsystem)
if err != nil {
return "", "", err
}
return cgroupRoot, cgroupDir, nil
}
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
pids := []int{}
// cpu is chosen because it is the only non optional subsystem in cgroups
subsystem := "cpu"
cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem)
if err != nil {
return pids, err
}
cgroupDir, err := cgroups.GetThisCgroupDir(subsystem)
cgroupRoot, cgroupDir, err := findCgroupRootAndDir(subsystem)
if err != nil {
return pids, err
}
@@ -407,8 +670,12 @@ func rootIsShared() bool {
return true
}
func (d *driver) containerDir(containerId string) string {
return path.Join(d.libPath, "containers", containerId)
}
func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
root := path.Join(d.root, "containers", c.ID, "config.lxc")
root := path.Join(d.containerDir(c.ID), "config.lxc")
fo, err := os.Create(root)
if err != nil {
@@ -434,7 +701,7 @@ func (d *driver) generateEnvConfig(c *execdriver.Command) error {
if err != nil {
return err
}
p := path.Join(d.root, "containers", c.ID, "config.env")
p := path.Join(d.libPath, "containers", c.ID, "config.env")
c.Mounts = append(c.Mounts, execdriver.Mount{
Source: p,
Destination: "/.dockerenv",
@@ -526,6 +793,8 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
}
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
return nil, fmt.Errorf("container stats are not supported with LXC")
if _, ok := d.activeContainers[id]; !ok {
return nil, fmt.Errorf("%s is not a key in active containers", id)
}
return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
}
+1 -5
View File
@@ -3,8 +3,6 @@ package lxc
import (
"fmt"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/namespaces"
"github.com/docker/libcontainer/utils"
)
@@ -12,9 +10,7 @@ func finalizeNamespace(args *InitArgs) error {
if err := utils.CloseExecFrom(3); err != nil {
return err
}
if err := namespaces.SetupUser(&libcontainer.Config{
User: args.User,
}); err != nil {
if err := setupUser(args.User); err != nil {
return fmt.Errorf("setup user %s", err)
}
if err := setupWorkingDirectory(args); err != nil {
+12 -13
View File
@@ -11,7 +11,6 @@ import (
nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template"
"github.com/docker/docker/utils"
"github.com/docker/libcontainer/label"
"github.com/docker/libcontainer/security/capabilities"
)
const LxcTemplate = `
@@ -52,7 +51,7 @@ lxc.cgroup.devices.allow = a
lxc.cgroup.devices.deny = a
#Allow the devices passed to us in the AllowedDevices list.
{{range $allowedDevice := .AllowedDevices}}
lxc.cgroup.devices.allow = {{$allowedDevice.GetCgroupAllowString}}
lxc.cgroup.devices.allow = {{$allowedDevice.CgroupString}}
{{end}}
{{end}}
@@ -108,8 +107,8 @@ lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}}
{{if .Resources.CpuShares}}
lxc.cgroup.cpu.shares = {{.Resources.CpuShares}}
{{end}}
{{if .Resources.Cpuset}}
lxc.cgroup.cpuset.cpus = {{.Resources.Cpuset}}
{{if .Resources.CpusetCpus}}
lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}}
{{end}}
{{end}}
@@ -126,7 +125,9 @@ lxc.network.ipv4 = {{.Network.Interface.IPAddress}}/{{.Network.Interface.IPPrefi
{{if .Network.Interface.Gateway}}
lxc.network.ipv4.gateway = {{.Network.Interface.Gateway}}
{{end}}
{{if .Network.Interface.MacAddress}}
lxc.network.hwaddr = {{.Network.Interface.MacAddress}}
{{end}}
{{if .ProcessConfig.Env}}
lxc.utsname = {{getHostname .ProcessConfig.Env}}
{{end}}
@@ -167,7 +168,7 @@ func keepCapabilities(adds []string, drops []string) ([]string, error) {
var newCaps []string
for _, cap := range caps {
log.Debugf("cap %s\n", cap)
realCap := capabilities.GetCapability(cap)
realCap := execdriver.GetCapability(cap)
numCap := fmt.Sprintf("%d", realCap.Value)
newCaps = append(newCaps, numCap)
}
@@ -178,13 +179,10 @@ func keepCapabilities(adds []string, drops []string) ([]string, error) {
func dropList(drops []string) ([]string, error) {
if utils.StringsContainsNoCase(drops, "all") {
var newCaps []string
for _, cap := range capabilities.GetAllCapabilities() {
log.Debugf("drop cap %s\n", cap)
realCap := capabilities.GetCapability(cap)
if realCap == nil {
return nil, fmt.Errorf("Invalid capability '%s'", cap)
}
numCap := fmt.Sprintf("%d", realCap.Value)
for _, capName := range execdriver.GetAllCapabilities() {
cap := execdriver.GetCapability(capName)
log.Debugf("drop cap %s\n", cap.Key)
numCap := fmt.Sprintf("%d", cap.Value)
newCaps = append(newCaps, numCap)
}
return newCaps, nil
@@ -194,6 +192,7 @@ func dropList(drops []string) ([]string, error) {
func isDirectory(source string) string {
f, err := os.Stat(source)
log.Debugf("dir: %s\n", source)
if err != nil {
if os.IsNotExist(err) {
return "dir"
+13 -13
View File
@@ -5,11 +5,6 @@ package lxc
import (
"bufio"
"fmt"
"github.com/docker/docker/daemon/execdriver"
nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template"
"github.com/docker/libcontainer/devices"
"github.com/docker/libcontainer/security/capabilities"
"github.com/syndtr/gocapability/capability"
"io/ioutil"
"math/rand"
"os"
@@ -17,6 +12,11 @@ import (
"strings"
"testing"
"time"
"github.com/docker/docker/daemon/execdriver"
nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template"
"github.com/docker/libcontainer/configs"
"github.com/syndtr/gocapability/capability"
)
func TestLXCConfig(t *testing.T) {
@@ -39,7 +39,7 @@ func TestLXCConfig(t *testing.T) {
cpu = cpuMin + rand.Intn(cpuMax-cpuMin)
)
driver, err := NewDriver(root, "", false)
driver, err := NewDriver(root, root, "", false)
if err != nil {
t.Fatal(err)
}
@@ -53,7 +53,7 @@ func TestLXCConfig(t *testing.T) {
Mtu: 1500,
Interface: nil,
},
AllowedDevices: make([]*devices.Device, 0),
AllowedDevices: make([]*configs.Device, 0),
ProcessConfig: execdriver.ProcessConfig{},
}
p, err := driver.generateLXCConfig(command)
@@ -76,7 +76,7 @@ func TestCustomLxcConfig(t *testing.T) {
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
driver, err := NewDriver(root, "", false)
driver, err := NewDriver(root, root, "", false)
if err != nil {
t.Fatal(err)
}
@@ -194,7 +194,7 @@ func TestCustomLxcConfigMounts(t *testing.T) {
}
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
driver, err := NewDriver(root, "", false)
driver, err := NewDriver(root, root, "", false)
if err != nil {
t.Fatal(err)
}
@@ -248,7 +248,7 @@ func TestCustomLxcConfigMisc(t *testing.T) {
}
defer os.RemoveAll(root)
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
driver, err := NewDriver(root, "", true)
driver, err := NewDriver(root, root, "", true)
if err != nil {
t.Fatal(err)
@@ -295,7 +295,7 @@ func TestCustomLxcConfigMisc(t *testing.T) {
grepFile(t, p, "lxc.cgroup.cpuset.cpus = 0,1")
container := nativeTemplate.New()
for _, cap := range container.Capabilities {
realCap := capabilities.GetCapability(cap)
realCap := execdriver.GetCapability(cap)
numCap := fmt.Sprintf("%d", realCap.Value)
if cap != "MKNOD" && cap != "KILL" {
grepFile(t, p, fmt.Sprintf("lxc.cap.keep = %s", numCap))
@@ -313,7 +313,7 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) {
}
defer os.RemoveAll(root)
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
driver, err := NewDriver(root, "", false)
driver, err := NewDriver(root, root, "", false)
if err != nil {
t.Fatal(err)
}
@@ -359,7 +359,7 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) {
grepFile(t, p, "lxc.cgroup.cpuset.cpus = 0,1")
container := nativeTemplate.New()
for _, cap := range container.Capabilities {
realCap := capabilities.GetCapability(cap)
realCap := execdriver.GetCapability(cap)
numCap := fmt.Sprintf("%d", realCap.Value)
if cap != "MKNOD" && cap != "KILL" {
grepFile(t, p, fmt.Sprintf("lxc.cap.keep = %s", numCap))
+120 -86
View File
@@ -3,39 +3,25 @@
package native
import (
"errors"
"fmt"
"os"
"os/exec"
"net"
"path/filepath"
"strings"
"syscall"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/native/template"
"github.com/docker/libcontainer"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/libcontainer/apparmor"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/devices"
"github.com/docker/libcontainer/mount"
"github.com/docker/libcontainer/security/capabilities"
"github.com/docker/libcontainer/utils"
)
// createContainer populates and configures the container type with the
// data provided by the execdriver.Command
func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, error) {
container := template.New()
container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env)
container.Tty = c.ProcessConfig.Tty
container.User = c.ProcessConfig.User
container.WorkingDir = c.WorkingDir
container.Env = c.ProcessConfig.Env
container.Cgroups.Name = c.ID
container.Cgroups.AllowedDevices = c.AllowedDevices
container.MountConfig.DeviceNodes = c.AutoCreatedDevices
container.RootFs = c.Rootfs
container.MountConfig.ReadonlyFs = c.ReadonlyRootfs
// check to see if we are running in ramdisk to disable pivot root
container.MountConfig.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
container.RestrictSys = true
func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) {
container := execdriver.InitContainer(c)
if err := d.createIpc(container, c); err != nil {
return nil, err
@@ -50,6 +36,14 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
}
if c.ProcessConfig.Privileged {
// clear readonly for /sys
for i := range container.Mounts {
if container.Mounts[i].Destination == "/sys" {
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
}
}
container.ReadonlyPaths = nil
container.MaskPaths = nil
if err := d.setPrivileged(container); err != nil {
return nil, err
}
@@ -63,7 +57,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
container.AppArmorProfile = c.AppArmorProfile
}
if err := d.setupCgroups(container, c); err != nil {
if err := execdriver.SetupCgroups(container, c); err != nil {
return nil, err
}
@@ -74,41 +68,52 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
if err := d.setupLabels(container, c); err != nil {
return nil, err
}
cmds := make(map[string]*exec.Cmd)
d.Lock()
for k, v := range d.activeContainers {
cmds[k] = v.cmd
}
d.Unlock()
d.setupRlimits(container, c)
return container, nil
}
func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Command) error {
func generateIfaceName() (string, error) {
for i := 0; i < 10; i++ {
name, err := utils.GenerateRandomName("veth", 7)
if err != nil {
continue
}
if _, err := net.InterfaceByName(name); err != nil {
if strings.Contains(err.Error(), "no such") {
return name, nil
}
return "", err
}
}
return "", errors.New("Failed to find name for new interface")
}
func (d *driver) createNetwork(container *configs.Config, c *execdriver.Command) error {
if c.Network.HostNetworking {
container.Namespaces.Remove(libcontainer.NEWNET)
container.Namespaces.Remove(configs.NEWNET)
return nil
}
container.Networks = []*libcontainer.Network{
container.Networks = []*configs.Network{
{
Mtu: c.Network.Mtu,
Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0),
Gateway: "localhost",
Type: "loopback",
Type: "loopback",
},
}
iName, err := generateIfaceName()
if err != nil {
return err
}
if c.Network.Interface != nil {
vethNetwork := libcontainer.Network{
Mtu: c.Network.Mtu,
Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen),
MacAddress: c.Network.Interface.MacAddress,
Gateway: c.Network.Interface.Gateway,
Type: "veth",
Bridge: c.Network.Interface.Bridge,
VethPrefix: "veth",
vethNetwork := configs.Network{
Name: "eth0",
HostInterfaceName: iName,
Mtu: c.Network.Mtu,
Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen),
MacAddress: c.Network.Interface.MacAddress,
Gateway: c.Network.Interface.Gateway,
Type: "veth",
Bridge: c.Network.Interface.Bridge,
}
if c.Network.Interface.GlobalIPv6Address != "" {
vethNetwork.IPv6Address = fmt.Sprintf("%s/%d", c.Network.Interface.GlobalIPv6Address, c.Network.Interface.GlobalIPv6PrefixLen)
@@ -122,21 +127,24 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
active := d.activeContainers[c.Network.ContainerID]
d.Unlock()
if active == nil || active.cmd.Process == nil {
if active == nil {
return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
}
cmd := active.cmd
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
container.Namespaces.Add(libcontainer.NEWNET, nspath)
state, err := active.State()
if err != nil {
return err
}
container.Namespaces.Add(configs.NEWNET, state.NamespacePaths[configs.NEWNET])
}
return nil
}
func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command) error {
func (d *driver) createIpc(container *configs.Config, c *execdriver.Command) error {
if c.Ipc.HostIpc {
container.Namespaces.Remove(libcontainer.NEWIPC)
container.Namespaces.Remove(configs.NEWIPC)
return nil
}
@@ -145,37 +153,38 @@ func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command
active := d.activeContainers[c.Ipc.ContainerID]
d.Unlock()
if active == nil || active.cmd.Process == nil {
if active == nil {
return fmt.Errorf("%s is not a valid running container to join", c.Ipc.ContainerID)
}
cmd := active.cmd
container.Namespaces.Add(libcontainer.NEWIPC, filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc"))
state, err := active.State()
if err != nil {
return err
}
container.Namespaces.Add(configs.NEWIPC, state.NamespacePaths[configs.NEWIPC])
}
return nil
}
func (d *driver) createPid(container *libcontainer.Config, c *execdriver.Command) error {
func (d *driver) createPid(container *configs.Config, c *execdriver.Command) error {
if c.Pid.HostPid {
container.Namespaces.Remove(libcontainer.NEWPID)
container.Namespaces.Remove(configs.NEWPID)
return nil
}
return nil
}
func (d *driver) setPrivileged(container *libcontainer.Config) (err error) {
container.Capabilities = capabilities.GetAllCapabilities()
func (d *driver) setPrivileged(container *configs.Config) (err error) {
container.Capabilities = execdriver.GetAllCapabilities()
container.Cgroups.AllowAllDevices = true
hostDeviceNodes, err := devices.GetHostDeviceNodes()
hostDevices, err := devices.HostDevices()
if err != nil {
return err
}
container.MountConfig.DeviceNodes = hostDeviceNodes
container.RestrictSys = false
container.Devices = hostDevices
if apparmor.IsEnabled() {
container.AppArmorProfile = "unconfined"
@@ -184,41 +193,66 @@ func (d *driver) setPrivileged(container *libcontainer.Config) (err error) {
return nil
}
func (d *driver) setCapabilities(container *libcontainer.Config, c *execdriver.Command) (err error) {
func (d *driver) setCapabilities(container *configs.Config, c *execdriver.Command) (err error) {
container.Capabilities, err = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop)
return err
}
func (d *driver) setupCgroups(container *libcontainer.Config, c *execdriver.Command) error {
if c.Resources != nil {
container.Cgroups.CpuShares = c.Resources.CpuShares
container.Cgroups.Memory = c.Resources.Memory
container.Cgroups.MemoryReservation = c.Resources.Memory
container.Cgroups.MemorySwap = c.Resources.MemorySwap
container.Cgroups.CpusetCpus = c.Resources.Cpuset
func (d *driver) setupRlimits(container *configs.Config, c *execdriver.Command) {
if c.Resources == nil {
return
}
return nil
}
func (d *driver) setupMounts(container *libcontainer.Config, c *execdriver.Command) error {
for _, m := range c.Mounts {
container.MountConfig.Mounts = append(container.MountConfig.Mounts, &mount.Mount{
Type: "bind",
Source: m.Source,
Destination: m.Destination,
Writable: m.Writable,
Private: m.Private,
Slave: m.Slave,
for _, rlimit := range c.Resources.Rlimits {
container.Rlimits = append(container.Rlimits, configs.Rlimit{
Type: rlimit.Type,
Hard: rlimit.Hard,
Soft: rlimit.Soft,
})
}
}
func (d *driver) setupMounts(container *configs.Config, c *execdriver.Command) error {
userMounts := make(map[string]struct{})
for _, m := range c.Mounts {
userMounts[m.Destination] = struct{}{}
}
// Filter out mounts that are overriden by user supplied mounts
var defaultMounts []*configs.Mount
for _, m := range container.Mounts {
if _, ok := userMounts[m.Destination]; !ok {
defaultMounts = append(defaultMounts, m)
}
}
container.Mounts = defaultMounts
for _, m := range c.Mounts {
dest, err := symlink.FollowSymlinkInScope(filepath.Join(c.Rootfs, m.Destination), c.Rootfs)
if err != nil {
return err
}
flags := syscall.MS_BIND | syscall.MS_REC
if !m.Writable {
flags |= syscall.MS_RDONLY
}
if m.Slave {
flags |= syscall.MS_SLAVE
}
container.Mounts = append(container.Mounts, &configs.Mount{
Source: m.Source,
Destination: dest,
Device: "bind",
Flags: flags,
})
}
return nil
}
func (d *driver) setupLabels(container *libcontainer.Config, c *execdriver.Command) error {
func (d *driver) setupLabels(container *configs.Config, c *execdriver.Command) error {
container.ProcessLabel = c.ProcessLabel
container.MountConfig.MountLabel = c.MountLabel
container.MountLabel = c.MountLabel
return nil
}
+199 -160
View File
@@ -17,16 +17,15 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/reexec"
sysinfo "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/term"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/apparmor"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
consolepkg "github.com/docker/libcontainer/console"
"github.com/docker/libcontainer/namespaces"
_ "github.com/docker/libcontainer/namespaces/nsenter"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/system"
"github.com/docker/libcontainer/utils"
)
const (
@@ -34,16 +33,12 @@ const (
Version = "0.2"
)
type activeContainer struct {
container *libcontainer.Config
cmd *exec.Cmd
}
type driver struct {
root string
initPath string
activeContainers map[string]*activeContainer
activeContainers map[string]libcontainer.Container
machineMemory int64
factory libcontainer.Factory
sync.Mutex
}
@@ -60,11 +55,26 @@ func NewDriver(root, initPath string) (*driver, error) {
if err := apparmor.InstallDefaultProfile(); err != nil {
return nil, err
}
cgm := libcontainer.Cgroupfs
if systemd.UseSystemd() {
cgm = libcontainer.SystemdCgroups
}
f, err := libcontainer.New(
root,
cgm,
libcontainer.InitPath(reexec.Self(), DriverName),
)
if err != nil {
return nil, err
}
return &driver{
root: root,
initPath: initPath,
activeContainers: make(map[string]*activeContainer),
activeContainers: make(map[string]libcontainer.Container),
machineMemory: meminfo.MemTotal,
factory: f,
}, nil
}
@@ -82,98 +92,164 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
var term execdriver.Terminal
p := &libcontainer.Process{
Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...),
Env: c.ProcessConfig.Env,
Cwd: c.WorkingDir,
User: c.ProcessConfig.User,
}
if c.ProcessConfig.Tty {
term, err = NewTtyConsole(&c.ProcessConfig, pipes)
rootuid, err := container.HostUID()
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
cons, err := p.NewConsole(rootuid)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
term, err = NewTtyConsole(cons, pipes, rootuid)
} else {
term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
p.Stdout = pipes.Stdout
p.Stderr = pipes.Stderr
r, w, err := os.Pipe()
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
if pipes.Stdin != nil {
go func() {
io.Copy(w, pipes.Stdin)
w.Close()
}()
p.Stdin = r
}
term = &execdriver.StdConsole{}
}
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
c.ProcessConfig.Terminal = term
cont, err := d.factory.Create(c.ID, container)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
d.Lock()
d.activeContainers[c.ID] = &activeContainer{
container: container,
cmd: &c.ProcessConfig.Cmd,
}
d.activeContainers[c.ID] = cont
d.Unlock()
var (
dataPath = filepath.Join(d.root, c.ID)
args = append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...)
)
if err := d.createContainerRoot(c.ID); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
defer d.cleanContainer(c.ID)
if err := d.writeContainerFile(container, c.ID); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
execOutputChan := make(chan execOutput, 1)
waitForStart := make(chan struct{})
go func() {
exitCode, err := namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, dataPath, args, func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string) *exec.Cmd {
c.ProcessConfig.Path = d.initPath
c.ProcessConfig.Args = append([]string{
DriverName,
"-console", console,
"-pipe", "3",
"-root", filepath.Join(d.root, c.ID),
"--",
}, args...)
// set this to nil so that when we set the clone flags anything else is reset
c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
}
c.ProcessConfig.ExtraFiles = []*os.File{child}
c.ProcessConfig.Env = container.Env
c.ProcessConfig.Dir = container.RootFs
return &c.ProcessConfig.Cmd
}, func() {
close(waitForStart)
if startCallback != nil {
c.ContainerPid = c.ProcessConfig.Process.Pid
startCallback(&c.ProcessConfig, c.ContainerPid)
}
})
execOutputChan <- execOutput{exitCode, err}
defer func() {
cont.Destroy()
d.cleanContainer(c.ID)
}()
select {
case execOutput := <-execOutputChan:
return execdriver.ExitStatus{ExitCode: execOutput.exitCode}, execOutput.err
case <-waitForStart:
break
if err := cont.Start(p); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
oomKill := false
state, err := libcontainer.GetState(filepath.Join(d.root, c.ID))
if err == nil {
oomKillNotification, err := libcontainer.NotifyOnOOM(state)
if err == nil {
_, oomKill = <-oomKillNotification
} else {
log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err)
if startCallback != nil {
pid, err := p.Pid()
if err != nil {
p.Signal(os.Kill)
p.Wait()
return execdriver.ExitStatus{ExitCode: -1}, err
}
} else {
log.Warnf("Failed to get container state, oom notify will not work: %s", err)
startCallback(&c.ProcessConfig, pid)
}
// wait for the container to exit.
execOutput := <-execOutputChan
return execdriver.ExitStatus{ExitCode: execOutput.exitCode, OOMKilled: oomKill}, execOutput.err
oom := notifyOnOOM(cont)
waitF := p.Wait
if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
// we need such hack for tracking processes with inerited fds,
// because cmd.Wait() waiting for all streams to be copied
waitF = waitInPIDHost(p, cont)
}
ps, err := waitF()
if err != nil {
execErr, ok := err.(*exec.ExitError)
if !ok {
return execdriver.ExitStatus{ExitCode: -1}, err
}
ps = execErr.ProcessState
}
cont.Destroy()
_, oomKill := <-oom
return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
}
func (d *driver) Kill(p *execdriver.Command, sig int) error {
return syscall.Kill(p.ProcessConfig.Process.Pid, syscall.Signal(sig))
// notifyOnOOM returns a channel that signals if the container received an OOM notification
// for any process. If it is unable to subscribe to OOM notifications then a closed
// channel is returned as it will be non-blocking and return the correct result when read.
func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
oom, err := container.NotifyOOM()
if err != nil {
log.Warnf("Your kernel does not support OOM notifications: %s", err)
c := make(chan struct{})
close(c)
return c
}
return oom
}
func killCgroupProcs(c libcontainer.Container) {
var procs []*os.Process
if err := c.Pause(); err != nil {
log.Warn(err)
}
pids, err := c.Processes()
if err != nil {
// don't care about childs if we can't get them, this is mostly because cgroup already deleted
log.Warnf("Failed to get processes from container %s: %v", c.ID(), err)
}
for _, pid := range pids {
if p, err := os.FindProcess(pid); err == nil {
procs = append(procs, p)
if err := p.Kill(); err != nil {
log.Warn(err)
}
}
}
if err := c.Resume(); err != nil {
log.Warn(err)
}
for _, p := range procs {
if _, err := p.Wait(); err != nil {
log.Warn(err)
}
}
}
func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
return func() (*os.ProcessState, error) {
pid, err := p.Pid()
if err != nil {
return nil, err
}
process, err := os.FindProcess(pid)
s, err := process.Wait()
if err != nil {
execErr, ok := err.(*exec.ExitError)
if !ok {
return s, err
}
s = execErr.ProcessState
}
killCgroupProcs(c)
p.Wait()
return s, err
}
}
func (d *driver) Kill(c *execdriver.Command, sig int) error {
active := d.activeContainers[c.ID]
if active == nil {
return fmt.Errorf("active container for %s does not exist", c.ID)
}
state, err := active.State()
if err != nil {
return err
}
return syscall.Kill(state.InitProcessPid, syscall.Signal(sig))
}
func (d *driver) Pause(c *execdriver.Command) error {
@@ -181,11 +257,7 @@ func (d *driver) Pause(c *execdriver.Command) error {
if active == nil {
return fmt.Errorf("active container for %s does not exist", c.ID)
}
active.container.Cgroups.Freezer = "FROZEN"
if systemd.UseSystemd() {
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
}
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
return active.Pause()
}
func (d *driver) Unpause(c *execdriver.Command) error {
@@ -193,47 +265,30 @@ func (d *driver) Unpause(c *execdriver.Command) error {
if active == nil {
return fmt.Errorf("active container for %s does not exist", c.ID)
}
active.container.Cgroups.Freezer = "THAWED"
if systemd.UseSystemd() {
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
}
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
return active.Resume()
}
func (d *driver) Terminate(p *execdriver.Command) error {
// lets check the start time for the process
state, err := libcontainer.GetState(filepath.Join(d.root, p.ID))
if err != nil {
if !os.IsNotExist(err) {
return err
}
// TODO: Remove this part for version 1.2.0
// This is added only to ensure smooth upgrades from pre 1.1.0 to 1.1.0
data, err := ioutil.ReadFile(filepath.Join(d.root, p.ID, "start"))
if err != nil {
// if we don't have the data on disk then we can assume the process is gone
// because this is only removed after we know the process has stopped
if os.IsNotExist(err) {
return nil
}
return err
}
state = &libcontainer.State{InitStartTime: string(data)}
}
currentStartTime, err := system.GetProcessStartTime(p.ProcessConfig.Process.Pid)
func (d *driver) Terminate(c *execdriver.Command) error {
defer d.cleanContainer(c.ID)
container, err := d.factory.Load(c.ID)
if err != nil {
return err
}
if state.InitStartTime == currentStartTime {
err = syscall.Kill(p.ProcessConfig.Process.Pid, 9)
syscall.Wait4(p.ProcessConfig.Process.Pid, nil, 0, nil)
defer container.Destroy()
state, err := container.State()
if err != nil {
return err
}
pid := state.InitProcessPid
currentStartTime, err := system.GetProcessStartTime(pid)
if err != nil {
return err
}
if state.InitProcessStartTime == currentStartTime {
err = syscall.Kill(pid, 9)
syscall.Wait4(pid, nil, 0, nil)
}
d.cleanContainer(p.ID)
return err
}
func (d *driver) Info(id string) execdriver.Info {
@@ -255,15 +310,10 @@ func (d *driver) GetPidsForContainer(id string) ([]int, error) {
if active == nil {
return nil, fmt.Errorf("active container for %s does not exist", id)
}
c := active.container.Cgroups
if systemd.UseSystemd() {
return systemd.GetPids(c)
}
return fs.GetPids(c)
return active.Processes()
}
func (d *driver) writeContainerFile(container *libcontainer.Config, id string) error {
func (d *driver) writeContainerFile(container *configs.Config, id string) error {
data, err := json.Marshal(container)
if err != nil {
return err
@@ -275,7 +325,7 @@ func (d *driver) cleanContainer(id string) error {
d.Lock()
delete(d.activeContainers, id)
d.Unlock()
return os.RemoveAll(filepath.Join(d.root, id, "container.json"))
return os.RemoveAll(filepath.Join(d.root, id))
}
func (d *driver) createContainerRoot(id string) error {
@@ -288,28 +338,24 @@ func (d *driver) Clean(id string) error {
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
c := d.activeContainers[id]
state, err := libcontainer.GetState(filepath.Join(d.root, id))
if err != nil {
if os.IsNotExist(err) {
return nil, execdriver.ErrNotRunning
}
return nil, err
if c == nil {
return nil, execdriver.ErrNotRunning
}
now := time.Now()
stats, err := libcontainer.GetStats(nil, state)
stats, err := c.Stats()
if err != nil {
return nil, err
}
memoryLimit := c.container.Cgroups.Memory
memoryLimit := c.Config().Cgroups.Memory
// if the container does not have any memory limit specified set the
// limit to the machines memory
if memoryLimit == 0 {
memoryLimit = d.machineMemory
}
return &execdriver.ResourceStats{
Read: now,
ContainerStats: stats,
MemoryLimit: memoryLimit,
Stats: stats,
Read: now,
MemoryLimit: memoryLimit,
}, nil
}
@@ -324,38 +370,31 @@ func getEnv(key string, env []string) string {
}
type TtyConsole struct {
MasterPty *os.File
console libcontainer.Console
}
func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) {
ptyMaster, console, err := consolepkg.CreateMasterAndConsole()
if err != nil {
return nil, err
}
func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes, rootuid int) (*TtyConsole, error) {
tty := &TtyConsole{
MasterPty: ptyMaster,
console: console,
}
if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil {
if err := tty.AttachPipes(pipes); err != nil {
tty.Close()
return nil, err
}
processConfig.Console = console
return tty, nil
}
func (t *TtyConsole) Master() *os.File {
return t.MasterPty
func (t *TtyConsole) Master() libcontainer.Console {
return t.console
}
func (t *TtyConsole) Resize(h, w int) error {
return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
}
func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error {
go func() {
if wb, ok := pipes.Stdout.(interface {
CloseWriters() error
@@ -363,12 +402,12 @@ func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) err
defer wb.CloseWriters()
}
io.Copy(pipes.Stdout, t.MasterPty)
io.Copy(pipes.Stdout, t.console)
}()
if pipes.Stdin != nil {
go func() {
io.Copy(t.MasterPty, pipes.Stdin)
io.Copy(t.console, pipes.Stdin)
pipes.Stdin.Close()
}()
@@ -378,5 +417,5 @@ func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) err
}
func (t *TtyConsole) Close() error {
return t.MasterPty.Close()
return t.console.Close()
}
+50 -40
View File
@@ -4,67 +4,77 @@ package native
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"syscall"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/reexec"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/namespaces"
_ "github.com/docker/libcontainer/nsenter"
"github.com/docker/libcontainer/utils"
)
const execCommandName = "nsenter-exec"
func init() {
reexec.Register(execCommandName, nsenterExec)
}
func nsenterExec() {
runtime.LockOSThread()
// User args are passed after '--' in the command line.
userArgs := findUserArgs()
config, err := loadConfigFromFd()
if err != nil {
log.Fatalf("docker-exec: unable to receive config from sync pipe: %s", err)
}
if err := namespaces.FinalizeSetns(config, userArgs); err != nil {
log.Fatalf("docker-exec: failed to exec: %s", err)
}
}
// TODO(vishh): Add support for running in priviledged mode and running as a different user.
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
active := d.activeContainers[c.ID]
if active == nil {
return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
}
state, err := libcontainer.GetState(filepath.Join(d.root, c.ID))
if err != nil {
return -1, fmt.Errorf("State unavailable for container with ID %s. The container may have been cleaned up already. Error: %s", c.ID, err)
}
var term execdriver.Terminal
var err error
p := &libcontainer.Process{
Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...),
Env: c.ProcessConfig.Env,
Cwd: c.WorkingDir,
User: c.ProcessConfig.User,
}
if processConfig.Tty {
term, err = NewTtyConsole(processConfig, pipes)
config := active.Config()
rootuid, err := config.HostUID()
if err != nil {
return -1, err
}
cons, err := p.NewConsole(rootuid)
if err != nil {
return -1, err
}
term, err = NewTtyConsole(cons, pipes, rootuid)
} else {
term, err = execdriver.NewStdConsole(processConfig, pipes)
p.Stdout = pipes.Stdout
p.Stderr = pipes.Stderr
p.Stdin = pipes.Stdin
term = &execdriver.StdConsole{}
}
if err != nil {
return -1, err
}
processConfig.Terminal = term
args := append([]string{processConfig.Entrypoint}, processConfig.Arguments...)
if err := active.Start(p); err != nil {
return -1, err
}
return namespaces.ExecIn(active.container, state, args, os.Args[0], "exec", processConfig.Stdin, processConfig.Stdout, processConfig.Stderr, processConfig.Console,
func(cmd *exec.Cmd) {
if startCallback != nil {
startCallback(&c.ProcessConfig, cmd.Process.Pid)
}
})
if startCallback != nil {
pid, err := p.Pid()
if err != nil {
p.Signal(os.Kill)
p.Wait()
return -1, err
}
startCallback(&c.ProcessConfig, pid)
}
ps, err := p.Wait()
if err != nil {
exitErr, ok := err.(*exec.ExitError)
if !ok {
return -1, err
}
ps = exitErr.ProcessState
}
return utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), nil
}
+2 -16
View File
@@ -2,13 +2,6 @@
package native
import (
"os"
"path/filepath"
"github.com/docker/libcontainer"
)
type info struct {
ID string
driver *driver
@@ -18,13 +11,6 @@ type info struct {
// pid file for a container. If the file exists then the
// container is currently running
func (i *info) IsRunning() bool {
if _, err := libcontainer.GetState(filepath.Join(i.driver.root, i.ID)); err == nil {
return true
}
// TODO: Remove this part for version 1.2.0
// This is added only to ensure smooth upgrades from pre 1.1.0 to 1.1.0
if _, err := os.Stat(filepath.Join(i.driver.root, i.ID, "pid")); err == nil {
return true
}
return false
_, ok := i.driver.activeContainers[i.ID]
return ok
}
+17 -32
View File
@@ -3,55 +3,40 @@
package native
import (
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/docker/docker/pkg/reexec"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/namespaces"
)
func init() {
reexec.Register(DriverName, initializer)
}
func fatal(err error) {
if lerr, ok := err.(libcontainer.Error); ok {
lerr.Detail(os.Stderr)
os.Exit(1)
}
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
func initializer() {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
var (
pipe = flag.Int("pipe", 0, "sync pipe fd")
console = flag.String("console", "", "console (pty slave) path")
root = flag.String("root", ".", "root path for configuration files")
)
flag.Parse()
var container *libcontainer.Config
f, err := os.Open(filepath.Join(*root, "container.json"))
factory, err := libcontainer.New("")
if err != nil {
writeError(err)
fatal(err)
}
if err := factory.StartInitialization(3); err != nil {
fatal(err)
}
if err := json.NewDecoder(f).Decode(&container); err != nil {
f.Close()
writeError(err)
}
f.Close()
rootfs, err := os.Getwd()
if err != nil {
writeError(err)
}
if err := namespaces.Init(container, rootfs, *console, os.NewFile(uintptr(*pipe), "child"), flag.Args()); err != nil {
writeError(err)
}
panic("Unreachable")
panic("unreachable")
}
func writeError(err error) {
@@ -1,14 +1,17 @@
package template
import (
"github.com/docker/libcontainer"
"syscall"
"github.com/docker/libcontainer/apparmor"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
)
const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
// New returns the docker default configuration for libcontainer
func New() *libcontainer.Config {
container := &libcontainer.Config{
func New() *configs.Config {
container := &configs.Config{
Capabilities: []string{
"CHOWN",
"DAC_OVERRIDE",
@@ -25,18 +28,64 @@ func New() *libcontainer.Config {
"KILL",
"AUDIT_WRITE",
},
Namespaces: libcontainer.Namespaces([]libcontainer.Namespace{
Namespaces: configs.Namespaces([]configs.Namespace{
{Type: "NEWNS"},
{Type: "NEWUTS"},
{Type: "NEWIPC"},
{Type: "NEWPID"},
{Type: "NEWNET"},
}),
Cgroups: &cgroups.Cgroup{
Cgroups: &configs.Cgroup{
Parent: "docker",
AllowAllDevices: false,
},
MountConfig: &libcontainer.MountConfig{},
Mounts: []*configs.Mount{
{
Source: "proc",
Destination: "/proc",
Device: "proc",
Flags: defaultMountFlags,
},
{
Source: "tmpfs",
Destination: "/dev",
Device: "tmpfs",
Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME,
Data: "mode=755",
},
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
},
{
Device: "tmpfs",
Source: "shm",
Destination: "/dev/shm",
Data: "mode=1777,size=65536k",
Flags: defaultMountFlags,
},
{
Source: "mqueue",
Destination: "/dev/mqueue",
Device: "mqueue",
Flags: defaultMountFlags,
},
{
Source: "sysfs",
Destination: "/sys",
Device: "sysfs",
Flags: defaultMountFlags | syscall.MS_RDONLY,
},
},
MaskPaths: []string{
"/proc/kcore",
},
ReadonlyPaths: []string{
"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
},
}
if apparmor.IsEnabled() {
+17 -24
View File
@@ -2,28 +2,21 @@
package native
import (
"encoding/json"
"os"
//func findUserArgs() []string {
//for i, a := range os.Args {
//if a == "--" {
//return os.Args[i+1:]
//}
//}
//return []string{}
//}
"github.com/docker/libcontainer"
)
func findUserArgs() []string {
for i, a := range os.Args {
if a == "--" {
return os.Args[i+1:]
}
}
return []string{}
}
// loadConfigFromFd loads a container's config from the sync pipe that is provided by
// fd 3 when running a process
func loadConfigFromFd() (*libcontainer.Config, error) {
var config *libcontainer.Config
if err := json.NewDecoder(os.NewFile(3, "child")).Decode(&config); err != nil {
return nil, err
}
return config, nil
}
//// loadConfigFromFd loads a container's config from the sync pipe that is provided by
//// fd 3 when running a process
//func loadConfigFromFd() (*configs.Config, error) {
//var config *libcontainer.Config
//if err := json.NewDecoder(os.NewFile(3, "child")).Decode(&config); err != nil {
//return nil, err
//}
//return config, nil
//}
+73 -3
View File
@@ -5,13 +5,83 @@ import (
"strings"
"github.com/docker/docker/utils"
"github.com/docker/libcontainer/security/capabilities"
"github.com/syndtr/gocapability/capability"
)
var capabilityList = Capabilities{
{Key: "SETPCAP", Value: capability.CAP_SETPCAP},
{Key: "SYS_MODULE", Value: capability.CAP_SYS_MODULE},
{Key: "SYS_RAWIO", Value: capability.CAP_SYS_RAWIO},
{Key: "SYS_PACCT", Value: capability.CAP_SYS_PACCT},
{Key: "SYS_ADMIN", Value: capability.CAP_SYS_ADMIN},
{Key: "SYS_NICE", Value: capability.CAP_SYS_NICE},
{Key: "SYS_RESOURCE", Value: capability.CAP_SYS_RESOURCE},
{Key: "SYS_TIME", Value: capability.CAP_SYS_TIME},
{Key: "SYS_TTY_CONFIG", Value: capability.CAP_SYS_TTY_CONFIG},
{Key: "MKNOD", Value: capability.CAP_MKNOD},
{Key: "AUDIT_WRITE", Value: capability.CAP_AUDIT_WRITE},
{Key: "AUDIT_CONTROL", Value: capability.CAP_AUDIT_CONTROL},
{Key: "MAC_OVERRIDE", Value: capability.CAP_MAC_OVERRIDE},
{Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN},
{Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN},
{Key: "SYSLOG", Value: capability.CAP_SYSLOG},
{Key: "CHOWN", Value: capability.CAP_CHOWN},
{Key: "NET_RAW", Value: capability.CAP_NET_RAW},
{Key: "DAC_OVERRIDE", Value: capability.CAP_DAC_OVERRIDE},
{Key: "FOWNER", Value: capability.CAP_FOWNER},
{Key: "DAC_READ_SEARCH", Value: capability.CAP_DAC_READ_SEARCH},
{Key: "FSETID", Value: capability.CAP_FSETID},
{Key: "KILL", Value: capability.CAP_KILL},
{Key: "SETGID", Value: capability.CAP_SETGID},
{Key: "SETUID", Value: capability.CAP_SETUID},
{Key: "LINUX_IMMUTABLE", Value: capability.CAP_LINUX_IMMUTABLE},
{Key: "NET_BIND_SERVICE", Value: capability.CAP_NET_BIND_SERVICE},
{Key: "NET_BROADCAST", Value: capability.CAP_NET_BROADCAST},
{Key: "IPC_LOCK", Value: capability.CAP_IPC_LOCK},
{Key: "IPC_OWNER", Value: capability.CAP_IPC_OWNER},
{Key: "SYS_CHROOT", Value: capability.CAP_SYS_CHROOT},
{Key: "SYS_PTRACE", Value: capability.CAP_SYS_PTRACE},
{Key: "SYS_BOOT", Value: capability.CAP_SYS_BOOT},
{Key: "LEASE", Value: capability.CAP_LEASE},
{Key: "SETFCAP", Value: capability.CAP_SETFCAP},
{Key: "WAKE_ALARM", Value: capability.CAP_WAKE_ALARM},
{Key: "BLOCK_SUSPEND", Value: capability.CAP_BLOCK_SUSPEND},
}
type (
CapabilityMapping struct {
Key string `json:"key,omitempty"`
Value capability.Cap `json:"value,omitempty"`
}
Capabilities []*CapabilityMapping
)
func (c *CapabilityMapping) String() string {
return c.Key
}
func GetCapability(key string) *CapabilityMapping {
for _, capp := range capabilityList {
if capp.Key == key {
cpy := *capp
return &cpy
}
}
return nil
}
func GetAllCapabilities() []string {
output := make([]string, len(capabilityList))
for i, capability := range capabilityList {
output[i] = capability.String()
}
return output
}
func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
var (
newCaps []string
allCaps = capabilities.GetAllCapabilities()
allCaps = GetAllCapabilities()
)
// look for invalid cap in the drop list
@@ -26,7 +96,7 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
// handle --cap-add=all
if utils.StringsContainsNoCase(adds, "all") {
basics = capabilities.GetAllCapabilities()
basics = allCaps
}
if !utils.StringsContainsNoCase(drops, "all") {
+17 -14
View File
@@ -11,20 +11,23 @@ func (daemon *Daemon) ContainerExport(job *engine.Job) engine.Status {
return job.Errorf("Usage: %s container_id", job.Name)
}
name := job.Args[0]
if container := daemon.Get(name); container != nil {
data, err := container.Export()
if err != nil {
return job.Errorf("%s: %s", name, err)
}
defer data.Close()
// Stream the entire contents of the container (basically a volatile snapshot)
if _, err := io.Copy(job.Stdout, data); err != nil {
return job.Errorf("%s: %s", name, err)
}
// FIXME: factor job-specific LogEvent to engine.Job.Run()
container.LogEvent("export")
return engine.StatusOK
container, err := daemon.Get(name)
if err != nil {
return job.Error(err)
}
return job.Errorf("No such container: %s", name)
data, err := container.Export()
if err != nil {
return job.Errorf("%s: %s", name, err)
}
defer data.Close()
// Stream the entire contents of the container (basically a volatile snapshot)
if _, err := io.Copy(job.Stdout, data); err != nil {
return job.Errorf("%s: %s", name, err)
}
// FIXME: factor job-specific LogEvent to engine.Job.Run()
container.LogEvent("export")
return engine.StatusOK
}
+51 -7
View File
@@ -23,6 +23,7 @@ package aufs
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
@@ -34,8 +35,9 @@ import (
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/directory"
mountpk "github.com/docker/docker/pkg/mount"
"github.com/docker/docker/utils"
"github.com/docker/libcontainer/label"
)
@@ -46,6 +48,9 @@ var (
graphdriver.FsMagicAufs,
}
backingFs = "<unknown>"
enableDirpermLock sync.Once
enableDirperm bool
)
func init() {
@@ -151,6 +156,7 @@ func (a *Driver) Status() [][2]string {
{"Root Dir", a.rootPath()},
{"Backing Filesystem", backingFs},
{"Dirs", fmt.Sprintf("%d", len(ids))},
{"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())},
}
}
@@ -215,7 +221,7 @@ func (a *Driver) Remove(id string) error {
defer a.Unlock()
if a.active[id] != 0 {
log.Errorf("Warning: removing active id %s", id)
log.Errorf("Removing active id %s", id)
}
// Make sure the dir is umounted first
@@ -319,7 +325,7 @@ func (a *Driver) applyDiff(id string, diff archive.ArchiveReader) error {
// relative to its base filesystem directory.
func (a *Driver) DiffSize(id, parent string) (size int64, err error) {
// AUFS doesn't need the parent layer to calculate the diff size.
return utils.TreeSize(path.Join(a.rootPath(), "diff", id))
return directory.Size(path.Join(a.rootPath(), "diff", id))
}
// ApplyDiff extracts the changeset from the given diff into the
@@ -377,7 +383,7 @@ func (a *Driver) mount(id, mountLabel string) error {
}
if err := a.aufsMount(layers, rw, target, mountLabel); err != nil {
return err
return fmt.Errorf("error creating aufs mount to %s: %v", target, err)
}
return nil
}
@@ -404,7 +410,7 @@ func (a *Driver) Cleanup() error {
for _, id := range ids {
if err := a.unmount(id); err != nil {
log.Errorf("Unmounting %s: %s", utils.TruncateID(id), err)
log.Errorf("Unmounting %s: %s", common.TruncateID(id), err)
}
}
@@ -421,7 +427,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
// Mount options are clipped to page size(4096 bytes). If there are more
// layers then these are remounted individually using append.
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-50) // room for xino & mountLabel
offset := 54
if useDirperm() {
offset += len("dirperm1")
}
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
firstMount := true
@@ -445,7 +455,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
}
if firstMount {
data := label.FormatMountLabel(fmt.Sprintf("%s,xino=/dev/shm/aufs.xino", string(b[:bp])), mountLabel)
opts := "dio,xino=/dev/shm/aufs.xino"
if useDirperm() {
opts += ",dirperm1"
}
data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel)
if err = mount("none", target, "aufs", 0, data); err != nil {
return
}
@@ -459,3 +473,33 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
return
}
// useDirperm checks dirperm1 mount option can be used with the current
// version of aufs.
func useDirperm() bool {
enableDirpermLock.Do(func() {
base, err := ioutil.TempDir("", "docker-aufs-base")
if err != nil {
log.Errorf("error checking dirperm1: %v", err)
return
}
defer os.RemoveAll(base)
union, err := ioutil.TempDir("", "docker-aufs-union")
if err != nil {
log.Errorf("error checking dirperm1: %v", err)
return
}
defer os.RemoveAll(union)
opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base)
if err := mount("none", union, "aufs", 0, opts); err != nil {
return
}
enableDirperm = true
if err := Unmount(union); err != nil {
log.Errorf("error checking dirperm1: failed to unmount %v", err)
}
})
return enableDirperm
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
func Unmount(target string) error {
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
log.Errorf("[warning]: couldn't run auplink before unmount: %s", err)
log.Errorf("Couldn't run auplink before unmount: %s", err)
}
if err := syscall.Unmount(target, 0); err != nil {
return err

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