Compare commits

...
53 Commits
Author SHA1 Message Date
David Calavera 8a9d4eaa6e Bump version to 1.8.0
Signed-off-by: David Calavera <david.calavera@gmail.com>
2015-08-03 17:49:58 -07:00
Eric Windisch 5851e2da60 Remove container AA profile from packaging
Signed-off-by: Eric Windisch <eric@windisch.us>
(cherry picked from commit 0f4e5f7149)
2015-08-03 17:49:58 -07:00
Eric Windisch 9eff33735a Fix the proc integration test & include missing AA profile
Integration tests were failing due to proc filter behavior
changes with new apparmor policies.

Also include the missing docker-unconfined policy resolving
potential startup errors. This policy is complain-only so
it should behave identically to the standard unconfined policy,
but will not apply system path-based policies within containers.

Signed-off-by: Eric Windisch <eric@windisch.us>
(cherry picked from commit 5832715052)
2015-08-03 17:49:58 -07:00
Alessandro Boch fc7697b050 Fix preallocated bridge networks
- Because of a bug, all the statically preallocated
  bridge networks have /24 as network mask.

Signed-off-by: Alessandro Boch <aboch@docker.com>
(cherry picked from commit dab0447ae0)
2015-08-03 17:21:31 -07:00
David Calavera 1bf8954d0d Remove key file when migration fails.
Signed-off-by: David Calavera <david.calavera@gmail.com>
(cherry picked from commit 07c45e499d)
2015-08-03 16:22:34 -07:00
Michael Crosby dfd9f5989a Add LXC built in support deprecation notice
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit 06f6c0c7e5)
2015-08-03 16:22:34 -07:00
Josh Hawn d9581e861d [graph] Enforce manifest/layer digest verification
We noticed a regression since the 1.7.1 patch after some refactoring. This
patch corrects the behavior and adds integration tests for modified manifest
and rootfs layer blobs.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)

(cherry picked from commit de52a3bcaa)
2015-08-03 16:22:33 -07:00
Stephen Rust c383ceaf37 Docs: Add Blockbridge volume plugin
Signed-off-by: Stephen Rust <srust@blockbridge.com>
(cherry picked from commit 8b15b7958a)
2015-08-03 11:29:25 -07:00
Harald Albers 948912f692 Filter completions of docker inspect by --type
Completion now filters the images and containers by given
`--type`.

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 69cde5a302)
2015-08-03 11:29:25 -07:00
jrabbit d19b1b927b Actually link to the information
Signed-off-by: jrabbit <jackjrabbit@gmail.com>
(cherry picked from commit d2cd142ce1)
2015-08-03 09:44:11 -07:00
Tibor Vass 53f5905379 Add missing LICENSE files for docker/notary
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit 084af30f39)
2015-08-03 09:43:19 -07:00
Tibor Vass 60cbf4da6c Vendor docker/distribution to 7dc8d4a26b689bd4892f2f2322dbce0b7119d686
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit 0cce1fb37f)
2015-08-03 09:43:19 -07:00
evalle 183628388c Fix wrong path to YaST's 'Routing' menu for openSUSE Tumbleweed
Signed-off-by: evalle <shmarnev@gmail.com>
(cherry picked from commit 5f1593c7b8)
2015-08-03 09:37:35 -07:00
Charles Chan fbd2267e7d Update docker_remote_api.md
Minor fixes:
* v1.19: GET /containers/(id)/logs - add missing '/'
* v1.18: Break up POST /containers/create and POST /containers/(id)/start into separate lines.

Signed-off-by: Charles Chan <charleswhchan@users.noreply.github.com>
(cherry picked from commit 084d464081)
2015-08-03 09:37:35 -07:00
Derek McGowan a16ab243e5 Updated to use latest version of notary
Update UX to use aliases for root, snapshot, and target key

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
(cherry picked from commit 6ce76cd9ed)
2015-08-03 09:37:35 -07:00
Derek McGowan b3c3c4cddc Vendor latest notary
Use updated notary to pick up updates from security review

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
(cherry picked from commit d594c6fcd8)
2015-08-03 09:37:34 -07:00
Qiang Huang 0fe5aad984 Remove unused variable
Introduced by #15209 unintentionally.

Signed-off-by: Qiang Huang <h.huangqiang@huawei.com>
(cherry picked from commit f5557f4f43)
2015-08-03 09:37:34 -07:00
Brian Goff 0f5e2fd479 Ensure reader position is at the end after tailing
After tailing a file, if the number of lines requested is > the number
of lines in the file, this would cause a json unmarshalling error to
occur when we later try to go follow the file.
So brute force set it to the end if any tailing occurred.

There is potential that there could be some missing log messages if logs
are being written very quickly, however I was not able to make this
happen even with `while true; do echo hello; done`, so this is probably
acceptable.

While testing this I also found a panic in LogWatcher.Close can be
called twice due to a race. Fix channel close to only close when there
has been no signal to the channel.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit c57faa91e2)
2015-08-03 09:37:34 -07:00
David Calavera 2f7145b1c5 Remove read index that causes dead lock.
Let the iterator to lock the index when it needs it.

Signed-off-by: David Calavera <david.calavera@gmail.com>
(cherry picked from commit 5ed84009b3)
2015-07-31 14:56:17 -07:00
Harald Albers 81efe1f32e Fix completion of commands after a global option with arg
Without this fix, `docker -l info ` would not complete the commands.

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit aab82c5c22)
2015-07-31 12:20:42 -07:00
Harald Albers 5ba75ac343 Add completion of global options to docker daemon
It's a bit confusing: the "global options" are valid as "global options"
for all client commands (i.e. all but daemon).
Example: `docker --log-level info run`

For `docker daemon`, these "global options" are only valid as "command
options".
Example: `docker daemon --log-level info`

As command completion cannot tell which command the user is going to
type next, completion for the daemon command has to allow illegal
syntaxes like
`docker --log-level info daemon --log-level info`

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit e0dad9a153)
2015-07-31 12:20:42 -07:00
Alessandro Boch 290987fcb4 Add test code to cover issue #14859
Signed-off-by: Alessandro Boch <aboch@docker.com>
(cherry picked from commit 4964ab0821)
2015-07-31 08:59:42 -07:00
Alessandro Boch 98855c863d Vendoring libnetwork 31139cdb513aea5ad1ed08b60d4350a68b4c96db
Signed-off-by: Alessandro Boch <aboch@docker.com>
(cherry picked from commit 91274625ba)
2015-07-31 08:59:42 -07:00
Jessica Frazelle b1f394a247 fix regression
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
(cherry picked from commit b0af811272)
2015-07-30 17:21:22 -07:00
Avi Miller a819a60a94 Fix for #14924. Seperates lsb_dist detection from dist_version detection
so that the latter can be distro specific.

Signed-off-by: Avi Miller <avi.miller@oracle.com>
(cherry picked from commit 5c6446f335)
2015-07-30 17:21:21 -07:00
Tibor Vass 33cdc7f2c4 registry: allow fallback on unknown errors
This patch fixes a bug where a user specifies a v1 mirror for
--registry-mirror and pull an image from the Hub.

It used to not fallback because of an unexpected error returned when
trying to JSON marshal nginx output.

We now ensure that any unexpected error falls back to the next endpoint
in the list.

Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit a21ba12f4e)
2015-07-30 17:21:21 -07:00
Jay Kamat 117860577c Fix a couple broken links
Signed-off-by: Jay Kamat <github@jgkamat.33mail.com>
(cherry picked from commit 4ca0aad855)
2015-07-30 17:21:21 -07:00
Dan Walsh b0ac5df367 Labels on network content need to be shared if shared network namespace
If I run two containers with the same network they share the same /etc/resolv.conf.
The current code changes the labels of the /etc/resolv.conf currently to the
private label which causes it to be unusable in the first container.

This patch changes the labels to a shared label if more then one container
will use the content.

Docker-DCO-1.1-Signed-off-by: Dan Walsh dwalsh@redhat.com (github: rhatdan)

Docker-DCO-1.1-Signed-off-by: Dan Walsh <dwalsh@redhat.com> (github: rhatdan)

(cherry picked from commit 90b8cebda6)
2015-07-30 17:21:21 -07:00
Josh Hawn c109095a58 Fix docker cp Behavior With Symlinks
[pkg/archive] Update archive/copy path handling

  - Remove unused TarOptions.Name field.
  - Add new TarOptions.RebaseNames field.
  - Update some of the logic around path dir/base splitting.
  - Update some of the logic behind archive entry name rebasing.

[api/types] Add LinkTarget field to PathStat

[daemon] Fix stat, archive, extract of symlinks

  These operations *should* resolve symlinks that are in the path but if the
  resource itself is a symlink then it *should not* be resolved. This patch
  puts this logic into a common function `resolvePath` which resolves symlinks
  of the path's dir in scope of the container rootfs but does not resolve the
  final element of the path. Now archive, extract, and stat operations will
  return symlinks if the path is indeed a symlink.

[api/client] Update cp path hanling

[docs/reference/api] Update description of stat

  Add the linkTarget field to the header of the archive endpoint.
  Remove path field.

[integration-cli] Fix/Add cp symlink test cases

  Copying a symlink should do just that: copy the symlink NOT
  copy the target of the symlink. Also, the resulting file from
  the copy should have the name of the symlink NOT the name of
  the target file.

  Copying to a symlink should copy to the symlink target and not
  modify the symlink itself.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)

(cherry picked from commit 75f6929b44)
2015-07-30 17:21:20 -07:00
Stephen Rust d394113dfe Check for nil before using HostConfig to adjustCpuShares
Fix #14915. Add unit test for #14915.
Thanks @runcom for the test case: when the client calls 1.18 api
version w/o hostconfig it results in a nil pointer dereference.

Signed-off-by: Stephen Rust <srust@blockbridge.com>
(cherry picked from commit c358a4cd35)
2015-07-30 17:21:20 -07:00
Michael Crosby 2af7f63173 Don't mount /proc as ro
This caused a regression with LSM labeling.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit bfc51cf660)
2015-07-30 17:21:20 -07:00
Eric Windisch f156fb7be5 Only explicitly deny ptrace for container-originated procs
The 'deny ptrace' statement was supposed to only ignore
ptrace failures in the AUDIT log. However, ptrace was implicitly
allowed from unconfined processes (such as the docker daemon and
its integration tests) due to the abstractions/base include.

This rule narrows the definition such that it will only ignore
the failures originating inside of the container and will not
cause denials when the daemon or its tests ptrace inside processes.

Introduces positive and negative tests for ptrace /w apparmor.

Signed-off-by: Eric Windisch <eric@windisch.us>
(cherry picked from commit f5c388b35a)
2015-07-30 12:54:14 -07:00
Tibor Vass 559043b953 tlsconfig: better format for error message in tlsconfig
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit c7a04fda2a)
2015-07-30 12:54:14 -07:00
Harald Albers ba8abcb3dd Add docker daemon to bash completion
Signed-off-by: Harald Albers <github@albersweb.de>
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit 8cc8ee9254)
2015-07-30 12:54:14 -07:00
David Calavera ebf396c6e8 Log each release step.
Signed-off-by: David Calavera <david.calavera@gmail.com>
(cherry picked from commit 776600fabb)
2015-07-30 09:07:15 -07:00
Josh Hawn 47d52fb872 [api/client] Tag resolved digest from Dockerfile
Builds where the base images have been resolved to trusted digest
references will now be tagged with the original tag reference from
the Dockerfile on a successful build.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)

(cherry picked from commit bb2e6c72d2)
2015-07-29 16:53:10 -07:00
Vincent Batts d167338876 graphdriver/*: expect uncompressed tar for ApplyDiff
The `ApplyDiff` function takes a tar archive stream that is
automagically decompressed later. This was causing a double
decompression, and when the layer was empty, that causes an early EOF.

Signed-off-by: Vincent Batts <vbatts@redhat.com>
(cherry picked from commit 273f50c741)
2015-07-29 16:48:42 -07:00
Vincent Batts e6844381f0 archive, chrootarchive: split out decompression
In `ApplyLayer` and `Untar`, the stream is magically decompressed. Since
this is not able to be toggled, rather than break this ./pkg/ API, add
an `ApplyUncompressedLayer` and `UntarUncompressed` that does not
magically decompress the layer stream.

Signed-off-by: Vincent Batts <vbatts@redhat.com>
(cherry picked from commit 56bf275e32)
2015-07-29 16:48:41 -07:00
Derek McGowan 589922adf0 Fix login and search TLS configuration
Currently login and search do not load per registry certificates.
This is a regression caused by the last refactor since this was recently fixed.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
(cherry picked from commit e863a07b89)
2015-07-29 16:45:16 -07:00
David Calavera 689c4e6075 Keep backwards compatibility in kill api.
Return an error when the container is stopped only in api versions
equal or greater than 1.20 (docker 1.8).

Signed-off-by: David Calavera <david.calavera@gmail.com>
(cherry picked from commit 621e3d8587)
2015-07-29 16:44:21 -07:00
David Calavera 43da1adedb Use *int64 for MemorySwappiness.
So we marshal/unmarshal its value properly when it's empty.

Signed-off-by: David Calavera <david.calavera@gmail.com>
(cherry picked from commit 4e25d2982b)
2015-07-29 16:44:21 -07:00
Antonio Murdaca 686fe02020 Fix install script to handle debian 8.1 apt repo string
Fix #14911

Signed-off-by: Antonio Murdaca <runcom@linux.com>
(cherry picked from commit 98f15cae89)
2015-07-29 10:46:26 -07:00
Eric Windisch 1d02be1c7a Mark engine AA policy as complain-only
The engine policy will now only complain
as a temporary measure to ensure we do not
cause breakages while users exercise this
policy.

This is NOT the policy for containers, but
for the newly-introduced policy for the
daemon itself.

Signed-off-by: Eric Windisch <eric@windisch.us>
(cherry picked from commit 6c887be769)
2015-07-29 09:54:16 -07:00
Eric Windisch edb60b950a AA: Eliminate 'file' permission
Implements the policies for the remaining binaries
called by the Docker engine and eliminates the
giant whitelisted 'all files' permission in favor
of granular whitelisting and child-specific policies.

It should be possible now to remove the 'file' permission,
but for the sake of keeping Docker unbroken, we'll try
to gradually tighten the policy.

Signed-off-by: Eric Windisch <eric@windisch.us>
(cherry picked from commit 8b2fcddcd2)
2015-07-29 09:54:16 -07:00
Eric Windisch e0e852ee6f Restore AppArmor profile generation
Will attempt to load profiles automatically. If loading fails
but the profiles are already loaded, execution will continue.

A hard failure will only occur if Docker cannot load
the profiles *and* they have not already been loaded via
some other means.

Also introduces documentation for AppArmor.

Signed-off-by: Eric Windisch <eric@windisch.us>
(cherry picked from commit 3edc88f76d)
2015-07-29 09:54:16 -07:00
Jessica Frazelle b537508f8c only write distributions file if dne
Signed-off-by: Jessica Frazelle <princess@docker.com>
(cherry picked from commit 2eee192366)
2015-07-28 17:48:56 -07:00
Avi Miller 37e886eb7b Fix typo.
Signed-off-by: Avi Miller <avi.miller@oracle.com>
(cherry picked from commit 59e48b3468)
2015-07-28 09:39:55 -07:00
Avi Miller 50f65742ef Switch to using only the RPM command to determine the distro version.
Signed-off-by: Avi Miller <avi.miller@oracle.com>
(cherry picked from commit a43199f143)
2015-07-28 09:39:50 -07:00
Alexander Morozov 56d859d052 Use math.MaxInt32 instead of math.MaxUint32
I think it was original intention, because even half of a comment was about
MaxInt32.

Fix #15038

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
(cherry picked from commit eb45602d2f)
2015-07-28 09:35:56 -07:00
Josh Hawn 546a704c63 [api/client] Fix build when context dir is symlink
Symbolic links in the context directory path are now evaluated.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)

(cherry picked from commit 01d570ad30)
2015-07-28 09:35:38 -07:00
Aaron Lehmann fa85dc0030 Update vendored distribution repo to new version
This version includes a fix that avoids checking against specific HTTP
status codes. The previous behavior violated the registry API spec.

Fixes #14975

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
(cherry picked from commit 091dbc1034)
2015-07-28 09:35:26 -07:00
Antonio Murdaca 36b6e5884d Format times in inspect command with a template as RFC3339Nano
In 1.6.2 we were decoding inspect API response into interface{}.
time.Time fields were JSON encoded as RFC3339Nano in the response
and when decoded into interface{} they were just strings so the inspect
template treated them as just strings.
From 1.7 we are decoding into types.ContainerJSON and when the template
gets executed it now gets a time.Time and it's formatted as
2015-07-22 05:02:38.091530369 +0000 UTC.
This patch brings back the old behavior by typing time.Time fields
as string so they gets formatted as they were encoded in JSON -- RCF3339Nano

Signed-off-by: Antonio Murdaca <runcom@linux.com>
(cherry picked from commit c9207bc0aa)
2015-07-28 09:35:14 -07:00
Jessica Frazelle 90991ddb9b fix deb packaging systemd files
Signed-off-by: Jessica Frazelle <princess@docker.com>
(cherry picked from commit a2ea8f2ad8)
2015-07-28 09:34:48 -07:00
117 changed files with 2972 additions and 836 deletions
+86
View File
@@ -1,5 +1,91 @@
# Changelog # Changelog
## 1.8.0 (2015-07-24)
### Distribution
+ Trusted pull, push and build, disabled by default
* Make tar layers deterministic between registries
* Don't allow deleting the image of running containers
* Check if a tag name to load is a valid digest
* Allow one character repository names
* Add a more accurate error description for invalid tag name
* Make build cache ignore mtime
### Cli
+ Add support for DOCKER_CONFIG/--config to specify config file dir
+ Add --type flag for docker inspect command
+ Add formatting options to `docker ps` with `--format`
+ Replace `docker -d` with new subcommand `docker daemon`
* Zsh completion updates and improvements
* Add some missing events to bash completion
* Support daemon urls with base paths in `docker -H`
* Validate status= filter to docker ps
* Display when a container is in --net=host in docker ps
* Extend docker inspect to export image metadata related to graph driver
* Restore --default-gateway{,-v6} daemon options
* Add missing unpublished ports in docker ps
* Allow duration strings in `docker events` as --since/--until
* Expose more mounts information in `docker inspect`
### Runtime
+ Add new Fluentd logging driver
+ Allow `docker import` to load from local files
+ Add logging driver for GELF via UDP
+ Allow to copy files from host to containers with `docker cp`
+ Promote volume drivers from experimental to master
+ Add rollover log driver, and --log-driver-opts flag
+ Add memory swappiness tuning options
* Remove cgroup read-only flag when privileged
* Make /proc, /sys, & /dev readonly for readonly containers
* Add cgroup bind mount by default
* Overlay: Export metadata for container and image in `docker inspect`
* Devicemapper: external device activation
* Devicemapper: Compare uuid of base device on startup
* Remove RC4 from the list of registry cipher suites
* Add syslog-facility option
* LXC execdriver compatibility with recent LXC versions
### Plugins
* Separate plugin sockets and specs locations
* Allow TLS connections to plugins
### Bug fixes
- Add missing 'Names' field to /containers/json API output
- Make `docker rmi --dangling` safe when pulling
- Devicemapper: Change default basesize to 100G
- Go Scheduler issue with sync.Mutex and gcc
- Fix issue where Search API endpoint would panic due to empty AuthConfig
- Set image canonical names correctly
- Check dockerinit only if lxc driver is used
- Fix ulimit usage of nproc
- Always attach STDIN if -i,--interactive is specified
- Show error messages when saving container state fails
- Fixed incorrect assumption on --bridge=none treated as disable network
- Check for invalid port specifications in host configuration
- Fix endpoint leave failure for --net=host mode
- Fix goroutine leak in the stats API if the container is not running
- Check for apparmor file before reading it
- Fix DOCKER_TLS_VERIFY being ignored
- Set umask to the default on startup
- Correct the message of pause and unpause a non-running container
- Adjust disallowed CpuShares in container creation
- ZFS: correctly apply selinux context
- Display empty string instead of <nil> when IP opt is nil
- `docker kill` returns error when container is not running
- Fix COPY/ADD quoted/json form
- Fix goroutine leak on logs -f with no output
- Remove panic in nat package on invalid hostport
- Fix container linking in Fedora 22
- Fix error caused using default gateways outside of the allocated range
- Format times in inspect command with a template as RFC3339Nano
- Make registry client to accept 2xx and 3xx http status responses as successful
## 1.7.1 (2015-07-14) ## 1.7.1 (2015-07-14)
#### Runtime #### Runtime
+1 -1
View File
@@ -137,7 +137,7 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_COMMIT 77bced079e83d80f40c1f0a544b1a8a3b97fb052 ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
+1 -1
View File
@@ -13,7 +13,7 @@ databases, and backend services without depending on a particular stack
or provider. or provider.
Docker began as an open-source implementation of the deployment engine which Docker began as an open-source implementation of the deployment engine which
powers [dotCloud](https://dotcloud.com), a popular Platform-as-a-Service. powers [dotCloud](https://www.dotcloud.com), a popular Platform-as-a-Service.
It benefits directly from the experience accumulated over several years It benefits directly from the experience accumulated over several years
of large-scale operation and support of hundreds of thousands of of large-scale operation and support of hundreds of thousands of
applications and databases. applications and databases.
+1 -1
View File
@@ -1 +1 @@
1.8.0-dev 1.8.0-rc2
+57 -9
View File
@@ -115,8 +115,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
} }
// Resolve the FROM lines in the Dockerfile to trusted digest references // Resolve the FROM lines in the Dockerfile to trusted digest references
// using Notary. // using Notary. On a successful build, we must tag the resolved digests
newDockerfile, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference) // to the original name specified in the Dockerfile.
newDockerfile, resolvedTags, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference)
if err != nil { if err != nil {
return fmt.Errorf("unable to process Dockerfile: %v", err) return fmt.Errorf("unable to process Dockerfile: %v", err)
} }
@@ -291,7 +292,20 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
} }
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code} return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
} }
return err
if err != nil {
return err
}
// Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite.
for _, resolved := range resolvedTags {
if err := cli.tagTrusted(resolved.repoInfo, resolved.digestRef, resolved.tagRef); err != nil {
return err
}
}
return nil
} }
// getDockerfileRelPath uses the given context directory for a `docker build` // getDockerfileRelPath uses the given context directory for a `docker build`
@@ -302,6 +316,22 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
return "", "", fmt.Errorf("unable to get absolute context directory: %v", err) return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
} }
// The context dir might be a symbolic link, so follow it to the actual
// target directory.
absContextDir, err = filepath.EvalSymlinks(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
}
stat, err := os.Lstat(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
}
if !stat.IsDir() {
return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
}
absDockerfile := givenDockerfile absDockerfile := givenDockerfile
if absDockerfile == "" { if absDockerfile == "" {
// No -f/--file was specified so use the default relative to the // No -f/--file was specified so use the default relative to the
@@ -467,14 +497,21 @@ func (td *trustedDockerfile) Close() error {
return os.Remove(td.File.Name()) return os.Remove(td.File.Name())
} }
// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
type resolvedTag struct {
repoInfo *registry.RepositoryInfo
digestRef, tagRef registry.Reference
}
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in // rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
// "FROM <image>" instructions to a digest reference. `translator` is a // "FROM <image>" instructions to a digest reference. `translator` is a
// function that takes a repository name and tag reference and returns a // function that takes a repository name and tag reference and returns a
// trusted digest reference. // trusted digest reference.
func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, err error) { func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
dockerfile, err := os.Open(dockerfileName) dockerfile, err := os.Open(dockerfileName)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to open Dockerfile: %v", err) return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err)
} }
defer dockerfile.Close() defer dockerfile.Close()
@@ -483,7 +520,7 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist
// Make a tempfile to store the rewritten Dockerfile. // Make a tempfile to store the rewritten Dockerfile.
tempFile, err := ioutil.TempFile("", "trusted-dockerfile-") tempFile, err := ioutil.TempFile("", "trusted-dockerfile-")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err) return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err)
} }
trustedFile := &trustedDockerfile{ trustedFile := &trustedDockerfile{
@@ -509,21 +546,32 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist
if tag == "" { if tag == "" {
tag = tags.DEFAULTTAG tag = tags.DEFAULTTAG
} }
repoInfo, err := registry.ParseRepositoryInfo(repo)
if err != nil {
return nil, nil, fmt.Errorf("unable to parse repository info: %v", err)
}
ref := registry.ParseReference(tag) ref := registry.ParseReference(tag)
if !ref.HasDigest() && isTrusted() { if !ref.HasDigest() && isTrusted() {
trustedRef, err := translator(repo, ref) trustedRef, err := translator(repo, ref)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo))) line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo)))
resolvedTags = append(resolvedTags, &resolvedTag{
repoInfo: repoInfo,
digestRef: trustedRef,
tagRef: ref,
})
} }
} }
n, err := fmt.Fprintln(tempFile, line) n, err := fmt.Fprintln(tempFile, line)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
trustedFile.size += int64(n) trustedFile.size += int64(n)
@@ -531,7 +579,7 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist
tempFile.Seek(0, os.SEEK_SET) tempFile.Seek(0, os.SEEK_SET)
return trustedFile, scanner.Err() return trustedFile, resolvedTags, scanner.Err()
} }
// replaceDockerfileTarWrapper wraps the given input tar archive stream and // replaceDockerfileTarWrapper wraps the given input tar archive stream and
+29 -10
View File
@@ -232,6 +232,20 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er
// Prepare destination copy info by stat-ing the container path. // Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath} dstInfo := archive.CopyInfo{Path: dstPath}
dstStat, err := cli.statContainerPath(dstContainer, dstPath) dstStat, err := cli.statContainerPath(dstContainer, dstPath)
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
linkTarget := dstStat.LinkTarget
if !filepath.IsAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath)
linkTarget = filepath.Join(dstParent, linkTarget)
}
dstInfo.Path = linkTarget
dstStat, err = cli.statContainerPath(dstContainer, linkTarget)
}
// Ignore any error and assume that the parent directory of the destination // Ignore any error and assume that the parent directory of the destination
// path exists, in which case the copy may still succeed. If there is any // path exists, in which case the copy may still succeed. If there is any
// type of conflict (e.g., non-directory overwriting an existing directory // type of conflict (e.g., non-directory overwriting an existing directory
@@ -242,15 +256,26 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir() dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
} }
var content io.Reader var (
content io.Reader
resolvedDstPath string
)
if srcPath == "-" { if srcPath == "-" {
// Use STDIN. // Use STDIN.
content = os.Stdin content = os.Stdin
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir { if !dstInfo.IsDir {
return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath)) return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
} }
} else { } else {
srcArchive, err := archive.TarResource(srcPath) // Prepare source copy info.
srcInfo, err := archive.CopyInfoSourcePath(srcPath)
if err != nil {
return err
}
srcArchive, err := archive.TarResource(srcInfo)
if err != nil { if err != nil {
return err return err
} }
@@ -262,12 +287,6 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er
// it to the specified directory in the container we get the disired // it to the specified directory in the container we get the disired
// copy behavior. // copy behavior.
// Prepare source copy info.
srcInfo, err := archive.CopyInfoStatPath(srcPath, true)
if err != nil {
return err
}
// See comments in the implementation of `archive.PrepareArchiveCopy` // See comments in the implementation of `archive.PrepareArchiveCopy`
// for exactly what goes into deciding how and whether the source // for exactly what goes into deciding how and whether the source
// archive needs to be altered for the correct copy behavior when it is // archive needs to be altered for the correct copy behavior when it is
@@ -280,12 +299,12 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er
} }
defer preparedArchive.Close() defer preparedArchive.Close()
dstPath = dstDir resolvedDstPath = dstDir
content = preparedArchive content = preparedArchive
} }
query := make(url.Values, 2) query := make(url.Values, 2)
query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API. query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
query.Set("noOverwriteDirNonDir", "true") query.Set("noOverwriteDirNonDir", "true")
+28 -9
View File
@@ -13,6 +13,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -176,11 +177,16 @@ func convertTarget(t client.Target) (target, error) {
} }
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever { func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out) aliasMap := map[string]string{
"root": "offline",
"snapshot": "tagging",
"targets": "tagging",
}
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
env := map[string]string{ env := map[string]string{
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"), "root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"),
"targets": os.Getenv("DOCKER_CONTENT_TRUST_TARGET_PASSPHRASE"), "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_SNAPSHOT_PASSPHRASE"), "targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
} }
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
if v := env[alias]; v != "" { if v := env[alias]; v != "" {
@@ -311,6 +317,22 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr
return nil return nil
} }
func selectKey(keys map[string]string) string {
if len(keys) == 0 {
return ""
}
keyIDs := []string{}
for k := range keys {
keyIDs = append(keyIDs, k)
}
// TODO(dmcgowan): let user choose if multiple keys, now pick consistently
sort.Strings(keyIDs)
return keyIDs[0]
}
func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) { func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
r, w := io.Pipe() r, w := io.Pipe()
out := io.MultiWriter(in, w) out := io.MultiWriter(in, w)
@@ -409,16 +431,13 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string,
ks := repo.KeyStoreManager ks := repo.KeyStoreManager
keys := ks.RootKeyStore().ListKeys() keys := ks.RootKeyStore().ListKeys()
var rootKey string
if len(keys) == 0 { rootKey := selectKey(keys)
if rootKey == "" {
rootKey, err = ks.GenRootKey("ecdsa") rootKey, err = ks.GenRootKey("ecdsa")
if err != nil { if err != nil {
return err return err
} }
} else {
// TODO(dmcgowan): let user choose
rootKey = keys[0]
} }
cryptoService, err := ks.GetRootCryptoService(rootKey) cryptoService, err := ks.GetRootCryptoService(rootKey)
+7 -1
View File
@@ -298,7 +298,13 @@ func (s *Server) postContainersKill(version version.Version, w http.ResponseWrit
} }
if err := s.daemon.ContainerKill(name, sig); err != nil { if err := s.daemon.ContainerKill(name, sig); err != nil {
return err _, isStopped := err.(daemon.ErrContainerNotRunning)
// Return error that's not caused because the container is stopped.
// Return error if the container is not running and the api is >= 1.20
// to keep backwards compatibility.
if version.GreaterThanOrEqualTo("1.20") || !isStopped {
return fmt.Errorf("Cannot kill container %s: %v", name, err)
}
} }
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
+1 -1
View File
@@ -109,7 +109,7 @@ func allocateDaemonPort(addr string) error {
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) { func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
if version.LessThan("1.19") { if version.LessThan("1.19") {
if hostConfig.CpuShares > 0 { if hostConfig != nil && hostConfig.CpuShares > 0 {
// Handle unsupported CpuShares // Handle unsupported CpuShares
if hostConfig.CpuShares < linuxMinCpuShares { if hostConfig.CpuShares < linuxMinCpuShares {
logrus.Warnf("Changing requested CpuShares of %d to minimum allowed of %d", hostConfig.CpuShares, linuxMinCpuShares) logrus.Warnf("Changing requested CpuShares of %d to minimum allowed of %d", hostConfig.CpuShares, linuxMinCpuShares)
+10 -11
View File
@@ -86,7 +86,7 @@ type ImageInspect struct {
Id string Id string
Parent string Parent string
Comment string Comment string
Created time.Time Created string
Container string Container string
ContainerConfig *runconfig.Config ContainerConfig *runconfig.Config
DockerVersion string DockerVersion string
@@ -130,14 +130,13 @@ type CopyConfig struct {
// ContainerPathStat is used to encode the header from // ContainerPathStat is used to encode the header from
// GET /containers/{name:.*}/archive // GET /containers/{name:.*}/archive
// "name" is the file or directory name. // "name" is basename of the resource.
// "path" is the absolute path to the resource in the container.
type ContainerPathStat struct { type ContainerPathStat struct {
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path"` Size int64 `json:"size"`
Size int64 `json:"size"` Mode os.FileMode `json:"mode"`
Mode os.FileMode `json:"mode"` Mtime time.Time `json:"mtime"`
Mtime time.Time `json:"mtime"` LinkTarget string `json:"linkTarget"`
} }
// GET "/containers/{name:.*}/top" // GET "/containers/{name:.*}/top"
@@ -215,14 +214,14 @@ type ContainerState struct {
Pid int Pid int
ExitCode int ExitCode int
Error string Error string
StartedAt time.Time StartedAt string
FinishedAt time.Time FinishedAt string
} }
// GET "/containers/{name:.*}/json" // GET "/containers/{name:.*}/json"
type ContainerJSONBase struct { type ContainerJSONBase struct {
Id string Id string
Created time.Time Created string
Path string Path string
Args []string Args []string
State *ContainerState State *ContainerState
-25
View File
@@ -1,25 +0,0 @@
#include <tunables/global>
profile docker-default flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network,
capability,
file,
umount,
deny @{PROC}/sys/fs/** wklx,
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
deny @{PROC}/sys/kernel/*/** wklx,
deny mount,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/efi/efivars/** rwklx,
deny /sys/kernel/security/** rwklx,
}
+110 -30
View File
@@ -1,6 +1,6 @@
@{DOCKER_GRAPH_PATH}=/var/lib/docker @{DOCKER_GRAPH_PATH}=/var/lib/docker
profile /usr/bin/docker (attach_disconnected) { profile /usr/bin/docker (attach_disconnected, complain) {
# Prevent following links to these files during container setup. # Prevent following links to these files during container setup.
deny /etc/** mkl, deny /etc/** mkl,
deny /dev/** kl, deny /dev/** kl,
@@ -21,51 +21,131 @@ profile /usr/bin/docker (attach_disconnected) {
ipc rw, ipc rw,
network, network,
capability, capability,
file, owner /** rw,
/var/lib/docker/** rwl,
# For non-root client use:
/dev/urandom r,
/run/docker.sock rw,
/proc/** r,
/sys/kernel/mm/hugepages/ r,
/etc/localtime r,
ptrace peer=@{profile_name}, ptrace peer=@{profile_name},
ptrace (read) peer=docker-default,
deny ptrace (trace) peer=docker-default,
deny ptrace peer=/usr/bin/docker///bin/ps,
/usr/bin/docker pix, /usr/bin/docker pix,
/sbin/xtables-multi rCix, /sbin/xtables-multi rCx,
/sbin/iptables rCx, /sbin/iptables rCx,
/sbin/modprobe rCx, /sbin/modprobe rCx,
/sbin/auplink rCx, /sbin/auplink rCx,
/bin/kmod rCx,
/usr/bin/xz rCx, /usr/bin/xz rCx,
/bin/ps rCx,
/bin/cat rCx,
/sbin/zfs rCx,
# Transitions # Transitions
change_profile -> docker-*, change_profile -> docker-*,
change_profile -> unconfined, change_profile -> unconfined,
profile /sbin/iptables { profile /bin/cat (complain) {
signal (receive) peer=/usr/bin/docker, /etc/ld.so.cache r,
capability net_admin, /lib/** r,
} /dev/null rw,
profile /sbin/auplink flags=(attach_disconnected) { /proc r,
signal (receive) peer=/usr/bin/docker, /bin/cat mr,
capability sys_admin,
capability dac_override,
@{DOCKER_GRAPH_PATH}/aufs/** rw, # For reading in 'docker stats':
# For user namespaces: /proc/[0-9]*/net/dev r,
@{DOCKER_GRAPH_PATH}/[0-9]*.[0-9]*/** rw,
# The following may be removed via delegates
/sys/fs/aufs/** r,
/lib/** r,
/apparmor/.null r,
/dev/null rw,
/etc/ld.so.cache r,
/sbin/auplink rm,
/proc/fs/aufs/** rw,
/proc/[0-9]*/mounts rw,
} }
profile /sbin/modprobe { profile /bin/ps (complain) {
signal (receive) peer=/usr/bin/docker, /etc/ld.so.cache r,
capability sys_module, /etc/localtime r,
file, /etc/passwd r,
/etc/nsswitch.conf r,
/lib/** r,
/proc/[0-9]*/** r,
/dev/null rw,
/bin/ps mr,
# We don't need ptrace so we'll deny and ignore the error.
deny ptrace (read, trace),
# Quiet dac_override denials
deny capability dac_override,
deny capability dac_read_search,
deny capability sys_ptrace,
/dev/tty r,
/proc/stat r,
/proc/cpuinfo r,
/proc/meminfo r,
/proc/uptime r,
/sys/devices/system/cpu/online r,
/proc/sys/kernel/pid_max r,
/proc/ r,
/proc/tty/drivers r,
}
profile /sbin/iptables (complain) {
signal (receive) peer=/usr/bin/docker,
capability net_admin,
}
profile /sbin/auplink flags=(attach_disconnected, complain) {
signal (receive) peer=/usr/bin/docker,
capability sys_admin,
capability dac_override,
@{DOCKER_GRAPH_PATH}/aufs/** rw,
@{DOCKER_GRAPH_PATH}/tmp/** rw,
# For user namespaces:
@{DOCKER_GRAPH_PATH}/[0-9]*.[0-9]*/** rw,
/sys/fs/aufs/** r,
/lib/** r,
/apparmor/.null r,
/dev/null rw,
/etc/ld.so.cache r,
/sbin/auplink rm,
/proc/fs/aufs/** rw,
/proc/[0-9]*/mounts rw,
}
profile /sbin/modprobe /bin/kmod (complain) {
signal (receive) peer=/usr/bin/docker,
capability sys_module,
/etc/ld.so.cache r,
/lib/** r,
/dev/null rw,
/apparmor/.null rw,
/sbin/modprobe rm,
/bin/kmod rm,
/proc/cmdline r,
/sys/module/** r,
/etc/modprobe.d{/,/**} r,
} }
# xz works via pipes, so we do not need access to the filesystem. # xz works via pipes, so we do not need access to the filesystem.
profile /usr/bin/xz { profile /usr/bin/xz (complain) {
signal (receive) peer=/usr/bin/docker, signal (receive) peer=/usr/bin/docker,
/etc/ld.so.cache r,
/lib/** r,
/usr/bin/xz rm,
deny /proc/** rw,
deny /sys/** rw,
}
profile /sbin/xtables-multi (attach_disconnected, complain) {
/etc/ld.so.cache r,
/lib/** r,
/sbin/xtables-multi rm,
/apparmor/.null w,
/dev/null rw,
capability net_raw,
capability net_admin,
network raw,
}
profile /sbin/zfs (attach_disconnected, complain) {
file,
capability,
} }
} }
+109 -65
View File
@@ -295,6 +295,10 @@ __docker_complete_log_driver_options() {
return 1 return 1
} }
__docker_log_levels() {
COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) )
}
# a selection of the available signals that is most likely of interest in the # a selection of the available signals that is most likely of interest in the
# context of docker containers. # context of docker containers.
__docker_signals() { __docker_signals() {
@@ -312,49 +316,20 @@ __docker_signals() {
COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo $cur | tr '[:lower:]' '[:upper:]')" ) ) COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo $cur | tr '[:lower:]' '[:upper:]')" ) )
} }
# global options that may appear after the docker command
_docker_docker() { _docker_docker() {
local boolean_options=" local boolean_options="
--daemon -d $global_boolean_options
--debug -D
--help -h --help -h
--icc
--ip-forward
--ip-masq
--iptables
--ipv6
--selinux-enabled
--tls
--tlsverify
--userland-proxy=false
--version -v --version -v
" "
case "$prev" in case "$prev" in
--exec-root|--graph|-g)
_filedir -d
return
;;
--log-driver)
__docker_log_drivers
return
;;
--log-level|-l) --log-level|-l)
COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) ) __docker_log_levels
return return
;; ;;
--log-opt) $(__docker_to_extglob "$global_options_with_args") )
__docker_log_driver_options
return
;;
--pidfile|-p|--tlscacert|--tlscert|--tlskey)
_filedir
return
;;
--storage-driver|-s)
COMPREPLY=( $( compgen -W "aufs devicemapper btrfs overlay" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
return
;;
$main_options_with_args_glob )
return return
;; ;;
esac esac
@@ -363,10 +338,10 @@ _docker_docker() {
case "$cur" in case "$cur" in
-*) -*)
COMPREPLY=( $( compgen -W "$boolean_options $main_options_with_args" -- "$cur" ) ) COMPREPLY=( $( compgen -W "$boolean_options $global_options_with_args" -- "$cur" ) )
;; ;;
*) *)
local counter="$(__docker_pos_first_nonflag $main_options_with_args_glob)" local counter=$( __docker_pos_first_nonflag $(__docker_to_extglob "$global_options_with_args") )
if [ $cword -eq $counter ]; then if [ $cword -eq $counter ]; then
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) ) COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
fi fi
@@ -478,6 +453,84 @@ _docker_create() {
_docker_run _docker_run
} }
_docker_daemon() {
local boolean_options="
$global_boolean_options
--help -h
--icc=false
--ip-forward=false
--ip-masq=false
--iptables=false
--ipv6
--selinux-enabled
--userland-proxy=false
"
local options_with_args="
$global_options_with_args
--api-cors-header
--bip
--bridge -b
--default-gateway
--default-gateway-v6
--default-ulimit
--dns
--dns-search
--exec-driver -e
--exec-opt
--exec-root
--fixed-cidr
--fixed-cidr-v6
--graph -g
--group -G
--insecure-registry
--ip
--label
--log-driver
--log-opt
--mtu
--pidfile -p
--registry-mirror
--storage-driver -s
--storage-opt
"
case "$prev" in
--exec-root|--graph|-g)
_filedir -d
return
;;
--log-driver)
__docker_log_drivers
return
;;
--pidfile|-p|--tlscacert|--tlscert|--tlskey)
_filedir
return
;;
--storage-driver|-s)
COMPREPLY=( $( compgen -W "aufs devicemapper btrfs overlay" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
return
;;
--log-level|-l)
__docker_log_levels
return
;;
--log-opt)
__docker_log_driver_options
return
;;
$(__docker_to_extglob "$options_with_args") )
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
;;
esac
}
_docker_diff() { _docker_diff() {
case "$cur" in case "$cur" in
-*) -*)
@@ -685,8 +738,17 @@ _docker_inspect() {
COMPREPLY=( $( compgen -W "--format -f --type --help" -- "$cur" ) ) COMPREPLY=( $( compgen -W "--format -f --type --help" -- "$cur" ) )
;; ;;
*) *)
__docker_containers_and_images case $(__docker_value_of_option --type) in
;; '')
__docker_containers_and_images
;;
container)
__docker_containers_all
;;
image)
__docker_image_repos_and_tags_and_ids
;;
esac
esac esac
} }
@@ -1287,6 +1349,7 @@ _docker() {
commit commit
cp cp
create create
daemon
diff diff
events events
exec exec
@@ -1323,40 +1386,21 @@ _docker() {
wait wait
) )
local main_options_with_args=" # These options are valid as global options for all client commands
--api-cors-header # and valid as command options for `docker daemon`
--bip local global_boolean_options="
--bridge -b --debug -D
--default-gateway --tls
--default-gateway-v6 --tlsverify
--default-ulimit "
--dns local global_options_with_args="
--dns-search
--exec-driver -e
--exec-opt
--exec-root
--fixed-cidr
--fixed-cidr-v6
--graph -g
--group -G
--host -H --host -H
--insecure-registry
--ip
--label
--log-driver
--log-level -l --log-level -l
--log-opt
--mtu
--pidfile -p
--registry-mirror
--storage-driver -s
--storage-opt
--tlscacert --tlscacert
--tlscert --tlscert
--tlskey --tlskey
" "
local main_options_with_args_glob=$(__docker_to_extglob "$main_options_with_args")
local host local host
COMPREPLY=() COMPREPLY=()
@@ -1372,7 +1416,7 @@ _docker() {
(( counter++ )) (( counter++ ))
host="${words[$counter]}" host="${words[$counter]}"
;; ;;
$main_options_with_args_glob ) $(__docker_to_extglob "$global_options_with_args") )
(( counter++ )) (( counter++ ))
;; ;;
-*) -*)
+110 -83
View File
@@ -70,6 +70,66 @@ func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNon
return container.ExtractToDir(path, noOverwriteDirNonDir, content) return container.ExtractToDir(path, noOverwriteDirNonDir, content)
} }
// resolvePath resolves the given path in the container to a resource on the
// host. Returns a resolved path (absolute path to the resource on the host),
// the absolute path to the resource relative to the container's rootfs, and
// a error if the path points to outside the container's rootfs.
func (container *Container) resolvePath(path string) (resolvedPath, absPath string, err error) {
// Consider the given path as an absolute path in the container.
absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
// Split the absPath into its Directory and Base components. We will
// resolve the dir in the scope of the container then append the base.
dirPath, basePath := filepath.Split(absPath)
resolvedDirPath, err := container.GetResourcePath(dirPath)
if err != nil {
return "", "", err
}
// resolvedDirPath will have been cleaned (no trailing path separators) so
// we can manually join it with the base path element.
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
return resolvedPath, absPath, nil
}
// statPath is the unexported version of StatPath. Locks and mounts should
// be aquired before calling this method and the given path should be fully
// resolved to a path on the host corresponding to the given absolute path
// inside the container.
func (container *Container) statPath(resolvedPath, absPath string) (stat *types.ContainerPathStat, err error) {
lstat, err := os.Lstat(resolvedPath)
if err != nil {
return nil, err
}
var linkTarget string
if lstat.Mode()&os.ModeSymlink != 0 {
// Fully evaluate the symlink in the scope of the container rootfs.
hostPath, err := container.GetResourcePath(absPath)
if err != nil {
return nil, err
}
linkTarget, err = filepath.Rel(container.basefs, hostPath)
if err != nil {
return nil, err
}
// Make it an absolute path.
linkTarget = filepath.Join(string(filepath.Separator), linkTarget)
}
return &types.ContainerPathStat{
Name: filepath.Base(absPath),
Size: lstat.Size(),
Mode: lstat.Mode(),
Mtime: lstat.ModTime(),
LinkTarget: linkTarget,
}, nil
}
// StatPath stats the filesystem resource at the specified path in this // StatPath stats the filesystem resource at the specified path in this
// container. Returns stat info about the resource. // container. Returns stat info about the resource.
func (container *Container) StatPath(path string) (stat *types.ContainerPathStat, err error) { func (container *Container) StatPath(path string) (stat *types.ContainerPathStat, err error) {
@@ -87,39 +147,12 @@ func (container *Container) StatPath(path string) (stat *types.ContainerPathStat
return nil, err return nil, err
} }
// Consider the given path as an absolute path in the container. resolvedPath, absPath, err := container.resolvePath(path)
absPath := path
if !filepath.IsAbs(absPath) {
absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path)
}
resolvedPath, err := container.GetResourcePath(absPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// A trailing "." or separator has important meaning. For example, if return container.statPath(resolvedPath, absPath)
// `"foo"` is a symlink to some directory `"dir"`, then `os.Lstat("foo")`
// will stat the link itself, while `os.Lstat("foo/")` will stat the link
// target. If the basename of the path is ".", it means to archive the
// contents of the directory with "." as the first path component rather
// than the name of the directory. This would cause extraction of the
// archive to *not* make another directory, but instead use the current
// directory.
resolvedPath = archive.PreserveTrailingDotOrSeparator(resolvedPath, absPath)
lstat, err := os.Lstat(resolvedPath)
if err != nil {
return nil, err
}
return &types.ContainerPathStat{
Name: lstat.Name(),
Path: absPath,
Size: lstat.Size(),
Mode: lstat.Mode(),
Mtime: lstat.ModTime(),
}, nil
} }
// ArchivePath creates an archive of the filesystem resource at the specified // ArchivePath creates an archive of the filesystem resource at the specified
@@ -154,41 +187,25 @@ func (container *Container) ArchivePath(path string) (content io.ReadCloser, sta
return nil, nil, err return nil, nil, err
} }
// Consider the given path as an absolute path in the container. resolvedPath, absPath, err := container.resolvePath(path)
absPath := path
if !filepath.IsAbs(absPath) {
absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path)
}
resolvedPath, err := container.GetResourcePath(absPath)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// A trailing "." or separator has important meaning. For example, if stat, err = container.statPath(resolvedPath, absPath)
// `"foo"` is a symlink to some directory `"dir"`, then `os.Lstat("foo")`
// will stat the link itself, while `os.Lstat("foo/")` will stat the link
// target. If the basename of the path is ".", it means to archive the
// contents of the directory with "." as the first path component rather
// than the name of the directory. This would cause extraction of the
// archive to *not* make another directory, but instead use the current
// directory.
resolvedPath = archive.PreserveTrailingDotOrSeparator(resolvedPath, absPath)
lstat, err := os.Lstat(resolvedPath)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
stat = &types.ContainerPathStat{ // We need to rebase the archive entries if the last element of the
Name: lstat.Name(), // resolved path was a symlink that was evaluated and is now different
Path: absPath, // than the requested path. For example, if the given path was "/foo/bar/",
Size: lstat.Size(), // but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
Mode: lstat.Mode(), // to ensure that the archive entries start with "bar" and not "baz". This
Mtime: lstat.ModTime(), // also catches the case when the root directory of the container is
} // requested: we want the archive entries to start with "/" and not the
// container ID.
data, err := archive.TarResource(resolvedPath) data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -227,27 +244,21 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool,
return err return err
} }
// Consider the given path as an absolute path in the container. // The destination path needs to be resolved to a host path, with all
absPath := path // symbolic links followed in the scope of the container's rootfs. Note
if !filepath.IsAbs(absPath) { // that we do not use `container.resolvePath(path)` here because we need
absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join("/", path), path) // to also evaluate the last path element if it is a symlink. This is so
} // that you can extract an archive to a symlink that points to a directory.
// Consider the given path as an absolute path in the container.
absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
// This will evaluate the last path element if it is a symlink.
resolvedPath, err := container.GetResourcePath(absPath) resolvedPath, err := container.GetResourcePath(absPath)
if err != nil { if err != nil {
return err return err
} }
// A trailing "." or separator has important meaning. For example, if
// `"foo"` is a symlink to some directory `"dir"`, then `os.Lstat("foo")`
// will stat the link itself, while `os.Lstat("foo/")` will stat the link
// target. If the basename of the path is ".", it means to archive the
// contents of the directory with "." as the first path component rather
// than the name of the directory. This would cause extraction of the
// archive to *not* make another directory, but instead use the current
// directory.
resolvedPath = archive.PreserveTrailingDotOrSeparator(resolvedPath, absPath)
stat, err := os.Lstat(resolvedPath) stat, err := os.Lstat(resolvedPath)
if err != nil { if err != nil {
return err return err
@@ -257,23 +268,23 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool,
return ErrExtractPointNotDirectory return ErrExtractPointNotDirectory
} }
// Need to check if the path is in a volume. If it is, it cannot be in a
// read-only volume. If it is not in a volume, the container cannot be
// configured with a read-only rootfs.
// Use the resolved path relative to the container rootfs as the new
// absPath. This way we fully follow any symlinks in a volume that may
// lead back outside the volume.
baseRel, err := filepath.Rel(container.basefs, resolvedPath) baseRel, err := filepath.Rel(container.basefs, resolvedPath)
if err != nil { if err != nil {
return err return err
} }
absPath = filepath.Join("/", baseRel) // Make it an absolute path.
absPath = filepath.Join(string(filepath.Separator), baseRel)
// Need to check if the path is in a volume. If it is, it cannot be in a toVolume, err := checkIfPathIsInAVolume(container, absPath)
// read-only volume. If it is not in a volume, the container cannot be if err != nil {
// configured with a read-only rootfs. return err
var toVolume bool
for _, mnt := range container.MountPoints {
if toVolume = mnt.hasResource(absPath); toVolume {
if mnt.RW {
break
}
return ErrVolumeReadonly
}
} }
if !toVolume && container.hostConfig.ReadonlyRootfs { if !toVolume && container.hostConfig.ReadonlyRootfs {
@@ -295,3 +306,19 @@ func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool,
return nil return nil
} }
// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
// cannot be in a read-only volume. If it is not in a volume, the container
// cannot be configured with a read-only rootfs.
func checkIfPathIsInAVolume(container *Container, absPath string) (bool, error) {
var toVolume bool
for _, mnt := range container.MountPoints {
if toVolume = mnt.hasResource(absPath); toVolume {
if mnt.RW {
break
}
return false, ErrVolumeReadonly
}
}
return toVolume, nil
}
+20 -8
View File
@@ -41,6 +41,14 @@ var (
ErrContainerRootfsReadonly = errors.New("container rootfs is marked read-only") ErrContainerRootfsReadonly = errors.New("container rootfs is marked read-only")
) )
type ErrContainerNotRunning struct {
id string
}
func (e ErrContainerNotRunning) Error() string {
return fmt.Sprintf("Container %s is not running", e.id)
}
type StreamConfig struct { type StreamConfig struct {
stdout *broadcastwriter.BroadcastWriter stdout *broadcastwriter.BroadcastWriter
stderr *broadcastwriter.BroadcastWriter stderr *broadcastwriter.BroadcastWriter
@@ -371,7 +379,7 @@ func (container *Container) KillSig(sig int) error {
} }
if !container.Running { if !container.Running {
return fmt.Errorf("Container %s is not running", container.ID) return ErrContainerNotRunning{container.ID}
} }
// signal to the monitor that it should not restart the container // signal to the monitor that it should not restart the container
@@ -408,7 +416,7 @@ func (container *Container) Pause() error {
// We cannot Pause the container which is not running // We cannot Pause the container which is not running
if !container.Running { if !container.Running {
return fmt.Errorf("Container %s is not running, cannot pause a non-running container", container.ID) return ErrContainerNotRunning{container.ID}
} }
// We cannot Pause the container which is already paused // We cannot Pause the container which is already paused
@@ -430,7 +438,7 @@ func (container *Container) Unpause() error {
// We cannot unpause the container which is not running // We cannot unpause the container which is not running
if !container.Running { if !container.Running {
return fmt.Errorf("Container %s is not running, cannot unpause a non-running container", container.ID) return ErrContainerNotRunning{container.ID}
} }
// We cannot unpause the container which is not paused // We cannot unpause the container which is not paused
@@ -448,7 +456,7 @@ func (container *Container) Unpause() error {
func (container *Container) Kill() error { func (container *Container) Kill() error {
if !container.IsRunning() { if !container.IsRunning() {
return fmt.Errorf("Container %s is not running", container.ID) return ErrContainerNotRunning{container.ID}
} }
// 1. Send SIGKILL // 1. Send SIGKILL
@@ -530,7 +538,7 @@ func (container *Container) Restart(seconds int) error {
func (container *Container) Resize(h, w int) error { func (container *Container) Resize(h, w int) error {
if !container.IsRunning() { if !container.IsRunning() {
return fmt.Errorf("Cannot resize container %s, container is not running", container.ID) return ErrContainerNotRunning{container.ID}
} }
if err := container.command.ProcessConfig.Terminal.Resize(h, w); err != nil { if err := container.command.ProcessConfig.Terminal.Resize(h, w); err != nil {
return err return err
@@ -1080,8 +1088,12 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
func (container *Container) networkMounts() []execdriver.Mount { func (container *Container) networkMounts() []execdriver.Mount {
var mounts []execdriver.Mount var mounts []execdriver.Mount
mode := "Z"
if container.hostConfig.NetworkMode.IsContainer() {
mode = "z"
}
if container.ResolvConfPath != "" { if container.ResolvConfPath != "" {
label.SetFileLabel(container.ResolvConfPath, container.MountLabel) label.Relabel(container.ResolvConfPath, container.MountLabel, mode)
mounts = append(mounts, execdriver.Mount{ mounts = append(mounts, execdriver.Mount{
Source: container.ResolvConfPath, Source: container.ResolvConfPath,
Destination: "/etc/resolv.conf", Destination: "/etc/resolv.conf",
@@ -1090,7 +1102,7 @@ func (container *Container) networkMounts() []execdriver.Mount {
}) })
} }
if container.HostnamePath != "" { if container.HostnamePath != "" {
label.SetFileLabel(container.HostnamePath, container.MountLabel) label.Relabel(container.HostnamePath, container.MountLabel, mode)
mounts = append(mounts, execdriver.Mount{ mounts = append(mounts, execdriver.Mount{
Source: container.HostnamePath, Source: container.HostnamePath,
Destination: "/etc/hostname", Destination: "/etc/hostname",
@@ -1099,7 +1111,7 @@ func (container *Container) networkMounts() []execdriver.Mount {
}) })
} }
if container.HostsPath != "" { if container.HostsPath != "" {
label.SetFileLabel(container.HostsPath, container.MountLabel) label.Relabel(container.HostsPath, container.MountLabel, mode)
mounts = append(mounts, execdriver.Mount{ mounts = append(mounts, execdriver.Mount{
Source: container.HostsPath, Source: container.HostsPath,
Destination: "/etc/hosts", Destination: "/etc/hosts",
+5 -1
View File
@@ -272,7 +272,11 @@ func populateCommand(c *Container, env []string) error {
BlkioWeight: c.hostConfig.BlkioWeight, BlkioWeight: c.hostConfig.BlkioWeight,
Rlimits: rlimits, Rlimits: rlimits,
OomKillDisable: c.hostConfig.OomKillDisable, OomKillDisable: c.hostConfig.OomKillDisable,
MemorySwappiness: c.hostConfig.MemorySwappiness, MemorySwappiness: -1,
}
if c.hostConfig.MemorySwappiness != nil {
resources.MemorySwappiness = *c.hostConfig.MemorySwappiness
} }
processConfig := execdriver.ProcessConfig{ processConfig := execdriver.ProcessConfig{
+7 -4
View File
@@ -167,13 +167,16 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 {
return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.") return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.")
} }
if hostConfig.MemorySwappiness != -1 && !daemon.SystemConfig().MemorySwappiness { if hostConfig.MemorySwappiness != nil && !daemon.SystemConfig().MemorySwappiness {
warnings = append(warnings, "Your kernel does not support memory swappiness capabilities, memory swappiness discarded.") warnings = append(warnings, "Your kernel does not support memory swappiness capabilities, memory swappiness discarded.")
logrus.Warnf("Your kernel does not support memory swappiness capabilities, memory swappiness discarded.") logrus.Warnf("Your kernel does not support memory swappiness capabilities, memory swappiness discarded.")
hostConfig.MemorySwappiness = -1 hostConfig.MemorySwappiness = nil
} }
if hostConfig.MemorySwappiness != -1 && (hostConfig.MemorySwappiness < 0 || hostConfig.MemorySwappiness > 100) { if hostConfig.MemorySwappiness != nil {
return warnings, fmt.Errorf("Invalid value: %d, valid memory swappiness range is 0-100.", hostConfig.MemorySwappiness) swappiness := *hostConfig.MemorySwappiness
if swappiness < -1 || swappiness > 100 {
return warnings, fmt.Errorf("Invalid value: %v, valid memory swappiness range is 0-100.", swappiness)
}
} }
if hostConfig.CpuPeriod > 0 && !daemon.SystemConfig().CpuCfsPeriod { if hostConfig.CpuPeriod > 0 && !daemon.SystemConfig().CpuCfsPeriod {
warnings = append(warnings, "Your kernel does not support CPU cfs period. Period discarded.") warnings = append(warnings, "Your kernel does not support CPU cfs period. Period discarded.")
@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"path" "path"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/daemon/execdriver/lxc"
"github.com/docker/docker/daemon/execdriver/native" "github.com/docker/docker/daemon/execdriver/native"
@@ -18,6 +19,7 @@ func NewDriver(name string, options []string, root, libPath, initPath string, sy
// we want to give the lxc driver the full docker root because it needs // 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 access and write config and template files in /var/lib/docker/containers/*
// to be backwards compatible // to be backwards compatible
logrus.Warn("LXC built-in support is deprecated.")
return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor) return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor)
case "native": case "native":
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, options) return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, options)
+164
View File
@@ -0,0 +1,164 @@
// +build linux
package native
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"path"
"strings"
"text/template"
"github.com/opencontainers/runc/libcontainer/apparmor"
)
const (
apparmorProfilePath = "/etc/apparmor.d/docker"
)
type data struct {
Name string
Imports []string
InnerImports []string
}
const baseTemplate = `
{{range $value := .Imports}}
{{$value}}
{{end}}
profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
{{range $value := .InnerImports}}
{{$value}}
{{end}}
network,
capability,
file,
umount,
signal (receive) peer=/usr/bin/docker,
signal (receive) peer=docker-unconfined,
deny @{PROC}/sys/fs/** wklx,
deny @{PROC}/fs/** wklx,
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/kore rwklx,
deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
deny @{PROC}/sys/kernel/*/** wklx,
deny mount,
deny ptrace (trace) peer=docker-default,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/efi/efivars/** rwklx,
deny /sys/kernel/security/** rwklx,
}
profile docker-unconfined flags=(attach_disconnected,mediate_deleted,complain) {
#include <abstractions/base>
network,
capability,
file,
umount,
mount,
pivot_root,
change_profile -> *,
ptrace,
signal,
}
`
func generateProfile(out io.Writer) error {
compiled, err := template.New("apparmor_profile").Parse(baseTemplate)
if err != nil {
return err
}
data := &data{
Name: "docker-default",
}
if tunablesExists() {
data.Imports = append(data.Imports, "#include <tunables/global>")
} else {
data.Imports = append(data.Imports, "@{PROC}=/proc/")
}
if abstractionsExists() {
data.InnerImports = append(data.InnerImports, "#include <abstractions/base>")
}
if err := compiled.Execute(out, data); err != nil {
return err
}
return nil
}
// check if the tunables/global exist
func tunablesExists() bool {
_, err := os.Stat("/etc/apparmor.d/tunables/global")
return err == nil
}
// check if abstractions/base exist
func abstractionsExists() bool {
_, err := os.Stat("/etc/apparmor.d/abstractions/base")
return err == nil
}
func installAppArmorProfile() error {
if !apparmor.IsEnabled() {
return nil
}
// Make sure /etc/apparmor.d exists
if err := os.MkdirAll(path.Dir(apparmorProfilePath), 0755); err != nil {
return err
}
f, err := os.OpenFile(apparmorProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
if err := generateProfile(f); err != nil {
f.Close()
return err
}
f.Close()
cmd := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker")
// to use the parser directly we have to make sure we are in the correct
// dir with the profile
cmd.Dir = "/etc/apparmor.d"
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Error loading docker apparmor profile: %s (%s)", err, output)
}
return nil
}
func hasAppArmorProfileLoaded(profile string) error {
file, err := os.Open("/sys/kernel/security/apparmor/profiles")
if err != nil {
return err
}
r := bufio.NewReader(file)
for {
p, err := r.ReadString('\n')
if err != nil {
return err
}
if strings.HasPrefix(p, profile+" ") {
return nil
}
}
}
+2 -2
View File
@@ -85,7 +85,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error)
} }
/* These paths must be remounted as r/o */ /* These paths must be remounted as r/o */
container.ReadonlyPaths = append(container.ReadonlyPaths, "/proc", "/dev") container.ReadonlyPaths = append(container.ReadonlyPaths, "/dev")
} }
if err := d.setupMounts(container, c); err != nil { if err := d.setupMounts(container, c); err != nil {
@@ -198,7 +198,7 @@ func (d *driver) setPrivileged(container *configs.Config) (err error) {
container.Devices = hostDevices container.Devices = hostDevices
if apparmor.IsEnabled() { if apparmor.IsEnabled() {
container.AppArmorProfile = "unconfined" container.AppArmorProfile = "docker-unconfined"
} }
return nil return nil
+15
View File
@@ -21,6 +21,7 @@ import (
sysinfo "github.com/docker/docker/pkg/system" sysinfo "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term"
"github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/cgroups/systemd" "github.com/opencontainers/runc/libcontainer/cgroups/systemd"
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/system"
@@ -51,6 +52,20 @@ func NewDriver(root, initPath string, options []string) (*driver, error) {
return nil, err return nil, err
} }
if apparmor.IsEnabled() {
if err := installAppArmorProfile(); err != nil {
apparmor_profiles := []string{"docker-default", "docker-unconfined"}
// Allow daemon to run if loading failed, but are active
// (possibly through another run, manually, or via system startup)
for _, policy := range apparmor_profiles {
if err := hasAppArmorProfileLoaded(policy); err != nil {
return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
}
}
}
}
// choose cgroup manager // choose cgroup manager
// this makes sure there are no breaking changes to people // this makes sure there are no breaking changes to people
// who upgrade from versions without native.cgroupdriver opt // who upgrade from versions without native.cgroupdriver opt
+1 -1
View File
@@ -323,7 +323,7 @@ func (a *Driver) Diff(id, parent string) (archive.Archive, error) {
} }
func (a *Driver) applyDiff(id string, diff archive.ArchiveReader) error { func (a *Driver) applyDiff(id string, diff archive.ArchiveReader) error {
return chrootarchive.Untar(diff, path.Join(a.rootPath(), "diff", id), nil) return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), nil)
} }
// DiffSize calculates the changes between the specified id // DiffSize calculates the changes between the specified id
+1
View File
@@ -77,6 +77,7 @@ type Driver interface {
// ApplyDiff extracts the changeset from the given diff into the // ApplyDiff extracts the changeset from the given diff into the
// layer with the specified id and parent, returning the size of the // layer with the specified id and parent, returning the size of the
// new layer in bytes. // new layer in bytes.
// The archive.ArchiveReader must be an uncompressed stream.
ApplyDiff(id, parent string, diff archive.ArchiveReader) (size int64, err error) ApplyDiff(id, parent string, diff archive.ArchiveReader) (size int64, err error)
// DiffSize calculates the changes between the specified id // DiffSize calculates the changes between the specified id
// and its parent and returns the size in bytes of the changes // and its parent and returns the size in bytes of the changes
+1 -1
View File
@@ -121,7 +121,7 @@ func (gdw *naiveDiffDriver) ApplyDiff(id, parent string, diff archive.ArchiveRea
start := time.Now().UTC() start := time.Now().UTC()
logrus.Debugf("Start untar layer") logrus.Debugf("Start untar layer")
if size, err = chrootarchive.ApplyLayer(layerFs, diff); err != nil { if size, err = chrootarchive.ApplyUncompressedLayer(layerFs, diff); err != nil {
return return
} }
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
+1 -1
View File
@@ -411,7 +411,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff archive.ArchiveReader)
return 0, err return 0, err
} }
if size, err = chrootarchive.ApplyLayer(tmpRootDir, diff); err != nil { if size, err = chrootarchive.ApplyUncompressedLayer(tmpRootDir, diff); err != nil {
return 0, err return 0, err
} }
+4 -3
View File
@@ -2,6 +2,7 @@ package daemon
import ( import (
"fmt" "fmt"
"time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
) )
@@ -91,13 +92,13 @@ func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSON
Pid: container.State.Pid, Pid: container.State.Pid,
ExitCode: container.State.ExitCode, ExitCode: container.State.ExitCode,
Error: container.State.Error, Error: container.State.Error,
StartedAt: container.State.StartedAt, StartedAt: container.State.StartedAt.Format(time.RFC3339Nano),
FinishedAt: container.State.FinishedAt, FinishedAt: container.State.FinishedAt.Format(time.RFC3339Nano),
} }
contJSONBase := &types.ContainerJSONBase{ contJSONBase := &types.ContainerJSONBase{
Id: container.ID, Id: container.ID,
Created: container.Created, Created: container.Created.Format(time.RFC3339Nano),
Path: container.Path, Path: container.Path,
Args: container.Args, Args: container.Args,
State: containerState, State: containerState,
+3 -6
View File
@@ -1,9 +1,6 @@
package daemon package daemon
import ( import "syscall"
"fmt"
"syscall"
)
// ContainerKill send signal to the container // ContainerKill send signal to the container
// If no signal is given (sig 0), then Kill with SIGKILL and wait // If no signal is given (sig 0), then Kill with SIGKILL and wait
@@ -18,12 +15,12 @@ func (daemon *Daemon) ContainerKill(name string, sig uint64) error {
// If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait())
if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL {
if err := container.Kill(); err != nil { if err := container.Kill(); err != nil {
return fmt.Errorf("Cannot kill container %s: %s", name, err) return err
} }
} else { } else {
// Otherwise, just send the requested signal // Otherwise, just send the requested signal
if err := container.KillSig(int(sig)); err != nil { if err := container.KillSig(int(sig)); err != nil {
return fmt.Errorf("Cannot kill container %s: %s", name, err) return err
} }
} }
return nil return nil
+2 -2
View File
@@ -93,9 +93,9 @@ func New(ctx logger.Context) (logger.Logger, error) {
} }
logrus.Debugf("logging driver fluentd configured for container:%s, host:%s, port:%d, tag:%s.", ctx.ContainerID, host, port, tag) logrus.Debugf("logging driver fluentd configured for container:%s, host:%s, port:%d, tag:%s.", ctx.ContainerID, host, port, tag)
// logger tries to recoonect 2**64 - 1 times // logger tries to recoonect 2**32 - 1 times
// failed (and panic) after 204 years [ 1.5 ** (2**32 - 1) - 1 seconds] // failed (and panic) after 204 years [ 1.5 ** (2**32 - 1) - 1 seconds]
log, err := fluent.New(fluent.Config{FluentPort: port, FluentHost: host, RetryWait: 1000, MaxRetry: math.MaxUint32}) log, err := fluent.New(fluent.Config{FluentPort: port, FluentHost: host, RetryWait: 1000, MaxRetry: math.MaxInt32})
if err != nil { if err != nil {
return nil, err return nil, err
} }
+2 -1
View File
@@ -259,7 +259,8 @@ func (l *JSONFileLogger) readLogs(logWatcher *logger.LogWatcher, config logger.R
if !config.Follow { if !config.Follow {
return return
} }
if config.Tail == 0 {
if config.Tail >= 0 {
latestFile.Seek(0, os.SEEK_END) latestFile.Seek(0, os.SEEK_END)
} }
+6 -1
View File
@@ -64,7 +64,12 @@ func NewLogWatcher() *LogWatcher {
// Close notifies the underlying log reader to stop // Close notifies the underlying log reader to stop
func (w *LogWatcher) Close() { func (w *LogWatcher) Close() {
close(w.closeNotifier) // only close if not already closed
select {
case <-w.closeNotifier:
default:
close(w.closeNotifier)
}
} }
// WatchClose returns a channel receiver that receives notification when the watcher has been closed // WatchClose returns a channel receiver that receives notification when the watcher has been closed
+2 -1
View File
@@ -100,6 +100,7 @@ func migrateKey() (err error) {
err = os.Remove(oldPath) err = os.Remove(oldPath)
} else { } else {
logrus.Warnf("Key migration failed, key file not removed at %s", oldPath) logrus.Warnf("Key migration failed, key file not removed at %s", oldPath)
os.Remove(newPath)
} }
}() }()
@@ -226,7 +227,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
} }
tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions) tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions)
if err != nil { if err != nil {
logrus.Fatalf("foobar: %v", err) logrus.Fatal(err)
} }
serverConfig.TLSConfig = tlsConfig serverConfig.TLSConfig = tlsConfig
} }
+1 -1
View File
@@ -12,7 +12,7 @@ weight = 99
# Automatically start containers # Automatically start containers
As of Docker 1.2, As of Docker 1.2,
[restart policies](/reference/commandline/cli/#restart-policies) are the [restart policies](/reference/run/#restart-policies-restart) are the
built-in Docker mechanism for restarting containers when they exit. If set, built-in Docker mechanism for restarting containers when they exit. If set,
restart policies will be used when the Docker daemon starts up, as typically restart policies will be used when the Docker daemon starts up, as typically
happens after a system boot. Restart policies will ensure that linked containers happens after a system boot. Restart policies will ensure that linked containers
+6
View File
@@ -31,6 +31,12 @@ Follow the instructions in the plugin's documentation.
The following plugins exist: The following plugins exist:
* The [Blockbridge plugin](https://github.com/blockbridge/blockbridge-docker-volume)
is a volume plugin that provides access to an extensible set of
container-based persistent storage options. It supports single and multi-host Docker
environments with features that include tenant isolation, automated
provisioning, encryption, secure deletion, snapshots and QoS.
* The [Flocker plugin](https://clusterhq.com/docker-plugin/) is a volume plugin * The [Flocker plugin](https://clusterhq.com/docker-plugin/) is a volume plugin
which provides multi-host portable volumes for Docker, enabling you to run which provides multi-host portable volumes for Docker, enabling you to run
databases and other stateful containers and move them around across a cluster databases and other stateful containers and move them around across a cluster
+1 -2
View File
@@ -64,8 +64,7 @@ a container. To exit the container type `exit`.
If you want your containers to be able to access the external network you must If you want your containers to be able to access the external network you must
enable the `net.ipv4.ip_forward` rule. enable the `net.ipv4.ip_forward` rule.
This can be done using YaST by browsing to the This can be done using YaST by browsing to the
`Network Devices -> Network Settings -> Routing` menu and ensuring that the `System -> Network Settings -> Routing` menu (for openSUSE Tumbleweed and later) or `Network Devices -> Network Settings -> Routing` menu (for SUSE Linux Enterprise 12 and previous openSUSE versions) and ensuring that the `Enable IPv4 Forwarding` box is checked.
`Enable IPv4 Forwarding` box is checked.
This option cannot be changed when networking is handled by the Network Manager. This option cannot be changed when networking is handled by the Network Manager.
In such cases the `/etc/sysconfig/SuSEfirewall2` file needs to be edited by In such cases the `/etc/sysconfig/SuSEfirewall2` file needs to be edited by
+8
View File
@@ -12,6 +12,14 @@ parent = "mn_use_docker"
The following list of features are deprecated. The following list of features are deprecated.
### LXC built-in exec driver
**Deprecated In Release: v1.8**
**Target For Removal In Release: v1.10**
The built-in LXC execution driver is deprecated for an external implementation.
The lxc-conf flag and API fields will also be removed.
### Old Command Line Options ### Old Command Line Options
**Deprecated In Release: [v1.8.0](/release-notes/#docker-engine-1-8-0)** **Deprecated In Release: [v1.8.0](/release-notes/#docker-engine-1-8-0)**
+2 -1
View File
@@ -108,7 +108,7 @@ of a 404.
You can now supply a `stream` bool to get only one set of stats and You can now supply a `stream` bool to get only one set of stats and
disconnect disconnect
`GET /containers(id)/logs` `GET /containers/(id)/logs`
**New!** **New!**
@@ -138,6 +138,7 @@ In addition, the end point now returns the new boolean fields
This endpoint now returns `Os`, `Arch` and `KernelVersion`. This endpoint now returns `Os`, `Arch` and `KernelVersion`.
`POST /containers/create` `POST /containers/create`
`POST /containers/(id)/start` `POST /containers/(id)/start`
**New!** **New!**
@@ -1109,7 +1109,7 @@ Query Parameters:
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Type: application/x-tar Content-Type: application/x-tar
X-Docker-Container-Path-Stat: eyJuYW1lIjoicm9vdCIsInBhdGgiOiIvcm9vdCIsInNpemUiOjQwOTYsIm1vZGUiOjIxNDc0ODQwOTYsIm10aW1lIjoiMjAxNC0wMi0yN1QyMDo1MToyM1oifQ== X-Docker-Container-Path-Stat: eyJuYW1lIjoicm9vdCIsInNpemUiOjQwOTYsIm1vZGUiOjIxNDc0ODQwOTYsIm10aW1lIjoiMjAxNC0wMi0yN1QyMDo1MToyM1oiLCJsaW5rVGFyZ2V0IjoiIn0=
{{ TAR STREAM }} {{ TAR STREAM }}
@@ -1120,10 +1120,10 @@ JSON object (whitespace added for readability):
{ {
"name": "root", "name": "root",
"path": "/root",
"size": 4096, "size": 4096,
"mode": 2147484096, "mode": 2147484096,
"mtime": "2014-02-27T20:51:23Z" "mtime": "2014-02-27T20:51:23Z",
"linkTarget": ""
} }
A `HEAD` request can also be made to this endpoint if only this information is A `HEAD` request can also be made to this endpoint if only this information is
+45
View File
@@ -0,0 +1,45 @@
AppArmor security profiles for Docker
--------------------------------------
AppArmor (Application Armor) is a security module that allows a system
administrator to associate a security profile with each program. Docker
expects to find an AppArmor policy loaded and enforced.
Container profiles are loaded automatically by Docker. A profile
for the Docker Engine itself also exists and is installed
with the official *.deb* packages. Advanced users and package
managers may find the profile for */usr/bin/docker* underneath
[contrib/apparmor](https://github.com/docker/docker/tree/master/contrib/apparmor)
in the Docker Engine source repository.
Understand the policies
------------------------
The `docker-default` profile the default for running
containers. It is moderately protective while
providing wide application compatability.
The `docker-unconfined` profile is intended for
privileged applications and is the default when runing
a container with the *--privileged* flag.
The system's standard `unconfined` profile inherits all
system-wide policies, applying path-based policies
intended for the host system inside of containers.
This was the default for privileged containers
prior to Docker 1.8.
Overriding the profile for a container
---------------------------------------
Users may override the AppArmor profile using the
`security-opt` option (per-container).
For example, the following explicitly specifies the default policy:
```
$ docker run --rm -it --security-opt apparmor:docker-default hello-world
```
-2
View File
@@ -61,8 +61,6 @@ After downloading the appropriate binary, you can follow the instructions
## Current experimental features ## Current experimental features
* [Support for Docker plugins](plugins.md)
* [Volume plugins](plugins_volume.md)
* [Network plugins](plugins_network.md) * [Network plugins](plugins_network.md)
* [Native Multi-host networking](networking.md) * [Native Multi-host networking](networking.md)
* [Compose, Swarm and networking integration](compose_swarm_networking.md) * [Compose, Swarm and networking integration](compose_swarm_networking.md)
+33 -27
View File
@@ -102,13 +102,12 @@ func (p *v2Puller) pullV2Repository(tag string) (err error) {
// downloadInfo is used to pass information from download to extractor // downloadInfo is used to pass information from download to extractor
type downloadInfo struct { type downloadInfo struct {
img *image.Image img *image.Image
tmpFile *os.File tmpFile *os.File
digest digest.Digest digest digest.Digest
layer distribution.ReadSeekCloser layer distribution.ReadSeekCloser
size int64 size int64
err chan error err chan error
verified bool
} }
type errVerification struct{} type errVerification struct{}
@@ -176,9 +175,11 @@ func (p *v2Puller) download(di *downloadInfo) {
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Verifying Checksum", nil)) out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Verifying Checksum", nil))
di.verified = verifier.Verified() if !verifier.Verified() {
if !di.verified { err = fmt.Errorf("filesystem layer verification failed for digest %s", di.digest)
logrus.Infof("Image verification failed for layer %s", di.digest) logrus.Error(err)
di.err <- err
return
} }
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Download complete", nil)) out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Download complete", nil))
@@ -252,7 +253,6 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
return false, err return false, err
} }
} }
verified = verified && d.verified
if d.layer != nil { if d.layer != nil {
// if tmpFile is empty assume download and extracted elsewhere // if tmpFile is empty assume download and extracted elsewhere
defer os.Remove(d.tmpFile.Name()) defer os.Remove(d.tmpFile.Name())
@@ -368,6 +368,28 @@ func (p *v2Puller) verifyTrustedKeys(namespace string, keys []libtrust.PublicKey
} }
func (p *v2Puller) validateManifest(m *manifest.SignedManifest, tag string) (verified bool, err error) { func (p *v2Puller) validateManifest(m *manifest.SignedManifest, tag string) (verified bool, err error) {
// If pull by digest, then verify the manifest digest. NOTE: It is
// important to do this first, before any other content validation. If the
// digest cannot be verified, don't even bother with those other things.
if manifestDigest, err := digest.ParseDigest(tag); err == nil {
verifier, err := digest.NewDigestVerifier(manifestDigest)
if err != nil {
return false, err
}
payload, err := m.Payload()
if err != nil {
return false, err
}
if _, err := verifier.Write(payload); err != nil {
return false, err
}
if !verifier.Verified() {
err := fmt.Errorf("image verification failed for digest %s", manifestDigest)
logrus.Error(err)
return false, err
}
}
// TODO(tiborvass): what's the usecase for having manifest == nil and err == nil ? Shouldn't be the error be "DoesNotExist" ? // TODO(tiborvass): what's the usecase for having manifest == nil and err == nil ? Shouldn't be the error be "DoesNotExist" ?
if m == nil { if m == nil {
return false, fmt.Errorf("image manifest does not exist for tag %q", tag) return false, fmt.Errorf("image manifest does not exist for tag %q", tag)
@@ -389,21 +411,5 @@ func (p *v2Puller) validateManifest(m *manifest.SignedManifest, tag string) (ver
if err != nil { if err != nil {
return false, fmt.Errorf("error verifying manifest keys: %v", err) return false, fmt.Errorf("error verifying manifest keys: %v", err)
} }
localDigest, err := digest.ParseDigest(tag)
// if pull by digest, then verify
if err == nil {
verifier, err := digest.NewDigestVerifier(localDigest)
if err != nil {
return false, err
}
payload, err := m.Payload()
if err != nil {
return false, err
}
if _, err := verifier.Write(payload); err != nil {
return false, err
}
verified = verified && verifier.Verified()
}
return verified, nil return verified, nil
} }
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"runtime" "runtime"
"time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@@ -34,7 +35,7 @@ func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) {
Id: image.ID, Id: image.ID,
Parent: image.Parent, Parent: image.Parent,
Comment: image.Comment, Comment: image.Comment,
Created: image.Created, Created: image.Created.Format(time.RFC3339Nano),
Container: image.Container, Container: image.Container,
ContainerConfig: &image.ContainerConfig, ContainerConfig: &image.ContainerConfig,
DockerVersion: image.DockerVersion, DockerVersion: image.DockerVersion,
+52 -17
View File
@@ -119,41 +119,76 @@ do_install() {
dist_version='' dist_version=''
if command_exists lsb_release; then if command_exists lsb_release; then
lsb_dist="$(lsb_release -si)" lsb_dist="$(lsb_release -si)"
dist_version="$(lsb_release --codename | cut -f2)"
fi fi
if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then
lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")" lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")"
dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
fi fi
if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then
lsb_dist='debian' lsb_dist='debian'
dist_version="$(cat /etc/debian_version | sed 's/\/.*//' | sed 's/\..*//')"
case "$dist_version" in
8)
dist_version="jessie"
;;
7)
dist_version="wheezy"
;;
esac
fi fi
if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then
lsb_dist='fedora' lsb_dist='fedora'
dist_version="$(rpm -qa \*-release | cut -d"-" -f3 | head -n1)" fi
if [ -z "$lsb_dist" ] && [ -r /etc/oracle-release ]; then
lsb_dist='oracleserver'
fi fi
if [ -z "$lsb_dist" ]; then if [ -z "$lsb_dist" ]; then
if [ -r /etc/centos-release ] || [ -r /etc/redhat-release ]; then if [ -r /etc/centos-release ] || [ -r /etc/redhat-release ]; then
lsb_dist='centos' lsb_dist='centos'
dist_version="$(rpm -qa \*-release | cut -d"-" -f3 | head -n1)"
fi fi
fi fi
if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then
lsb_dist="$(. /etc/os-release && echo "$ID")" lsb_dist="$(. /etc/os-release && echo "$ID")"
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi fi
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
case "$lsb_dist" in
ubuntu)
if command_exists lsb_release; then
dist_version="$(lsb_release --codename | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
fi
;;
debian)
dist_version="$(cat /etc/debian_version | sed 's/\/.*//' | sed 's/\..*//')"
case "$dist_version" in
8)
dist_version="jessie"
;;
7)
dist_version="wheezy"
;;
esac
;;
oracleserver)
# need to switch lsb_dist to match yum repo URL
lsb_dist="oraclelinux"
dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n" | sed 's/\/.*//' | sed 's/\..*//')"
;;
fedora|centos)
dist_version="$(rpm -q --whatprovides redhat-release --queryformat "%{VERSION}\n" | sed 's/\/.*//' | sed 's/\..*//')"
;;
*)
if command_exists lsb_release; then
dist_version="$(lsb_release --codename | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi
;;
esac
case "$lsb_dist" in case "$lsb_dist" in
amzn) amzn)
( (
@@ -237,8 +272,8 @@ do_install() {
exit 0 exit 0
;; ;;
fedora|centos) fedora|centos|oraclelinux)
cat >/etc/yum.repos.d/docker-${repo}.repo <<-EOF $sh_c "cat >/etc/yum.repos.d/docker-${repo}.repo" <<-EOF
[docker-${repo}-repo] [docker-${repo}-repo]
name=Docker ${repo} Repository name=Docker ${repo} Repository
baseurl=https://yum.dockerproject.org/repo/${repo}/${lsb_dist}/${dist_version} baseurl=https://yum.dockerproject.org/repo/${repo}/${lsb_dist}/${dist_version}
+1 -1
View File
@@ -33,7 +33,7 @@ override_dh_installudev:
dh_installudev --priority=z80 dh_installudev --priority=z80
override_dh_install: override_dh_install:
dh_apparmor --profile-name=docker -pdocker-engine dh_install
dh_apparmor --profile-name=docker-engine -pdocker-engine dh_apparmor --profile-name=docker-engine -pdocker-engine
%: %:
+12 -10
View File
@@ -21,17 +21,19 @@ APTDIR=$DOCKER_RELEASE_DIR/apt/repo
mkdir -p "$APTDIR/conf" "$APTDIR/db" mkdir -p "$APTDIR/conf" "$APTDIR/db"
# create/update distributions file # create/update distributions file
for suite in $(exec contrib/reprepro/suites.sh); do if [[ ! -f "$APTDIR/conf/distributions" ]]; then
cat <<-EOF for suite in $(exec contrib/reprepro/suites.sh); do
Origin: Docker cat <<-EOF
Suite: $suite Origin: Docker
Codename: $suite Suite: $suite
Architectures: amd64 i386 Codename: $suite
Components: main testing experimental Architectures: amd64 i386
Description: Docker APT Repository Components: main testing experimental
Description: Docker APT Repository
EOF EOF
done > "$APTDIR/conf/distributions" done > "$APTDIR/conf/distributions"
fi
# set the component and priority for the version being released # set the component and priority for the version being released
component="main" component="main"
+1 -3
View File
@@ -74,8 +74,7 @@ bundle_ubuntu() {
# Include contributed apparmor policy # Include contributed apparmor policy
mkdir -p "$DIR/etc/apparmor.d/" mkdir -p "$DIR/etc/apparmor.d/"
cp contrib/apparmor/docker "$DIR/etc/apparmor.d/" cp contrib/apparmor/* "$DIR/etc/apparmor.d/"
cp contrib/apparmor/docker-engine "$DIR/etc/apparmor.d/"
# Copy the binary # Copy the binary
# This will fail if the binary bundle hasn't been built # This will fail if the binary bundle hasn't been built
@@ -95,7 +94,6 @@ if [ "$1" = 'configure' ] && [ -z "$2" ]; then
fi fi
if ( aa-status --enabled ); then if ( aa-status --enabled ); then
/sbin/apparmor_parser -r -W -T /etc/apparmor.d/docker
/sbin/apparmor_parser -r -W -T /etc/apparmor.d/docker-engine /sbin/apparmor_parser -r -W -T /etc/apparmor.d/docker-engine
fi fi
+7
View File
@@ -70,6 +70,7 @@ BUCKET=$AWS_S3_BUCKET
# GPG_KEY="740B314AE3941731B942C66ADF4FD13717AAD7D6" # GPG_KEY="740B314AE3941731B942C66ADF4FD13717AAD7D6"
setup_s3() { setup_s3() {
echo "Setting up S3"
# Try creating the bucket. Ignore errors (it might already exist). # Try creating the bucket. Ignore errors (it might already exist).
s3cmd mb "s3://$BUCKET" 2>/dev/null || true s3cmd mb "s3://$BUCKET" 2>/dev/null || true
# Check access to the bucket. # Check access to the bucket.
@@ -102,6 +103,7 @@ s3_url() {
} }
build_all() { build_all() {
echo "Building release"
if ! ./hack/make.sh "${RELEASE_BUNDLES[@]}"; then if ! ./hack/make.sh "${RELEASE_BUNDLES[@]}"; then
echo >&2 echo >&2
echo >&2 'The build or tests appear to have failed.' echo >&2 'The build or tests appear to have failed.'
@@ -162,6 +164,7 @@ upload_release_build() {
} }
release_build() { release_build() {
echo "Releasing binaries"
GOOS=$1 GOOS=$1
GOARCH=$2 GOARCH=$2
@@ -246,6 +249,7 @@ release_build() {
# 1. A full APT repository is published at $BUCKET/ubuntu/ # 1. A full APT repository is published at $BUCKET/ubuntu/
# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index # 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index
release_ubuntu() { release_ubuntu() {
echo "Releasing ubuntu"
[ -e "bundles/$VERSION/ubuntu" ] || { [ -e "bundles/$VERSION/ubuntu" ] || {
echo >&2 './hack/make.sh must be run before release_ubuntu' echo >&2 './hack/make.sh must be run before release_ubuntu'
exit 1 exit 1
@@ -338,16 +342,19 @@ EOF
# Upload the index script # Upload the index script
release_index() { release_index() {
echo "Releasing index"
sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 "s3://$BUCKET/index" sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 "s3://$BUCKET/index"
} }
release_test() { release_test() {
echo "Releasing tests"
if [ -e "bundles/$VERSION/test" ]; then if [ -e "bundles/$VERSION/test" ]; then
s3cmd --acl-public sync "bundles/$VERSION/test/" "s3://$BUCKET/test/" s3cmd --acl-public sync "bundles/$VERSION/test/" "s3://$BUCKET/test/"
fi fi
} }
setup_gpg() { setup_gpg() {
echo "Setting up GPG"
# Make sure that we have our keys # Make sure that we have our keys
mkdir -p "$HOME/.gnupg/" mkdir -p "$HOME/.gnupg/"
s3cmd sync "s3://$BUCKET/ubuntu/.gnupg/" "$HOME/.gnupg/" || true s3cmd sync "s3://$BUCKET/ubuntu/.gnupg/" "$HOME/.gnupg/" || true
+4 -4
View File
@@ -21,7 +21,7 @@ clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://gith
clone hg code.google.com/p/gosqlite 74691fb6f837 clone hg code.google.com/p/gosqlite 74691fb6f837
#get libnetwork packages #get libnetwork packages
clone git github.com/docker/libnetwork f1c5671f1ee2133055144e566cd8b3a0ae4f0433 clone git github.com/docker/libnetwork 78fc31ddc425fb379765c6b7ab5b96748bd8fc08
clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4 clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4
@@ -35,11 +35,11 @@ clone git github.com/coreos/go-etcd v2.0.0
clone git github.com/hashicorp/consul v0.5.2 clone git github.com/hashicorp/consul v0.5.2
# get graph and distribution packages # get graph and distribution packages
clone git github.com/docker/distribution cd8ff553b6b1911be23dfeabb73e33108bcbf147 clone git github.com/docker/distribution 7dc8d4a26b689bd4892f2f2322dbce0b7119d686
clone git github.com/vbatts/tar-split v0.9.4 clone git github.com/vbatts/tar-split v0.9.4
clone git github.com/docker/notary 77bced079e83d80f40c1f0a544b1a8a3b97fb052 clone git github.com/docker/notary 8e8122eb5528f621afcd4e2854c47302f17392f7
clone git github.com/endophage/gotuf 374908abc8af7e953a2813c5c2b3944ab625ca68 clone git github.com/endophage/gotuf 89ceb27829b9353dfee5ccccf7a3a9bb77008b05
clone git github.com/tent/canonical-json-go 96e4ba3a7613a1216cbd1badca4efe382adea337 clone git github.com/tent/canonical-json-go 96e4ba3a7613a1216cbd1badca4efe382adea337
clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
@@ -4,9 +4,11 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@@ -1687,3 +1689,45 @@ func (s *DockerSuite) TestPostContainersStartWithLinksInHostConfigIdLinked(c *ch
c.Assert(res.StatusCode, check.Equals, http.StatusNoContent) c.Assert(res.StatusCode, check.Equals, http.StatusNoContent)
b.Close() b.Close()
} }
// #14915
func (s *DockerSuite) TestContainersApiCreateNoHostConfig118(c *check.C) {
config := struct {
Image string
}{"busybox"}
status, _, err := sockRequest("POST", "/v1.18/containers/create", config)
c.Assert(err, check.IsNil)
c.Assert(status, check.Equals, http.StatusCreated)
}
// Ensure an error occurs when you have a container read-only rootfs but you
// extract an archive to a symlink in a writable volume which points to a
// directory outside of the volume.
func (s *DockerSuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *check.C) {
testRequires(c, SameHostDaemon) // Requires local volume mount bind.
testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-")
defer os.RemoveAll(testVol)
makeTestContentInDir(c, testVol)
cID := makeTestContainer(c, testContainerOptions{
readOnly: true,
volumes: defaultVolumes(testVol), // Our bind mount is at /vol2
})
defer deleteContainer(cID)
// Attempt to extract to a symlink in the volume which points to a
// directory outside the volume. This should cause an error because the
// rootfs is read-only.
query := make(url.Values, 1)
query.Set("path", "/vol2/symlinkToAbsDir")
urlPath := fmt.Sprintf("/v1.20/containers/%s/archive?%s", cID, query.Encode())
statusCode, body, err := sockRequest("PUT", urlPath, nil)
c.Assert(err, check.IsNil)
if !isCpCannotCopyReadOnly(fmt.Errorf(string(body))) {
c.Fatalf("expected ErrContainerRootfsReadonly error, but got %d: %s", statusCode, string(body))
}
}
+47 -2
View File
@@ -5349,8 +5349,15 @@ func (s *DockerTrustSuite) TestTrustedBuild(c *check.C) {
c.Fatalf("Unexpected output on trusted build:\n%s", out) c.Fatalf("Unexpected output on trusted build:\n%s", out)
} }
// Build command does not create untrusted tag // We should also have a tag reference for the image.
//dockerCmd(c, "rmi", repoName) if out, exitCode := dockerCmd(c, "inspect", repoName); exitCode != 0 {
c.Fatalf("unexpected exit code inspecting image %q: %d: %s", repoName, exitCode, out)
}
// We should now be able to remove the tag reference.
if out, exitCode := dockerCmd(c, "rmi", repoName); exitCode != 0 {
c.Fatalf("unexpected exit code inspecting image %q: %d: %s", repoName, exitCode, out)
}
} }
func (s *DockerTrustSuite) TestTrustedBuildUntrustedTag(c *check.C) { func (s *DockerTrustSuite) TestTrustedBuildUntrustedTag(c *check.C) {
@@ -5373,3 +5380,41 @@ func (s *DockerTrustSuite) TestTrustedBuildUntrustedTag(c *check.C) {
c.Fatalf("Unexpected output on trusted build with untrusted tag:\n%s", out) c.Fatalf("Unexpected output on trusted build with untrusted tag:\n%s", out)
} }
} }
func (s *DockerTrustSuite) TestBuildContextDirIsSymlink(c *check.C) {
tempDir, err := ioutil.TempDir("", "test-build-dir-is-symlink-")
if err != nil {
c.Fatal(err)
}
defer os.RemoveAll(tempDir)
// Make a real context directory in this temp directory with a simple
// Dockerfile.
realContextDirname := filepath.Join(tempDir, "context")
if err := os.Mkdir(realContextDirname, os.FileMode(0755)); err != nil {
c.Fatal(err)
}
if err = ioutil.WriteFile(
filepath.Join(realContextDirname, "Dockerfile"),
[]byte(`
FROM busybox
RUN echo hello world
`),
os.FileMode(0644),
); err != nil {
c.Fatal(err)
}
// Make a symlink to the real context directory.
contextSymlinkName := filepath.Join(tempDir, "context_link")
if err := os.Symlink(realContextDirname, contextSymlinkName); err != nil {
c.Fatal(err)
}
// Executing the build with the symlink as the specified context should
// *not* fail.
if out, exitStatus := dockerCmd(c, "build", contextSymlinkName); exitStatus != 0 {
c.Fatalf("build failed with exit status %d: %s", exitStatus, out)
}
}
+106 -10
View File
@@ -1,25 +1,29 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
"github.com/go-check/check" "github.com/go-check/check"
) )
var ( var (
repoName = fmt.Sprintf("%v/dockercli/busybox-by-dgst", privateRegistryURL) remoteRepoName = "dockercli/busybox-by-dgst"
repoName = fmt.Sprintf("%v/%s", privateRegistryURL, remoteRepoName)
pushDigestRegex = regexp.MustCompile("[\\S]+: digest: ([\\S]+) size: [0-9]+") pushDigestRegex = regexp.MustCompile("[\\S]+: digest: ([\\S]+) size: [0-9]+")
digestRegex = regexp.MustCompile("Digest: ([\\S]+)") digestRegex = regexp.MustCompile("Digest: ([\\S]+)")
) )
func setupImage(c *check.C) (string, error) { func setupImage(c *check.C) (digest.Digest, error) {
return setupImageWithTag(c, "latest") return setupImageWithTag(c, "latest")
} }
func setupImageWithTag(c *check.C, tag string) (string, error) { func setupImageWithTag(c *check.C, tag string) (digest.Digest, error) {
containerName := "busyboxbydigest" containerName := "busyboxbydigest"
dockerCmd(c, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox") dockerCmd(c, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox")
@@ -52,7 +56,7 @@ func setupImageWithTag(c *check.C, tag string) (string, error) {
} }
pushDigest := matches[1] pushDigest := matches[1]
return pushDigest, nil return digest.Digest(pushDigest), nil
} }
func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
@@ -72,7 +76,7 @@ func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
pullDigest := matches[1] pullDigest := matches[1]
// make sure the pushed and pull digests match // make sure the pushed and pull digests match
if pushDigest != pullDigest { if pushDigest.String() != pullDigest {
c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
} }
} }
@@ -95,7 +99,7 @@ func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) {
pullDigest := matches[1] pullDigest := matches[1]
// make sure the pushed and pull digests match // make sure the pushed and pull digests match
if pushDigest != pullDigest { if pushDigest.String() != pullDigest {
c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
} }
} }
@@ -291,7 +295,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
out, _ := dockerCmd(c, "images", "--digests") out, _ := dockerCmd(c, "images", "--digests")
// make sure repo shown, tag=<none>, digest = $digest1 // make sure repo shown, tag=<none>, digest = $digest1
re1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1 + `\s`) re1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`)
if !re1.MatchString(out) { if !re1.MatchString(out) {
c.Fatalf("expected %q: %s", re1.String(), out) c.Fatalf("expected %q: %s", re1.String(), out)
} }
@@ -319,7 +323,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
} }
// make sure repo shown, tag=<none>, digest = $digest2 // make sure repo shown, tag=<none>, digest = $digest2
re2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2 + `\s`) re2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`)
if !re2.MatchString(out) { if !re2.MatchString(out) {
c.Fatalf("expected %q: %s", re2.String(), out) c.Fatalf("expected %q: %s", re2.String(), out)
} }
@@ -332,7 +336,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
// make sure image 1 has repo, tag, <none> AND repo, <none>, digest // make sure image 1 has repo, tag, <none> AND repo, <none>, digest
reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*<none>\s`) reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*<none>\s`)
reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1 + `\s`) reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`)
if !reWithTag1.MatchString(out) { if !reWithTag1.MatchString(out) {
c.Fatalf("expected %q: %s", reWithTag1.String(), out) c.Fatalf("expected %q: %s", reWithTag1.String(), out)
} }
@@ -357,7 +361,7 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
// make sure image 2 has repo, tag, digest // make sure image 2 has repo, tag, digest
reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*<none>\s`) reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*<none>\s`)
reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2 + `\s`) reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`)
if !reWithTag2.MatchString(out) { if !reWithTag2.MatchString(out) {
c.Fatalf("expected %q: %s", reWithTag2.String(), out) c.Fatalf("expected %q: %s", reWithTag2.String(), out)
} }
@@ -401,3 +405,95 @@ func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C)
dockerCmd(c, "rmi", imageID) dockerCmd(c, "rmi", imageID)
} }
// TestPullFailsWithAlteredManifest tests that a `docker pull` fails when
// we have modified a manifest blob and its digest cannot be verified.
func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) {
manifestDigest, err := setupImage(c)
if err != nil {
c.Fatalf("error setting up image: %v", err)
}
// Load the target manifest blob.
manifestBlob := s.reg.readBlobContents(c, manifestDigest)
var imgManifest manifest.Manifest
if err := json.Unmarshal(manifestBlob, &imgManifest); err != nil {
c.Fatalf("unable to decode image manifest from blob: %s", err)
}
// Add a malicious layer digest to the list of layers in the manifest.
imgManifest.FSLayers = append(imgManifest.FSLayers, manifest.FSLayer{
BlobSum: digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
})
// Move the existing data file aside, so that we can replace it with a
// malicious blob of data. NOTE: we defer the returned undo func.
undo := s.reg.tempMoveBlobData(c, manifestDigest)
defer undo()
alteredManifestBlob, err := json.Marshal(imgManifest)
if err != nil {
c.Fatalf("unable to encode altered image manifest to JSON: %s", err)
}
s.reg.writeBlobContents(c, manifestDigest, alteredManifestBlob)
// Now try pulling that image by digest. We should get an error about
// digest verification for the manifest digest.
// Pull from the registry using the <name>@<digest> reference.
imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
out, exitStatus, _ := dockerCmdWithError(c, "pull", imageReference)
if exitStatus == 0 {
c.Fatalf("expected a zero exit status but got %d: %s", exitStatus, out)
}
expectedErrorMsg := fmt.Sprintf("image verification failed for digest %s", manifestDigest)
if !strings.Contains(out, expectedErrorMsg) {
c.Fatalf("expected error message %q in output: %s", expectedErrorMsg, out)
}
}
// TestPullFailsWithAlteredLayer tests that a `docker pull` fails when
// we have modified a layer blob and its digest cannot be verified.
func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) {
manifestDigest, err := setupImage(c)
if err != nil {
c.Fatalf("error setting up image: %v", err)
}
// Load the target manifest blob.
manifestBlob := s.reg.readBlobContents(c, manifestDigest)
var imgManifest manifest.Manifest
if err := json.Unmarshal(manifestBlob, &imgManifest); err != nil {
c.Fatalf("unable to decode image manifest from blob: %s", err)
}
// Next, get the digest of one of the layers from the manifest.
targetLayerDigest := imgManifest.FSLayers[0].BlobSum
// Move the existing data file aside, so that we can replace it with a
// malicious blob of data. NOTE: we defer the returned undo func.
undo := s.reg.tempMoveBlobData(c, targetLayerDigest)
defer undo()
// Now make a fake data blob in this directory.
s.reg.writeBlobContents(c, targetLayerDigest, []byte("This is not the data you are looking for."))
// Now try pulling that image by digest. We should get an error about
// digest verification for the target layer digest.
// Pull from the registry using the <name>@<digest> reference.
imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
out, exitStatus, _ := dockerCmdWithError(c, "pull", imageReference)
if exitStatus == 0 {
c.Fatalf("expected a zero exit status but got: %d", exitStatus)
}
expectedErrorMsg := fmt.Sprintf("filesystem layer verification failed for digest %s", targetLayerDigest)
if !strings.Contains(out, expectedErrorMsg) {
c.Fatalf("expected error message %q in output: %s", expectedErrorMsg, out)
}
}
@@ -130,6 +130,114 @@ func (s *DockerSuite) TestCpFromErrDstNotDir(c *check.C) {
} }
} }
// Check that copying from a container to a local symlink copies to the symlink
// target and does not overwrite the local symlink itself.
func (s *DockerSuite) TestCpFromSymlinkDestination(c *check.C) {
cID := makeTestContainer(c, testContainerOptions{addContent: true})
defer deleteContainer(cID)
tmpDir := getTestDir(c, "test-cp-from-err-dst-not-dir")
defer os.RemoveAll(tmpDir)
makeTestContentInDir(c, tmpDir)
// First, copy a file from the container to a symlink to a file. This
// should overwrite the symlink target contents with the source contents.
srcPath := containerCpPath(cID, "/file2")
dstPath := cpPath(tmpDir, "symlinkToFile1")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, dstPath, "file1"); err != nil {
c.Fatal(err)
}
// The file should have the contents of "file2" now.
if err := fileContentEquals(c, cpPath(tmpDir, "file1"), "file2\n"); err != nil {
c.Fatal(err)
}
// Next, copy a file from the container to a symlink to a directory. This
// should copy the file into the symlink target directory.
dstPath = cpPath(tmpDir, "symlinkToDir1")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, dstPath, "dir1"); err != nil {
c.Fatal(err)
}
// The file should have the contents of "file2" now.
if err := fileContentEquals(c, cpPath(tmpDir, "file2"), "file2\n"); err != nil {
c.Fatal(err)
}
// Next, copy a file from the container to a symlink to a file that does
// not exist (a broken symlink). This should create the target file with
// the contents of the source file.
dstPath = cpPath(tmpDir, "brokenSymlinkToFileX")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, dstPath, "fileX"); err != nil {
c.Fatal(err)
}
// The file should have the contents of "file2" now.
if err := fileContentEquals(c, cpPath(tmpDir, "fileX"), "file2\n"); err != nil {
c.Fatal(err)
}
// Next, copy a directory from the container to a symlink to a local
// directory. This should copy the directory into the symlink target
// directory and not modify the symlink.
srcPath = containerCpPath(cID, "/dir2")
dstPath = cpPath(tmpDir, "symlinkToDir1")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, dstPath, "dir1"); err != nil {
c.Fatal(err)
}
// The directory should now contain a copy of "dir2".
if err := fileContentEquals(c, cpPath(tmpDir, "dir1/dir2/file2-1"), "file2-1\n"); err != nil {
c.Fatal(err)
}
// Next, copy a directory from the container to a symlink to a local
// directory that does not exist (a broken symlink). This should create
// the target as a directory with the contents of the source directory. It
// should not modify the symlink.
dstPath = cpPath(tmpDir, "brokenSymlinkToDirX")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, dstPath, "dirX"); err != nil {
c.Fatal(err)
}
// The "dirX" directory should now be a copy of "dir2".
if err := fileContentEquals(c, cpPath(tmpDir, "dirX/file2-1"), "file2-1\n"); err != nil {
c.Fatal(err)
}
}
// Possibilities are reduced to the remaining 10 cases: // Possibilities are reduced to the remaining 10 cases:
// //
// case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action // case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action
+165 -9
View File
@@ -250,29 +250,185 @@ func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) {
c.Fatal(err) c.Fatal(err)
} }
tmpname := filepath.Join(tmpdir, cpTestName) tmpname := filepath.Join(tmpdir, "container_path")
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
path := path.Join("/", "container_path") path := path.Join("/", "container_path")
dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
file, _ := os.Open(tmpname) // We should have copied a symlink *NOT* the file itself!
defer file.Close() linkTarget, err := os.Readlink(tmpname)
test, err := ioutil.ReadAll(file)
if err != nil { if err != nil {
c.Fatal(err) c.Fatal(err)
} }
if string(test) == cpHostContents { if linkTarget != filepath.FromSlash(cpFullPath) {
c.Errorf("output matched host file -- absolute symlink can escape container rootfs") c.Errorf("symlink target was %q, but expected: %q", linkTarget, cpFullPath)
}
}
// Check that symlinks to a directory behave as expected when copying one from
// a container.
func (s *DockerSuite) TestCpFromSymlinkToDirectory(c *check.C) {
out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPathParent+" /dir_link")
if exitCode != 0 {
c.Fatal("failed to create a container", out)
} }
if string(test) != cpContainerContents { cleanedContainerID := strings.TrimSpace(out)
c.Errorf("output doesn't match the input for absolute symlink")
out, _ = dockerCmd(c, "wait", cleanedContainerID)
if strings.TrimSpace(out) != "0" {
c.Fatal("failed to set up container", out)
} }
testDir, err := ioutil.TempDir("", "test-cp-from-symlink-to-dir-")
if err != nil {
c.Fatal(err)
}
defer os.RemoveAll(testDir)
// This copy command should copy the symlink, not the target, into the
// temporary directory.
dockerCmd(c, "cp", cleanedContainerID+":"+"/dir_link", testDir)
expectedPath := filepath.Join(testDir, "dir_link")
linkTarget, err := os.Readlink(expectedPath)
if err != nil {
c.Fatalf("unable to read symlink at %q: %v", expectedPath, err)
}
if linkTarget != filepath.FromSlash(cpTestPathParent) {
c.Errorf("symlink target was %q, but expected: %q", linkTarget, cpTestPathParent)
}
os.Remove(expectedPath)
// This copy command should resolve the symlink (note the trailing
// seperator), copying the target into the temporary directory.
dockerCmd(c, "cp", cleanedContainerID+":"+"/dir_link/", testDir)
// It *should not* have copied the directory using the target's name, but
// used the given name instead.
unexpectedPath := filepath.Join(testDir, cpTestPathParent)
if stat, err := os.Lstat(unexpectedPath); err == nil {
c.Fatalf("target name was copied: %q - %q", stat.Mode(), stat.Name())
}
// It *should* have copied the directory using the asked name "dir_link".
stat, err := os.Lstat(expectedPath)
if err != nil {
c.Fatalf("unable to stat resource at %q: %v", expectedPath, err)
}
if !stat.IsDir() {
c.Errorf("should have copied a directory but got %q instead", stat.Mode())
}
}
// Check that symlinks to a directory behave as expected when copying one to a
// container.
func (s *DockerSuite) TestCpToSymlinkToDirectory(c *check.C) {
testRequires(c, SameHostDaemon) // Requires local volume mount bind.
testVol, err := ioutil.TempDir("", "test-cp-to-symlink-to-dir-")
if err != nil {
c.Fatal(err)
}
defer os.RemoveAll(testVol)
// Create a test container with a local volume. We will test by copying
// to the volume path in the container which we can then verify locally.
out, exitCode := dockerCmd(c, "create", "-v", testVol+":/testVol", "busybox")
if exitCode != 0 {
c.Fatal("failed to create a container", out)
}
cleanedContainerID := strings.TrimSpace(out)
// Create a temp directory to hold a test file nested in a direcotry.
testDir, err := ioutil.TempDir("", "test-cp-to-symlink-to-dir-")
if err != nil {
c.Fatal(err)
}
defer os.RemoveAll(testDir)
// This file will be at "/testDir/some/path/test" and will be copied into
// the test volume later.
hostTestFilename := filepath.Join(testDir, cpFullPath)
if err := os.MkdirAll(filepath.Dir(hostTestFilename), os.FileMode(0700)); err != nil {
c.Fatal(err)
}
if err := ioutil.WriteFile(hostTestFilename, []byte(cpHostContents), os.FileMode(0600)); err != nil {
c.Fatal(err)
}
// Now create another temp directory to hold a symlink to the
// "/testDir/some" directory.
linkDir, err := ioutil.TempDir("", "test-cp-to-symlink-to-dir-")
if err != nil {
c.Fatal(err)
}
defer os.RemoveAll(linkDir)
// Then symlink "/linkDir/dir_link" to "/testdir/some".
linkTarget := filepath.Join(testDir, cpTestPathParent)
localLink := filepath.Join(linkDir, "dir_link")
if err := os.Symlink(linkTarget, localLink); err != nil {
c.Fatal(err)
}
// Now copy that symlink into the test volume in the container.
dockerCmd(c, "cp", localLink, cleanedContainerID+":/testVol")
// This copy command should have copied the symlink *not* the target.
expectedPath := filepath.Join(testVol, "dir_link")
actualLinkTarget, err := os.Readlink(expectedPath)
if err != nil {
c.Fatalf("unable to read symlink at %q: %v", expectedPath, err)
}
if actualLinkTarget != linkTarget {
c.Errorf("symlink target was %q, but expected: %q", actualLinkTarget, linkTarget)
}
// Good, now remove that copied link for the next test.
os.Remove(expectedPath)
// This copy command should resolve the symlink (note the trailing
// seperator), copying the target into the test volume directory in the
// container.
dockerCmd(c, "cp", localLink+"/", cleanedContainerID+":/testVol")
// It *should not* have copied the directory using the target's name, but
// used the given name instead.
unexpectedPath := filepath.Join(testVol, cpTestPathParent)
if stat, err := os.Lstat(unexpectedPath); err == nil {
c.Fatalf("target name was copied: %q - %q", stat.Mode(), stat.Name())
}
// It *should* have copied the directory using the asked name "dir_link".
stat, err := os.Lstat(expectedPath)
if err != nil {
c.Fatalf("unable to stat resource at %q: %v", expectedPath, err)
}
if !stat.IsDir() {
c.Errorf("should have copied a directory but got %q instead", stat.Mode())
}
// And this directory should contain the file copied from the host at the
// expected location: "/testVol/dir_link/path/test"
expectedFilepath := filepath.Join(testVol, "dir_link/path/test")
fileContents, err := ioutil.ReadFile(expectedFilepath)
if err != nil {
c.Fatal(err)
}
if string(fileContents) != cpHostContents {
c.Fatalf("file contains %q but expected %q", string(fileContents), cpHostContents)
}
} }
// Test for #5619 // Test for #5619
@@ -146,6 +146,118 @@ func (s *DockerSuite) TestCpToErrDstNotDir(c *check.C) {
} }
} }
// Check that copying from a local path to a symlink in a container copies to
// the symlink target and does not overwrite the container symlink itself.
func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) {
testRequires(c, SameHostDaemon) // Requires local volume mount bind.
testVol := getTestDir(c, "test-cp-to-symlink-destination-")
defer os.RemoveAll(testVol)
makeTestContentInDir(c, testVol)
cID := makeTestContainer(c, testContainerOptions{
volumes: defaultVolumes(testVol), // Our bind mount is at /vol2
})
defer deleteContainer(cID)
// First, copy a local file to a symlink to a file in the container. This
// should overwrite the symlink target contents with the source contents.
srcPath := cpPath(testVol, "file2")
dstPath := containerCpPath(cID, "/vol2/symlinkToFile1")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, cpPath(testVol, "symlinkToFile1"), "file1"); err != nil {
c.Fatal(err)
}
// The file should have the contents of "file2" now.
if err := fileContentEquals(c, cpPath(testVol, "file1"), "file2\n"); err != nil {
c.Fatal(err)
}
// Next, copy a local file to a symlink to a directory in the container.
// This should copy the file into the symlink target directory.
dstPath = containerCpPath(cID, "/vol2/symlinkToDir1")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"); err != nil {
c.Fatal(err)
}
// The file should have the contents of "file2" now.
if err := fileContentEquals(c, cpPath(testVol, "file2"), "file2\n"); err != nil {
c.Fatal(err)
}
// Next, copy a file to a symlink to a file that does not exist (a broken
// symlink) in the container. This should create the target file with the
// contents of the source file.
dstPath = containerCpPath(cID, "/vol2/brokenSymlinkToFileX")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToFileX"), "fileX"); err != nil {
c.Fatal(err)
}
// The file should have the contents of "file2" now.
if err := fileContentEquals(c, cpPath(testVol, "fileX"), "file2\n"); err != nil {
c.Fatal(err)
}
// Next, copy a local directory to a symlink to a directory in the
// container. This should copy the directory into the symlink target
// directory and not modify the symlink.
srcPath = cpPath(testVol, "/dir2")
dstPath = containerCpPath(cID, "/vol2/symlinkToDir1")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"); err != nil {
c.Fatal(err)
}
// The directory should now contain a copy of "dir2".
if err := fileContentEquals(c, cpPath(testVol, "dir1/dir2/file2-1"), "file2-1\n"); err != nil {
c.Fatal(err)
}
// Next, copy a local directory to a symlink to a local directory that does
// not exist (a broken symlink) in the container. This should create the
// target as a directory with the contents of the source directory. It
// should not modify the symlink.
dstPath = containerCpPath(cID, "/vol2/brokenSymlinkToDirX")
if err := runDockerCp(c, srcPath, dstPath); err != nil {
c.Fatalf("unexpected error %T: %s", err, err)
}
// The symlink should not have been modified.
if err := symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToDirX"), "dirX"); err != nil {
c.Fatal(err)
}
// The "dirX" directory should now be a copy of "dir2".
if err := fileContentEquals(c, cpPath(testVol, "dirX/file2-1"), "file2-1\n"); err != nil {
c.Fatal(err)
}
}
// Possibilities are reduced to the remaining 10 cases: // Possibilities are reduced to the remaining 10 cases:
// //
// case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action // case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action
+20 -2
View File
@@ -74,8 +74,11 @@ var defaultFileData = []fileData{
{ftRegular, "dir4/file3-1", "file4-1"}, {ftRegular, "dir4/file3-1", "file4-1"},
{ftRegular, "dir4/file3-2", "file4-2"}, {ftRegular, "dir4/file3-2", "file4-2"},
{ftDir, "dir5", ""}, {ftDir, "dir5", ""},
{ftSymlink, "symlink1", "target1"}, {ftSymlink, "symlinkToFile1", "file1"},
{ftSymlink, "symlink2", "target2"}, {ftSymlink, "symlinkToDir1", "dir1"},
{ftSymlink, "brokenSymlinkToFileX", "fileX"},
{ftSymlink, "brokenSymlinkToDirX", "dirX"},
{ftSymlink, "symlinkToAbsDir", "/root"},
} }
func defaultMkContentCommand() string { func defaultMkContentCommand() string {
@@ -268,6 +271,21 @@ func fileContentEquals(c *check.C, filename, contents string) (err error) {
return return
} }
func symlinkTargetEquals(c *check.C, symlink, expectedTarget string) (err error) {
c.Logf("checking that the symlink %q points to %q\n", symlink, expectedTarget)
actualTarget, err := os.Readlink(symlink)
if err != nil {
return err
}
if actualTarget != expectedTarget {
return fmt.Errorf("symlink target points to %q not %q", actualTarget, expectedTarget)
}
return nil
}
func containerStartOutputEquals(c *check.C, cID, contents string) (err error) { func containerStartOutputEquals(c *check.C, cID, contents string) (err error) {
c.Logf("checking that container %q start output contains %q\n", cID, contents) c.Logf("checking that container %q start output contains %q\n", cID, contents)
+12
View File
@@ -780,6 +780,18 @@ func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4Explicit(c *check.C) {
deleteInterface(c, defaultNetworkBridge) deleteInterface(c, defaultNetworkBridge)
} }
func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4ExplicitOutsideContainerSubnet(c *check.C) {
defaultNetworkBridge := "docker0"
deleteInterface(c, defaultNetworkBridge)
// Program a custom default gateway outside of the container subnet, daemon should accept it and start
err := s.d.StartWithBusybox("--bip", "172.16.0.10/16", "--fixed-cidr", "172.16.1.0/24", "--default-gateway", "172.16.0.254")
c.Assert(err, check.IsNil)
deleteInterface(c, defaultNetworkBridge)
s.d.Restart()
}
func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) {
d := s.d d := s.d
+7
View File
@@ -536,3 +536,10 @@ func (s *DockerSuite) TestExecWithImageUser(c *check.C) {
c.Fatalf("exec with user by id expected dockerio user got %s", out) c.Fatalf("exec with user by id expected dockerio user got %s", out)
} }
} }
func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) {
dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top")
if _, status := dockerCmd(c, "exec", "parent", "true"); status != 0 {
c.Fatalf("exec into a read-only container failed with exit status %d", status)
}
}
@@ -5,6 +5,7 @@ import (
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/go-check/check" "github.com/go-check/check"
@@ -260,3 +261,28 @@ func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) {
c.Fatalf("Expected rw to be false") c.Fatalf("Expected rw to be false")
} }
} }
// #14947
func (s *DockerSuite) TestInspectTimesAsRFC3339Nano(c *check.C) {
out, _ := dockerCmd(c, "run", "-d", "busybox", "true")
id := strings.TrimSpace(out)
startedAt, err := inspectField(id, "State.StartedAt")
c.Assert(err, check.IsNil)
finishedAt, err := inspectField(id, "State.FinishedAt")
c.Assert(err, check.IsNil)
created, err := inspectField(id, "Created")
c.Assert(err, check.IsNil)
_, err = time.Parse(time.RFC3339Nano, startedAt)
c.Assert(err, check.IsNil)
_, err = time.Parse(time.RFC3339Nano, finishedAt)
c.Assert(err, check.IsNil)
_, err = time.Parse(time.RFC3339Nano, created)
c.Assert(err, check.IsNil)
created, err = inspectField("busybox", "Created")
c.Assert(err, check.IsNil)
_, err = time.Parse(time.RFC3339Nano, created)
c.Assert(err, check.IsNil)
}
+11
View File
@@ -1,6 +1,8 @@
package main package main
import ( import (
"fmt"
"net/http"
"strings" "strings"
"github.com/go-check/check" "github.com/go-check/check"
@@ -87,3 +89,12 @@ func (s *DockerSuite) TestKillWithInvalidSignal(c *check.C) {
c.Fatal("Container should be in running state after an invalid signal") c.Fatal("Container should be in running state after an invalid signal")
} }
} }
func (s *DockerSuite) TestKillofStoppedContainerAPIPre120(c *check.C) {
dockerCmd(c, "run", "--name", "docker-kill-test-api", "-d", "busybox", "top")
dockerCmd(c, "stop", "docker-kill-test-api")
status, _, err := sockRequest("POST", fmt.Sprintf("/v1.19/containers/%s/kill", "docker-kill-test-api"), nil)
c.Assert(err, check.IsNil)
c.Assert(status, check.Equals, http.StatusNoContent)
}
+1 -1
View File
@@ -275,7 +275,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithIncorrectPassphraseForNonRoot(c *c
// Push with wrong passphrases // Push with wrong passphrases
pushCmd = exec.Command(dockerBinary, "push", repoName) pushCmd = exec.Command(dockerBinary, "push", repoName)
s.trustedCmdWithPassphrases(pushCmd, "12345678", "87654321", "87654321") s.trustedCmdWithPassphrases(pushCmd, "12345678", "87654321")
out, _, err = runCommandWithOutput(pushCmd) out, _, err = runCommandWithOutput(pushCmd)
if err == nil { if err == nil {
c.Fatalf("Error missing from trusted push with short targets passphrase: \n%s", out) c.Fatalf("Error missing from trusted push with short targets passphrase: \n%s", out)
+76 -5
View File
@@ -2242,7 +2242,7 @@ func (s *DockerSuite) TestRunContainerWithWritableRootfs(c *check.C) {
func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) { func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) {
testRequires(c, NativeExecDriver) testRequires(c, NativeExecDriver)
for _, f := range []string{"/file", "/etc/hosts", "/etc/resolv.conf", "/etc/hostname", "/proc/uptime", "/sys/kernel", "/dev/.dont.touch.me"} { for _, f := range []string{"/file", "/etc/hosts", "/etc/resolv.conf", "/etc/hostname", "/sys/kernel", "/dev/.dont.touch.me"} {
testReadOnlyFile(f, c) testReadOnlyFile(f, c)
} }
} }
@@ -2397,7 +2397,10 @@ func (s *DockerSuite) TestRunWriteToProcAsound(c *check.C) {
func (s *DockerSuite) TestRunReadProcTimer(c *check.C) { func (s *DockerSuite) TestRunReadProcTimer(c *check.C) {
testRequires(c, NativeExecDriver) testRequires(c, NativeExecDriver)
out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/timer_stats") out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/timer_stats")
if err != nil || code != 0 { if code != 0 {
return
}
if err != nil {
c.Fatal(err) c.Fatal(err)
} }
if strings.Trim(out, "\n ") != "" { if strings.Trim(out, "\n ") != "" {
@@ -2414,7 +2417,10 @@ func (s *DockerSuite) TestRunReadProcLatency(c *check.C) {
return return
} }
out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/latency_stats") out, code, err := dockerCmdWithError(c, "run", "busybox", "cat", "/proc/latency_stats")
if err != nil || code != 0 { if code != 0 {
return
}
if err != nil {
c.Fatal(err) c.Fatal(err)
} }
if strings.Trim(out, "\n ") != "" { if strings.Trim(out, "\n ") != "" {
@@ -2422,6 +2428,28 @@ func (s *DockerSuite) TestRunReadProcLatency(c *check.C) {
} }
} }
func (s *DockerSuite) TestRunReadFilteredProc(c *check.C) {
testRequires(c, Apparmor)
testReadPaths := []string{
"/proc/latency_stats",
"/proc/timer_stats",
"/proc/kcore",
}
for i, filePath := range testReadPaths {
name := fmt.Sprintf("procsieve-%d", i)
shellCmd := fmt.Sprintf("exec 3<%s", filePath)
out, exitCode, err := dockerCmdWithError(c, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd)
if exitCode != 0 {
return
}
if err != nil {
c.Fatalf("Open FD for read should have failed with permission denied, got: %s, %v", out, err)
}
}
}
func (s *DockerSuite) TestMountIntoProc(c *check.C) { func (s *DockerSuite) TestMountIntoProc(c *check.C) {
testRequires(c, NativeExecDriver) testRequires(c, NativeExecDriver)
_, code, err := dockerCmdWithError(c, "run", "-v", "/proc//sys", "busybox", "true") _, code, err := dockerCmdWithError(c, "run", "-v", "/proc//sys", "busybox", "true")
@@ -2515,13 +2543,17 @@ func (s *DockerSuite) TestRunWriteFilteredProc(c *check.C) {
"/proc/sys/kernel/modprobe", "/proc/sys/kernel/modprobe",
"/proc/sys/kernel/core_pattern", "/proc/sys/kernel/core_pattern",
"/proc/sysrq-trigger", "/proc/sysrq-trigger",
"/proc/kcore",
} }
for i, filePath := range testWritePaths { for i, filePath := range testWritePaths {
name := fmt.Sprintf("writeprocsieve-%d", i) name := fmt.Sprintf("writeprocsieve-%d", i)
shellCmd := fmt.Sprintf("exec 3>%s", filePath) shellCmd := fmt.Sprintf("exec 3>%s", filePath)
runCmd := exec.Command(dockerBinary, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd) out, code, err := dockerCmdWithError(c, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd)
if out, exitCode, err := runCommandWithOutput(runCmd); err == nil || exitCode == 0 { if code != 0 {
return
}
if err != nil {
c.Fatalf("Open FD for write should have failed with permission denied, got: %s, %v", out, err) c.Fatalf("Open FD for write should have failed with permission denied, got: %s, %v", out, err)
} }
} }
@@ -2704,3 +2736,42 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
c.Fatalf("Missing expected output on trusted push:\n%s", out) c.Fatalf("Missing expected output on trusted push:\n%s", out)
} }
} }
func (s *DockerSuite) TestPtraceContainerProcsFromHost(c *check.C) {
testRequires(c, SameHostDaemon)
out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
id := strings.TrimSpace(out)
if err := waitRun(id); err != nil {
c.Fatal(err)
}
pid1, err := inspectField(id, "State.Pid")
c.Assert(err, check.IsNil)
_, err = os.Readlink(fmt.Sprintf("/proc/%s/ns/net", pid1))
if err != nil {
c.Fatal(err)
}
}
func (s *DockerSuite) TestAppArmorDeniesPtrace(c *check.C) {
testRequires(c, SameHostDaemon)
testRequires(c, Apparmor)
// Run through 'sh' so we are NOT pid 1. Pid 1 may be able to trace
// itself, but pid>1 should not be able to trace pid1.
_, exitCode, _ := dockerCmdWithError(c, "run", "busybox", "sh", "-c", "readlink /proc/1/ns/net")
if exitCode == 0 {
c.Fatal("ptrace was not successfully restricted by AppArmor")
}
}
func (s *DockerSuite) TestAppArmorTraceSelf(c *check.C) {
testRequires(c, SameHostDaemon)
testRequires(c, Apparmor)
_, exitCode, _ := dockerCmdWithError(c, "run", "busybox", "readlink", "/proc/1/ns/net")
if exitCode != 0 {
c.Fatal("ptrace of self failed.")
}
}
+48
View File
@@ -8,6 +8,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"github.com/docker/distribution/digest"
"github.com/go-check/check" "github.com/go-check/check"
) )
@@ -70,3 +71,50 @@ func (t *testRegistryV2) Close() {
t.cmd.Process.Kill() t.cmd.Process.Kill()
os.RemoveAll(t.dir) os.RemoveAll(t.dir)
} }
func (t *testRegistryV2) getBlobFilename(blobDigest digest.Digest) string {
// Split the digest into it's algorithm and hex components.
dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex()
// The path to the target blob data looks something like:
// baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data"
return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", t.dir, dgstAlg, dgstHex[:2], dgstHex)
}
func (t *testRegistryV2) readBlobContents(c *check.C, blobDigest digest.Digest) []byte {
// Load the target manifest blob.
manifestBlob, err := ioutil.ReadFile(t.getBlobFilename(blobDigest))
if err != nil {
c.Fatalf("unable to read blob: %s", err)
}
return manifestBlob
}
func (t *testRegistryV2) writeBlobContents(c *check.C, blobDigest digest.Digest, data []byte) {
if err := ioutil.WriteFile(t.getBlobFilename(blobDigest), data, os.FileMode(0644)); err != nil {
c.Fatalf("unable to write malicious data blob: %s", err)
}
}
func (t *testRegistryV2) tempMoveBlobData(c *check.C, blobDigest digest.Digest) (undo func()) {
tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
if err != nil {
c.Fatalf("unable to get temporary blob file: %s", err)
}
tempFile.Close()
blobFilename := t.getBlobFilename(blobDigest)
// Move the existing data file aside, so that we can replace it with a
// another blob of data.
if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
os.Remove(tempFile.Name())
c.Fatalf("unable to move data blob: %s", err)
}
return func() {
os.Rename(tempFile.Name(), blobFilename)
os.Remove(tempFile.Name())
}
}
+9 -9
View File
@@ -32,7 +32,8 @@ func newTestNotary(c *check.C) (*testNotary, error) {
"trust_service": { "trust_service": {
"type": "local", "type": "local",
"hostname": "", "hostname": "",
"port": "" "port": "",
"key_algorithm": "ed25519"
}, },
"logging": { "logging": {
"level": 5 "level": 5
@@ -116,25 +117,24 @@ func (t *testNotary) Close() {
func (s *DockerTrustSuite) trustedCmd(cmd *exec.Cmd) { func (s *DockerTrustSuite) trustedCmd(cmd *exec.Cmd) {
pwd := "12345678" pwd := "12345678"
trustCmdEnv(cmd, s.not.address(), pwd, pwd, pwd) trustCmdEnv(cmd, s.not.address(), pwd, pwd)
} }
func (s *DockerTrustSuite) trustedCmdWithServer(cmd *exec.Cmd, server string) { func (s *DockerTrustSuite) trustedCmdWithServer(cmd *exec.Cmd, server string) {
pwd := "12345678" pwd := "12345678"
trustCmdEnv(cmd, server, pwd, pwd, pwd) trustCmdEnv(cmd, server, pwd, pwd)
} }
func (s *DockerTrustSuite) trustedCmdWithPassphrases(cmd *exec.Cmd, rootPwd, snapshotPwd, targetPwd string) { func (s *DockerTrustSuite) trustedCmdWithPassphrases(cmd *exec.Cmd, offlinePwd, taggingPwd string) {
trustCmdEnv(cmd, s.not.address(), rootPwd, snapshotPwd, targetPwd) trustCmdEnv(cmd, s.not.address(), offlinePwd, taggingPwd)
} }
func trustCmdEnv(cmd *exec.Cmd, server, rootPwd, snapshotPwd, targetPwd string) { func trustCmdEnv(cmd *exec.Cmd, server, offlinePwd, taggingPwd string) {
env := []string{ env := []string{
"DOCKER_CONTENT_TRUST=1", "DOCKER_CONTENT_TRUST=1",
fmt.Sprintf("DOCKER_CONTENT_TRUST_SERVER=%s", server), fmt.Sprintf("DOCKER_CONTENT_TRUST_SERVER=%s", server),
fmt.Sprintf("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=%s", rootPwd), fmt.Sprintf("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE=%s", offlinePwd),
fmt.Sprintf("DOCKER_CONTENT_TRUST_SNAPSHOT_PASSPHRASE=%s", snapshotPwd), fmt.Sprintf("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE=%s", taggingPwd),
fmt.Sprintf("DOCKER_CONTENT_TRUST_TARGET_PASSPHRASE=%s", targetPwd),
} }
cmd.Env = append(os.Environ(), env...) cmd.Env = append(os.Environ(), env...)
} }
+41 -17
View File
@@ -37,11 +37,13 @@ type (
Compression Compression Compression Compression
NoLchown bool NoLchown bool
ChownOpts *TarChownOptions ChownOpts *TarChownOptions
Name string
IncludeSourceDir bool IncludeSourceDir bool
// When unpacking, specifies whether overwriting a directory with a // When unpacking, specifies whether overwriting a directory with a
// non-directory is allowed and vice versa. // non-directory is allowed and vice versa.
NoOverwriteDirNonDir bool NoOverwriteDirNonDir bool
// For each include when creating an archive, the included name will be
// replaced with the matching name from this map.
RebaseNames map[string]string
} }
// Archiver allows the reuse of most utility functions of this package // Archiver allows the reuse of most utility functions of this package
@@ -454,8 +456,9 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
seen := make(map[string]bool) seen := make(map[string]bool)
var renamedRelFilePath string // For when tar.Options.Name is set
for _, include := range options.IncludeFiles { for _, include := range options.IncludeFiles {
rebaseName := options.RebaseNames[include]
// We can't use filepath.Join(srcPath, include) because this will // We can't use filepath.Join(srcPath, include) because this will
// clean away a trailing "." or "/" which may be important. // clean away a trailing "." or "/" which may be important.
walkRoot := strings.Join([]string{srcPath, include}, string(filepath.Separator)) walkRoot := strings.Join([]string{srcPath, include}, string(filepath.Separator))
@@ -503,14 +506,17 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
} }
seen[relFilePath] = true seen[relFilePath] = true
// TODO Windows: Verify if this needs to be os.Pathseparator // Rename the base resource.
// Rename the base resource if rebaseName != "" {
if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) { var replacement string
renamedRelFilePath = relFilePath if rebaseName != string(filepath.Separator) {
} // Special case the root directory to replace with an
// Set this to make sure the items underneath also get renamed // empty string instead so that we don't end up with
if options.Name != "" { // double slashes in the paths.
relFilePath = strings.Replace(relFilePath, renamedRelFilePath, options.Name, 1) replacement = rebaseName
}
relFilePath = strings.Replace(relFilePath, include, replacement, 1)
} }
if err := ta.addTarFile(filePath, relFilePath); err != nil { if err := ta.addTarFile(filePath, relFilePath); err != nil {
@@ -633,8 +639,20 @@ loop:
// The archive may be compressed with one of the following algorithms: // The archive may be compressed with one of the following algorithms:
// identity (uncompressed), gzip, bzip2, xz. // identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist. // FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, dest string, options *TarOptions) error { func Untar(tarArchive io.Reader, dest string, options *TarOptions) error {
if archive == nil { return untarHandler(tarArchive, dest, options, true)
}
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
// and unpacks it into the directory at `dest`.
// The archive must be an uncompressed stream.
func UntarUncompressed(tarArchive io.Reader, dest string, options *TarOptions) error {
return untarHandler(tarArchive, dest, options, false)
}
// Handler for teasing out the automatic decompression
func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decompress bool) error {
if tarArchive == nil {
return fmt.Errorf("Empty archive") return fmt.Errorf("Empty archive")
} }
dest = filepath.Clean(dest) dest = filepath.Clean(dest)
@@ -644,12 +662,18 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
if options.ExcludePatterns == nil { if options.ExcludePatterns == nil {
options.ExcludePatterns = []string{} options.ExcludePatterns = []string{}
} }
decompressedArchive, err := DecompressStream(archive)
if err != nil { var r io.Reader = tarArchive
return err if decompress {
decompressedArchive, err := DecompressStream(tarArchive)
if err != nil {
return err
}
defer decompressedArchive.Close()
r = decompressedArchive
} }
defer decompressedArchive.Close()
return Unpack(decompressedArchive, dest, options) return Unpack(r, dest, options)
} }
func (archiver *Archiver) TarUntar(src, dst string) error { func (archiver *Archiver) TarUntar(src, dst string) error {
+1 -1
View File
@@ -695,7 +695,7 @@ func TestTarWithOptions(t *testing.T) {
{&TarOptions{ExcludePatterns: []string{"2"}}, 1}, {&TarOptions{ExcludePatterns: []string{"2"}}, 1},
{&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
{&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
{&TarOptions{Name: "test", IncludeFiles: []string{"1"}}, 4}, {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4},
} }
for _, testCase := range cases { for _, testCase := range cases {
changes, err := tarUntar(t, origin, testCase.opts) changes, err := tarUntar(t, origin, testCase.opts)
+148 -65
View File
@@ -6,7 +6,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -64,34 +63,33 @@ func SpecifiesCurrentDir(path string) bool {
return filepath.Base(path) == "." return filepath.Base(path) == "."
} }
// SplitPathDirEntry splits the given path between its // SplitPathDirEntry splits the given path between its directory name and its
// parent directory and its basename in that directory. // basename by first cleaning the path but preserves a trailing "." if the
func SplitPathDirEntry(localizedPath string) (dir, base string) { // original path specified the current directory.
normalizedPath := filepath.ToSlash(localizedPath) func SplitPathDirEntry(path string) (dir, base string) {
vol := filepath.VolumeName(normalizedPath) cleanedPath := filepath.Clean(path)
normalizedPath = normalizedPath[len(vol):]
if normalizedPath == "/" { if SpecifiesCurrentDir(path) {
// Specifies the root path. cleanedPath += string(filepath.Separator) + "."
return filepath.FromSlash(vol + normalizedPath), "."
} }
trimmedPath := vol + strings.TrimRight(normalizedPath, "/") return filepath.Dir(cleanedPath), filepath.Base(cleanedPath)
dir = filepath.FromSlash(path.Dir(trimmedPath))
base = filepath.FromSlash(path.Base(trimmedPath))
return dir, base
} }
// TarResource archives the resource at the given sourcePath into a Tar // TarResource archives the resource described by the given CopyInfo to a Tar
// archive. A non-nil error is returned if sourcePath does not exist or is // archive. A non-nil error is returned if sourcePath does not exist or is
// asserted to be a directory but exists as another type of file. // asserted to be a directory but exists as another type of file.
// //
// This function acts as a convenient wrapper around TarWithOptions, which // This function acts as a convenient wrapper around TarWithOptions, which
// requires a directory as the source path. TarResource accepts either a // requires a directory as the source path. TarResource accepts either a
// directory or a file path and correctly sets the Tar options. // directory or a file path and correctly sets the Tar options.
func TarResource(sourcePath string) (content Archive, err error) { func TarResource(sourceInfo CopyInfo) (content Archive, err error) {
return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName)
}
// TarResourceRebase is like TarResource but renames the first path element of
// items in the resulting tar archive to match the given rebaseName if not "".
func TarResourceRebase(sourcePath, rebaseName string) (content Archive, err error) {
if _, err = os.Lstat(sourcePath); err != nil { if _, err = os.Lstat(sourcePath); err != nil {
// Catches the case where the source does not exist or is not a // Catches the case where the source does not exist or is not a
// directory if asserted to be a directory, as this also causes an // directory if asserted to be a directory, as this also causes an
@@ -99,22 +97,6 @@ func TarResource(sourcePath string) (content Archive, err error) {
return return
} }
if len(sourcePath) > 1 && HasTrailingPathSeparator(sourcePath) {
// In the case where the source path is a symbolic link AND it ends
// with a path separator, we will want to evaluate the symbolic link.
trimmedPath := sourcePath[:len(sourcePath)-1]
stat, err := os.Lstat(trimmedPath)
if err != nil {
return nil, err
}
if stat.Mode()&os.ModeSymlink != 0 {
if sourcePath, err = filepath.EvalSymlinks(trimmedPath); err != nil {
return nil, err
}
}
}
// Separate the source path between it's directory and // Separate the source path between it's directory and
// the entry in that directory which we are archiving. // the entry in that directory which we are archiving.
sourceDir, sourceBase := SplitPathDirEntry(sourcePath) sourceDir, sourceBase := SplitPathDirEntry(sourcePath)
@@ -127,32 +109,137 @@ func TarResource(sourcePath string) (content Archive, err error) {
Compression: Uncompressed, Compression: Uncompressed,
IncludeFiles: filter, IncludeFiles: filter,
IncludeSourceDir: true, IncludeSourceDir: true,
RebaseNames: map[string]string{
sourceBase: rebaseName,
},
}) })
} }
// CopyInfo holds basic info about the source // CopyInfo holds basic info about the source
// or destination path of a copy operation. // or destination path of a copy operation.
type CopyInfo struct { type CopyInfo struct {
Path string Path string
Exists bool Exists bool
IsDir bool IsDir bool
RebaseName string
} }
// CopyInfoStatPath stats the given path to create a CopyInfo // CopyInfoSourcePath stats the given path to create a CopyInfo
// struct representing that resource. If mustExist is true, then // struct representing that resource for the source of an archive copy
// it is an error if there is no file or directory at the given path. // operation. The given path should be an absolute local path. A source path
func CopyInfoStatPath(path string, mustExist bool) (CopyInfo, error) { // has all symlinks evaluated that appear before the last path separator ("/"
pathInfo := CopyInfo{Path: path} // on Unix). As it is to be a copy source, the path must exist.
func CopyInfoSourcePath(path string) (CopyInfo, error) {
// Split the given path into its Directory and Base components. We will
// evaluate symlinks in the directory component then append the base.
dirPath, basePath := filepath.Split(path)
fileInfo, err := os.Lstat(path) resolvedDirPath, err := filepath.EvalSymlinks(dirPath)
if err != nil {
if err == nil { return CopyInfo{}, err
pathInfo.Exists, pathInfo.IsDir = true, fileInfo.IsDir()
} else if os.IsNotExist(err) && !mustExist {
err = nil
} }
return pathInfo, err // resolvedDirPath will have been cleaned (no trailing path separators) so
// we can manually join it with the base path element.
resolvedPath := resolvedDirPath + string(filepath.Separator) + basePath
var rebaseName string
if HasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) {
// In the case where the path had a trailing separator and a symlink
// evaluation has changed the last path component, we will need to
// rebase the name in the archive that is being copied to match the
// originally requested name.
rebaseName = filepath.Base(path)
}
stat, err := os.Lstat(resolvedPath)
if err != nil {
return CopyInfo{}, err
}
return CopyInfo{
Path: resolvedPath,
Exists: true,
IsDir: stat.IsDir(),
RebaseName: rebaseName,
}, nil
}
// CopyInfoDestinationPath stats the given path to create a CopyInfo
// struct representing that resource for the destination of an archive copy
// operation. The given path should be an absolute local path.
func CopyInfoDestinationPath(path string) (info CopyInfo, err error) {
maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot.
originalPath := path
stat, err := os.Lstat(path)
if err == nil && stat.Mode()&os.ModeSymlink == 0 {
// The path exists and is not a symlink.
return CopyInfo{
Path: path,
Exists: true,
IsDir: stat.IsDir(),
}, nil
}
// While the path is a symlink.
for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ {
if n > maxSymlinkIter {
// Don't follow symlinks more than this arbitrary number of times.
return CopyInfo{}, errors.New("too many symlinks in " + originalPath)
}
// The path is a symbolic link. We need to evaluate it so that the
// destination of the copy operation is the link target and not the
// link itself. This is notably different than CopyInfoSourcePath which
// only evaluates symlinks before the last appearing path separator.
// Also note that it is okay if the last path element is a broken
// symlink as the copy operation should create the target.
var linkTarget string
linkTarget, err = os.Readlink(path)
if err != nil {
return CopyInfo{}, err
}
if !filepath.IsAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := SplitPathDirEntry(path)
linkTarget = filepath.Join(dstParent, linkTarget)
}
path = linkTarget
stat, err = os.Lstat(path)
}
if err != nil {
// It's okay if the destination path doesn't exist. We can still
// continue the copy operation if the parent directory exists.
if !os.IsNotExist(err) {
return CopyInfo{}, err
}
// Ensure destination parent dir exists.
dstParent, _ := SplitPathDirEntry(path)
parentDirStat, err := os.Lstat(dstParent)
if err != nil {
return CopyInfo{}, err
}
if !parentDirStat.IsDir() {
return CopyInfo{}, ErrNotDirectory
}
return CopyInfo{Path: path}, nil
}
// The path exists after resolving symlinks.
return CopyInfo{
Path: path,
Exists: true,
IsDir: stat.IsDir(),
}, nil
} }
// PrepareArchiveCopy prepares the given srcContent archive, which should // PrepareArchiveCopy prepares the given srcContent archive, which should
@@ -210,6 +297,13 @@ func PrepareArchiveCopy(srcContent ArchiveReader, srcInfo, dstInfo CopyInfo) (ds
// rebaseArchiveEntries rewrites the given srcContent archive replacing // rebaseArchiveEntries rewrites the given srcContent archive replacing
// an occurance of oldBase with newBase at the beginning of entry names. // an occurance of oldBase with newBase at the beginning of entry names.
func rebaseArchiveEntries(srcContent ArchiveReader, oldBase, newBase string) Archive { func rebaseArchiveEntries(srcContent ArchiveReader, oldBase, newBase string) Archive {
if oldBase == "/" {
// If oldBase specifies the root directory, use an empty string as
// oldBase instead so that newBase doesn't replace the path separator
// that all paths will start with.
oldBase = ""
}
rebased, w := io.Pipe() rebased, w := io.Pipe()
go func() { go func() {
@@ -259,11 +353,11 @@ func CopyResource(srcPath, dstPath string) error {
srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath) srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath)
dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath) dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath)
if srcInfo, err = CopyInfoStatPath(srcPath, true); err != nil { if srcInfo, err = CopyInfoSourcePath(srcPath); err != nil {
return err return err
} }
content, err := TarResource(srcPath) content, err := TarResource(srcInfo)
if err != nil { if err != nil {
return err return err
} }
@@ -275,24 +369,13 @@ func CopyResource(srcPath, dstPath string) error {
// CopyTo handles extracting the given content whose // CopyTo handles extracting the given content whose
// entries should be sourced from srcInfo to dstPath. // entries should be sourced from srcInfo to dstPath.
func CopyTo(content ArchiveReader, srcInfo CopyInfo, dstPath string) error { func CopyTo(content ArchiveReader, srcInfo CopyInfo, dstPath string) error {
dstInfo, err := CopyInfoStatPath(dstPath, false) // The destination path need not exist, but CopyInfoDestinationPath will
// ensure that at least the parent directory exists.
dstInfo, err := CopyInfoDestinationPath(dstPath)
if err != nil { if err != nil {
return err return err
} }
if !dstInfo.Exists {
// Ensure destination parent dir exists.
dstParent, _ := SplitPathDirEntry(dstPath)
dstStat, err := os.Lstat(dstParent)
if err != nil {
return err
}
if !dstStat.IsDir() {
return ErrNotDirectory
}
}
dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo) dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo)
if err != nil { if err != nil {
return err return err
+6 -18
View File
@@ -138,13 +138,7 @@ func TestCopyErrSrcNotExists(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t) tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB) defer removeAllPaths(tmpDirA, tmpDirB)
content, err := TarResource(filepath.Join(tmpDirA, "file1")) if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1")); !os.IsNotExist(err) {
if err == nil {
content.Close()
t.Fatal("expected IsNotExist error, but got nil instead")
}
if !os.IsNotExist(err) {
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err) t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
} }
} }
@@ -158,13 +152,7 @@ func TestCopyErrSrcNotDir(t *testing.T) {
// Load A with some sample files and directories. // Load A with some sample files and directories.
createSampleDir(t, tmpDirA) createSampleDir(t, tmpDirA)
content, err := TarResource(joinTrailingSep(tmpDirA, "file1")) if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1")); !isNotDir(err) {
if err == nil {
content.Close()
t.Fatal("expected IsNotDir error, but got nil instead")
}
if !isNotDir(err) {
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err) t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
} }
} }
@@ -181,7 +169,7 @@ func TestCopyErrDstParentNotExists(t *testing.T) {
srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false}
// Try with a file source. // Try with a file source.
content, err := TarResource(srcInfo.Path) content, err := TarResource(srcInfo)
if err != nil { if err != nil {
t.Fatalf("unexpected error %T: %s", err, err) t.Fatalf("unexpected error %T: %s", err, err)
} }
@@ -199,7 +187,7 @@ func TestCopyErrDstParentNotExists(t *testing.T) {
// Try with a directory source. // Try with a directory source.
srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true}
content, err = TarResource(srcInfo.Path) content, err = TarResource(srcInfo)
if err != nil { if err != nil {
t.Fatalf("unexpected error %T: %s", err, err) t.Fatalf("unexpected error %T: %s", err, err)
} }
@@ -228,7 +216,7 @@ func TestCopyErrDstNotDir(t *testing.T) {
// Try with a file source. // Try with a file source.
srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false} srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false}
content, err := TarResource(srcInfo.Path) content, err := TarResource(srcInfo)
if err != nil { if err != nil {
t.Fatalf("unexpected error %T: %s", err, err) t.Fatalf("unexpected error %T: %s", err, err)
} }
@@ -245,7 +233,7 @@ func TestCopyErrDstNotDir(t *testing.T) {
// Try with a directory source. // Try with a directory source.
srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true} srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true}
content, err = TarResource(srcInfo.Path) content, err = TarResource(srcInfo)
if err != nil { if err != nil {
t.Fatalf("unexpected error %T: %s", err, err) t.Fatalf("unexpected error %T: %s", err, err)
} }
+22 -6
View File
@@ -173,10 +173,24 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
return size, nil return size, nil
} }
// ApplyLayer parses a diff in the standard layer format from `layer`, and // ApplyLayer parses a diff in the standard layer format from `layer`,
// applies it to the directory `dest`. Returns the size in bytes of the // and applies it to the directory `dest`. The stream `layer` can be
// contents of the layer. // compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyLayer(dest string, layer ArchiveReader) (int64, error) { func ApplyLayer(dest string, layer ArchiveReader) (int64, error) {
return applyLayerHandler(dest, layer, true)
}
// ApplyUncompressedLayer parses a diff in the standard layer format from
// `layer`, and applies it to the directory `dest`. The stream `layer`
// can only be uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyUncompressedLayer(dest string, layer ArchiveReader) (int64, error) {
return applyLayerHandler(dest, layer, false)
}
// do the bulk load of ApplyLayer, but allow for not calling DecompressStream
func applyLayerHandler(dest string, layer ArchiveReader, decompress bool) (int64, error) {
dest = filepath.Clean(dest) dest = filepath.Clean(dest)
// We need to be able to set any perms // We need to be able to set any perms
@@ -186,9 +200,11 @@ func ApplyLayer(dest string, layer ArchiveReader) (int64, error) {
} }
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
layer, err = DecompressStream(layer) if decompress {
if err != nil { layer, err = DecompressStream(layer)
return 0, err if err != nil {
return 0, err
}
} }
return UnpackLayer(dest, layer) return UnpackLayer(dest, layer)
} }
+22 -5
View File
@@ -3,6 +3,7 @@ package chrootarchive
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@@ -17,6 +18,18 @@ var chrootArchiver = &archive.Archiver{Untar: Untar}
// The archive may be compressed with one of the following algorithms: // The archive may be compressed with one of the following algorithms:
// identity (uncompressed), gzip, bzip2, xz. // identity (uncompressed), gzip, bzip2, xz.
func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error { func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
return untarHandler(tarArchive, dest, options, true)
}
// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive,
// and unpacks it into the directory at `dest`.
// The archive must be an uncompressed stream.
func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
return untarHandler(tarArchive, dest, options, false)
}
// Handler for teasing out the automatic decompression
func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool) error {
if tarArchive == nil { if tarArchive == nil {
return fmt.Errorf("Empty archive") return fmt.Errorf("Empty archive")
@@ -35,13 +48,17 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error
} }
} }
decompressedArchive, err := archive.DecompressStream(tarArchive) r := ioutil.NopCloser(tarArchive)
if err != nil { if decompress {
return err decompressedArchive, err := archive.DecompressStream(tarArchive)
if err != nil {
return err
}
defer decompressedArchive.Close()
r = decompressedArchive
} }
defer decompressedArchive.Close()
return invokeUnpack(decompressedArchive, dest, options) return invokeUnpack(r, dest, options)
} }
// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
+1 -1
View File
@@ -49,7 +49,7 @@ func untar() {
os.Exit(0) os.Exit(0)
} }
func invokeUnpack(decompressedArchive io.ReadCloser, dest string, options *archive.TarOptions) error { func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions) error {
// We can't pass a potentially large exclude list directly via cmd line // We can't pass a potentially large exclude list directly via cmd line
// because we easily overrun the kernel's max argument/environment size // because we easily overrun the kernel's max argument/environment size
+25 -9
View File
@@ -65,20 +65,36 @@ func applyLayer() {
os.Exit(0) os.Exit(0)
} }
// ApplyLayer parses a diff in the standard layer format from `layer`, and // ApplyLayer parses a diff in the standard layer format from `layer`,
// applies it to the directory `dest`. Returns the size in bytes of the // and applies it to the directory `dest`. The stream `layer` can only be
// contents of the layer. // uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyLayer(dest string, layer archive.ArchiveReader) (size int64, err error) { func ApplyLayer(dest string, layer archive.ArchiveReader) (size int64, err error) {
return applyLayerHandler(dest, layer, true)
}
// ApplyUncompressedLayer parses a diff in the standard layer format from
// `layer`, and applies it to the directory `dest`. The stream `layer`
// can only be uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyUncompressedLayer(dest string, layer archive.ArchiveReader) (int64, error) {
return applyLayerHandler(dest, layer, false)
}
func applyLayerHandler(dest string, layer archive.ArchiveReader, decompress bool) (size int64, err error) {
dest = filepath.Clean(dest) dest = filepath.Clean(dest)
decompressed, err := archive.DecompressStream(layer) if decompress {
if err != nil { decompressed, err := archive.DecompressStream(layer)
return 0, err if err != nil {
return 0, err
}
defer decompressed.Close()
layer = decompressed
} }
defer decompressed.Close()
cmd := reexec.Command("docker-applyLayer", dest) cmd := reexec.Command("docker-applyLayer", dest)
cmd.Stdin = decompressed cmd.Stdin = layer
outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer) outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
cmd.Stdout, cmd.Stderr = outBuf, errBuf cmd.Stdout, cmd.Stderr = outBuf, errBuf
+4 -4
View File
@@ -72,10 +72,10 @@ func certPool(caFile string) (*x509.CertPool, error) {
certPool := x509.NewCertPool() certPool := x509.NewCertPool()
pem, err := ioutil.ReadFile(caFile) pem, err := ioutil.ReadFile(caFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not read CA certificate %s: %v", caFile, err) return nil, fmt.Errorf("Could not read CA certificate %q: %v", caFile, err)
} }
if !certPool.AppendCertsFromPEM(pem) { if !certPool.AppendCertsFromPEM(pem) {
return nil, fmt.Errorf("failed to append certificates from PEM file: %s", caFile) return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
} }
s := certPool.Subjects() s := certPool.Subjects()
subjects := make([]string, len(s)) subjects := make([]string, len(s))
@@ -116,9 +116,9 @@ func Server(options Options) (*tls.Config, error) {
tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", options.CertFile, options.KeyFile, err) return nil, fmt.Errorf("Could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err)
} }
return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err) return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err)
} }
tlsConfig.Certificates = []tls.Certificate{tlsCert} tlsConfig.Certificates = []tls.Certificate{tlsCert}
if options.ClientAuth >= tls.VerifyClientCertIfGiven { if options.ClientAuth >= tls.VerifyClientCertIfGiven {
-2
View File
@@ -111,8 +111,6 @@ func (idx *TruncIndex) Get(s string) (string, error) {
// Iterates over all stored IDs, and passes each of them to the given handler // Iterates over all stored IDs, and passes each of them to the given handler
func (idx *TruncIndex) Iterate(handler func(id string)) { func (idx *TruncIndex) Iterate(handler func(id string)) {
idx.RLock()
defer idx.RUnlock()
idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error { idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error {
handler(string(prefix)) handler(string(prefix))
return nil return nil
+5 -5
View File
@@ -13,7 +13,6 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/api/v2" "github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/client/transport" "github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/pkg/tlsconfig"
) )
// for mocking in unit tests // for mocking in unit tests
@@ -45,10 +44,11 @@ func scanForAPIVersion(address string) (string, APIVersion) {
// NewEndpoint parses the given address to return a registry endpoint. // NewEndpoint parses the given address to return a registry endpoint.
func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) { func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
// *TODO: Allow per-registry configuration of endpoints. tlsConfig, err := newTLSConfig(index.Name, index.Secure)
tlsConfig := tlsconfig.ServerDefault if err != nil {
tlsConfig.InsecureSkipVerify = !index.Secure return nil, err
endpoint, err := newEndpoint(index.GetAuthConfigKey(), &tlsConfig, metaHeaders) }
endpoint, err := newEndpoint(index.GetAuthConfigKey(), tlsConfig, metaHeaders)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+25 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/api/errcode" "github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2" "github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/client"
"github.com/docker/distribution/registry/client/transport" "github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/parsers/kernel"
@@ -49,6 +50,23 @@ func init() {
dockerUserAgent = useragent.AppendVersions("", httpVersion...) dockerUserAgent = useragent.AppendVersions("", httpVersion...)
} }
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
// PreferredServerCipherSuites should have no effect
tlsConfig := tlsconfig.ServerDefault
tlsConfig.InsecureSkipVerify = !isSecure
if isSecure {
hostDir := filepath.Join(CertsDir, hostname)
logrus.Debugf("hostDir: %s", hostDir)
if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil {
return nil, err
}
}
return &tlsConfig, nil
}
func hasFile(files []os.FileInfo, name string) bool { func hasFile(files []os.FileInfo, name string) bool {
for _, f := range files { for _, f := range files {
if f.Name() == name { if f.Name() == name {
@@ -194,8 +212,14 @@ func ContinueOnError(err error) bool {
return ContinueOnError(v.Err) return ContinueOnError(v.Err)
case errcode.Error: case errcode.Error:
return shouldV2Fallback(v) return shouldV2Fallback(v)
case *client.UnexpectedHTTPResponseError:
return true
} }
return false // let's be nice and fallback if the error is a completely
// unexpected one.
// If new errors have to be handled in some way, please
// add them to the switch above.
return true
} }
// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
+1 -18
View File
@@ -5,10 +5,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"path/filepath"
"strings" "strings"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/auth"
"github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/tlsconfig" "github.com/docker/docker/pkg/tlsconfig"
@@ -99,22 +97,7 @@ func (e APIEndpoint) ToV1Endpoint(metaHeaders http.Header) (*Endpoint, error) {
// TLSConfig constructs a client TLS configuration based on server defaults // TLSConfig constructs a client TLS configuration based on server defaults
func (s *Service) TLSConfig(hostname string) (*tls.Config, error) { func (s *Service) TLSConfig(hostname string) (*tls.Config, error) {
// PreferredServerCipherSuites should have no effect return newTLSConfig(hostname, s.Config.isSecureIndex(hostname))
tlsConfig := tlsconfig.ServerDefault
isSecure := s.Config.isSecureIndex(hostname)
tlsConfig.InsecureSkipVerify = !isSecure
if isSecure {
hostDir := filepath.Join(CertsDir, hostname)
logrus.Debugf("hostDir: %s", hostDir)
if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil {
return nil, err
}
}
return &tlsConfig, nil
} }
func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) { func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) {
+3 -3
View File
@@ -231,9 +231,9 @@ type HostConfig struct {
CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetCpus string // CpusetCpus 0-2, 0,1
CpusetMems string // CpusetMems 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1
CpuQuota int64 CpuQuota int64
BlkioWeight int64 // Block IO weight (relative weight vs. other containers) BlkioWeight int64 // Block IO weight (relative weight vs. other containers)
OomKillDisable bool // Whether to disable OOM Killer or not OomKillDisable bool // Whether to disable OOM Killer or not
MemorySwappiness int64 // Tuning container memory swappiness behaviour MemorySwappiness *int64 // Tuning container memory swappiness behaviour
Privileged bool Privileged bool
PortBindings nat.PortMap PortBindings nat.PortMap
Links []string Links []string
+1 -1
View File
@@ -351,7 +351,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
CpuQuota: *flCpuQuota, CpuQuota: *flCpuQuota,
BlkioWeight: *flBlkioWeight, BlkioWeight: *flBlkioWeight,
OomKillDisable: *flOomKillDisable, OomKillDisable: *flOomKillDisable,
MemorySwappiness: swappiness, MemorySwappiness: flSwappiness,
Privileged: *flPrivileged, Privileged: *flPrivileged,
PortBindings: portBindings, PortBindings: portBindings,
Links: flLinks.GetAll(), Links: flLinks.GetAll(),
+1
View File
@@ -10,6 +10,7 @@ ENV DOCKER_BUILDTAGS include_rados
WORKDIR $DISTRIBUTION_DIR WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR COPY . $DISTRIBUTION_DIR
COPY cmd/registry/config-dev.yml $DISTRIBUTION_DIR/cmd/registry/config.yml
RUN make PREFIX=/go clean binaries RUN make PREFIX=/go clean binaries
VOLUME ["/var/lib/registry"] VOLUME ["/var/lib/registry"]
+13 -1
View File
@@ -27,6 +27,9 @@ var (
// ErrBlobInvalidLength returned when the blob has an expected length on // ErrBlobInvalidLength returned when the blob has an expected length on
// commit, meaning mismatched with the descriptor or an invalid value. // commit, meaning mismatched with the descriptor or an invalid value.
ErrBlobInvalidLength = errors.New("blob invalid length") ErrBlobInvalidLength = errors.New("blob invalid length")
// ErrUnsupported returned when an unsupported operation is attempted
ErrUnsupported = errors.New("unsupported operation")
) )
// ErrBlobInvalidDigest returned when digest check fails. // ErrBlobInvalidDigest returned when digest check fails.
@@ -70,6 +73,11 @@ type BlobStatter interface {
Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error) Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error)
} }
// BlobDeleter enables deleting blobs from storage.
type BlobDeleter interface {
Delete(ctx context.Context, dgst digest.Digest) error
}
// BlobDescriptorService manages metadata about a blob by digest. Most // BlobDescriptorService manages metadata about a blob by digest. Most
// implementations will not expose such an interface explicitly. Such mappings // implementations will not expose such an interface explicitly. Such mappings
// should be maintained by interacting with the BlobIngester. Hence, this is // should be maintained by interacting with the BlobIngester. Hence, this is
@@ -87,6 +95,9 @@ type BlobDescriptorService interface {
// the restriction that the algorithm of the descriptor must match the // the restriction that the algorithm of the descriptor must match the
// canonical algorithm (ie sha256) of the annotator. // canonical algorithm (ie sha256) of the annotator.
SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error
// Clear enables descriptors to be unlinked
Clear(ctx context.Context, dgst digest.Digest) error
} }
// ReadSeekCloser is the primary reader type for blob data, combining // ReadSeekCloser is the primary reader type for blob data, combining
@@ -183,8 +194,9 @@ type BlobService interface {
} }
// BlobStore represent the entire suite of blob related operations. Such an // BlobStore represent the entire suite of blob related operations. Such an
// implementation can access, read, write and serve blobs. // implementation can access, read, write, delete and serve blobs.
type BlobStore interface { type BlobStore interface {
BlobService BlobService
BlobServer BlobServer
BlobDeleter
} }
@@ -1,6 +1,8 @@
package context package context
import ( import (
"sync"
"github.com/docker/distribution/uuid" "github.com/docker/distribution/uuid"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@@ -14,11 +16,19 @@ type Context interface {
// provided as the main background context. // provided as the main background context.
type instanceContext struct { type instanceContext struct {
Context Context
id string // id of context, logged as "instance.id" id string // id of context, logged as "instance.id"
once sync.Once // once protect generation of the id
} }
func (ic *instanceContext) Value(key interface{}) interface{} { func (ic *instanceContext) Value(key interface{}) interface{} {
if key == "instance.id" { if key == "instance.id" {
ic.once.Do(func() {
// We want to lazy initialize the UUID such that we don't
// call a random generator from the package initialization
// code. For various reasons random could not be available
// https://github.com/docker/distribution/issues/782
ic.id = uuid.Generate().String()
})
return ic.id return ic.id
} }
@@ -27,7 +37,6 @@ func (ic *instanceContext) Value(key interface{}) interface{} {
var background = &instanceContext{ var background = &instanceContext{
Context: context.Background(), Context: context.Background(),
id: uuid.Generate().String(),
} }
// Background returns a non-nil, empty Context. The background context // Background returns a non-nil, empty Context. The background context
@@ -3,8 +3,6 @@ package context
import ( import (
"fmt" "fmt"
"github.com/docker/distribution/uuid"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
) )
@@ -101,8 +99,3 @@ func getLogrusLogger(ctx Context, keys ...interface{}) *logrus.Entry {
return logger.WithFields(fields) return logger.WithFields(fields)
} }
func init() {
// inject a logger into the uuid library.
uuid.Loggerf = GetLogger(Background()).Warnf
}
@@ -398,6 +398,8 @@ var routeDescriptors = []RouteDescriptor{
Description: "Fetch the tags under the repository identified by `name`.", Description: "Fetch the tags under the repository identified by `name`.",
Requests: []RequestDescriptor{ Requests: []RequestDescriptor{
{ {
Name: "Tags",
Description: "Return all tags for the repository",
Headers: []ParameterDescriptor{ Headers: []ParameterDescriptor{
hostHeader, hostHeader,
authHeader, authHeader,
@@ -455,6 +457,7 @@ var routeDescriptors = []RouteDescriptor{
}, },
}, },
{ {
Name: "Tags Paginated",
Description: "Return a portion of the tags for the specified repository.", Description: "Return a portion of the tags for the specified repository.",
PathParameters: []ParameterDescriptor{nameParameterDescriptor}, PathParameters: []ParameterDescriptor{nameParameterDescriptor},
QueryParameters: paginationParameters, QueryParameters: paginationParameters,
@@ -483,6 +486,30 @@ var routeDescriptors = []RouteDescriptor{
}, },
}, },
}, },
Failures: []ResponseDescriptor{
{
StatusCode: http.StatusNotFound,
Description: "The repository is not known to the registry.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
ErrorCodeNameUnknown,
},
},
{
StatusCode: http.StatusUnauthorized,
Description: "The client does not have access to the repository.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
ErrorCodeUnauthorized,
},
},
},
}, },
}, },
}, },
@@ -580,7 +607,7 @@ var routeDescriptors = []RouteDescriptor{
Successes: []ResponseDescriptor{ Successes: []ResponseDescriptor{
{ {
Description: "The manifest has been accepted by the registry and is stored under the specified `name` and `tag`.", Description: "The manifest has been accepted by the registry and is stored under the specified `name` and `tag`.",
StatusCode: http.StatusAccepted, StatusCode: http.StatusCreated,
Headers: []ParameterDescriptor{ Headers: []ParameterDescriptor{
{ {
Name: "Location", Name: "Location",
@@ -10,6 +10,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/docker/distribution/registry/client"
"github.com/docker/distribution/registry/client/transport" "github.com/docker/distribution/registry/client/transport"
) )
@@ -209,7 +210,7 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token string, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if !client.SuccessStatus(resp.StatusCode) {
return "", fmt.Errorf("token auth attempt for registry: %s request failed with status: %d %s", req.URL, resp.StatusCode, http.StatusText(resp.StatusCode)) return "", fmt.Errorf("token auth attempt for registry: %s request failed with status: %d %s", req.URL, resp.StatusCode, http.StatusText(resp.StatusCode))
} }
@@ -44,7 +44,7 @@ func (hbu *httpBlobUpload) ReadFrom(r io.Reader) (n int64, err error) {
return 0, err return 0, err
} }
if resp.StatusCode != http.StatusAccepted { if !SuccessStatus(resp.StatusCode) {
return 0, hbu.handleErrorResponse(resp) return 0, hbu.handleErrorResponse(resp)
} }
@@ -79,7 +79,7 @@ func (hbu *httpBlobUpload) Write(p []byte) (n int, err error) {
return 0, err return 0, err
} }
if resp.StatusCode != http.StatusAccepted { if !SuccessStatus(resp.StatusCode) {
return 0, hbu.handleErrorResponse(resp) return 0, hbu.handleErrorResponse(resp)
} }
@@ -142,7 +142,7 @@ func (hbu *httpBlobUpload) Commit(ctx context.Context, desc distribution.Descrip
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated { if !SuccessStatus(resp.StatusCode) {
return distribution.Descriptor{}, hbu.handleErrorResponse(resp) return distribution.Descriptor{}, hbu.handleErrorResponse(resp)
} }
@@ -160,12 +160,10 @@ func (hbu *httpBlobUpload) Cancel(ctx context.Context) error {
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if resp.StatusCode == http.StatusNotFound || SuccessStatus(resp.StatusCode) {
case http.StatusNoContent, http.StatusNotFound:
return nil return nil
default:
return hbu.handleErrorResponse(resp)
} }
return hbu.handleErrorResponse(resp)
} }
func (hbu *httpBlobUpload) Close() error { func (hbu *httpBlobUpload) Close() error {
@@ -61,3 +61,9 @@ func handleErrorResponse(resp *http.Response) error {
} }
return &UnexpectedHTTPStatusError{Status: resp.Status} return &UnexpectedHTTPStatusError{Status: resp.Status}
} }
// SuccessStatus returns true if the argument is a successful HTTP response
// code (in the range 200 - 399 inclusive).
func SuccessStatus(status int) bool {
return status >= 200 && status <= 399
}
@@ -70,8 +70,7 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if SuccessStatus(resp.StatusCode) {
case http.StatusOK:
var ctlg struct { var ctlg struct {
Repositories []string `json:"repositories"` Repositories []string `json:"repositories"`
} }
@@ -90,8 +89,7 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri
if link == "" { if link == "" {
returnErr = io.EOF returnErr = io.EOF
} }
} else {
default:
return 0, handleErrorResponse(resp) return 0, handleErrorResponse(resp)
} }
@@ -199,8 +197,7 @@ func (ms *manifests) Tags() ([]string, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if SuccessStatus(resp.StatusCode) {
case http.StatusOK:
b, err := ioutil.ReadAll(resp.Body) b, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -214,11 +211,10 @@ func (ms *manifests) Tags() ([]string, error) {
} }
return tagsResponse.Tags, nil return tagsResponse.Tags, nil
case http.StatusNotFound: } else if resp.StatusCode == http.StatusNotFound {
return nil, nil return nil, nil
default:
return nil, handleErrorResponse(resp)
} }
return nil, handleErrorResponse(resp)
} }
func (ms *manifests) Exists(dgst digest.Digest) (bool, error) { func (ms *manifests) Exists(dgst digest.Digest) (bool, error) {
@@ -238,14 +234,12 @@ func (ms *manifests) ExistsByTag(tag string) (bool, error) {
return false, err return false, err
} }
switch resp.StatusCode { if SuccessStatus(resp.StatusCode) {
case http.StatusOK:
return true, nil return true, nil
case http.StatusNotFound: } else if resp.StatusCode == http.StatusNotFound {
return false, nil return false, nil
default:
return false, handleErrorResponse(resp)
} }
return false, handleErrorResponse(resp)
} }
func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) { func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) {
@@ -254,13 +248,14 @@ func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) {
return ms.GetByTag(dgst.String()) return ms.GetByTag(dgst.String())
} }
// AddEtagToTag allows a client to supply an eTag to GetByTag which will // AddEtagToTag allows a client to supply an eTag to GetByTag which will be
// be used for a conditional HTTP request. If the eTag matches, a nil // used for a conditional HTTP request. If the eTag matches, a nil manifest
// manifest and nil error will be returned. // and nil error will be returned. etag is automatically quoted when added to
func AddEtagToTag(tagName, dgst string) distribution.ManifestServiceOption { // this map.
func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption {
return func(ms distribution.ManifestService) error { return func(ms distribution.ManifestService) error {
if ms, ok := ms.(*manifests); ok { if ms, ok := ms.(*manifests); ok {
ms.etags[tagName] = dgst ms.etags[tag] = fmt.Sprintf(`"%s"`, etag)
return nil return nil
} }
return fmt.Errorf("etag options is a client-only option") return fmt.Errorf("etag options is a client-only option")
@@ -293,8 +288,9 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if resp.StatusCode == http.StatusNotModified {
case http.StatusOK: return nil, nil
} else if SuccessStatus(resp.StatusCode) {
var sm manifest.SignedManifest var sm manifest.SignedManifest
decoder := json.NewDecoder(resp.Body) decoder := json.NewDecoder(resp.Body)
@@ -302,11 +298,8 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic
return nil, err return nil, err
} }
return &sm, nil return &sm, nil
case http.StatusNotModified:
return nil, nil
default:
return nil, handleErrorResponse(resp)
} }
return nil, handleErrorResponse(resp)
} }
func (ms *manifests) Put(m *manifest.SignedManifest) error { func (ms *manifests) Put(m *manifest.SignedManifest) error {
@@ -328,13 +321,11 @@ func (ms *manifests) Put(m *manifest.SignedManifest) error {
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if SuccessStatus(resp.StatusCode) {
case http.StatusAccepted:
// TODO(dmcgowan): make use of digest header // TODO(dmcgowan): make use of digest header
return nil return nil
default:
return handleErrorResponse(resp)
} }
return handleErrorResponse(resp)
} }
func (ms *manifests) Delete(dgst digest.Digest) error { func (ms *manifests) Delete(dgst digest.Digest) error {
@@ -353,12 +344,10 @@ func (ms *manifests) Delete(dgst digest.Digest) error {
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if SuccessStatus(resp.StatusCode) {
case http.StatusOK:
return nil return nil
default:
return handleErrorResponse(resp)
} }
return handleErrorResponse(resp)
} }
type blobs struct { type blobs struct {
@@ -366,7 +355,8 @@ type blobs struct {
ub *v2.URLBuilder ub *v2.URLBuilder
client *http.Client client *http.Client
statter distribution.BlobStatter statter distribution.BlobDescriptorService
distribution.BlobDeleter
} }
func sanitizeLocation(location, source string) (string, error) { func sanitizeLocation(location, source string) (string, error) {
@@ -459,8 +449,7 @@ func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if SuccessStatus(resp.StatusCode) {
case http.StatusAccepted:
// TODO(dmcgowan): Check for invalid UUID // TODO(dmcgowan): Check for invalid UUID
uuid := resp.Header.Get("Docker-Upload-UUID") uuid := resp.Header.Get("Docker-Upload-UUID")
location, err := sanitizeLocation(resp.Header.Get("Location"), u) location, err := sanitizeLocation(resp.Header.Get("Location"), u)
@@ -475,15 +464,18 @@ func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) {
startedAt: time.Now(), startedAt: time.Now(),
location: location, location: location,
}, nil }, nil
default:
return nil, handleErrorResponse(resp)
} }
return nil, handleErrorResponse(resp)
} }
func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
panic("not implemented") panic("not implemented")
} }
func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error {
return bs.statter.Clear(ctx, dgst)
}
type blobStatter struct { type blobStatter struct {
name string name string
ub *v2.URLBuilder ub *v2.URLBuilder
@@ -502,8 +494,7 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
} }
defer resp.Body.Close() defer resp.Body.Close()
switch resp.StatusCode { if SuccessStatus(resp.StatusCode) {
case http.StatusOK:
lengthHeader := resp.Header.Get("Content-Length") lengthHeader := resp.Header.Get("Content-Length")
length, err := strconv.ParseInt(lengthHeader, 10, 64) length, err := strconv.ParseInt(lengthHeader, 10, 64)
if err != nil { if err != nil {
@@ -515,11 +506,10 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
Size: length, Size: length,
Digest: dgst, Digest: dgst,
}, nil }, nil
case http.StatusNotFound: } else if resp.StatusCode == http.StatusNotFound {
return distribution.Descriptor{}, distribution.ErrBlobUnknown return distribution.Descriptor{}, distribution.ErrBlobUnknown
default:
return distribution.Descriptor{}, handleErrorResponse(resp)
} }
return distribution.Descriptor{}, handleErrorResponse(resp)
} }
func buildCatalogValues(maxEntries int, last string) url.Values { func buildCatalogValues(maxEntries int, last string) url.Values {
@@ -535,3 +525,30 @@ func buildCatalogValues(maxEntries int, last string) url.Values {
return values return values
} }
func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst)
if err != nil {
return err
}
req, err := http.NewRequest("DELETE", blobURL, nil)
if err != nil {
return err
}
resp, err := bs.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if SuccessStatus(resp.StatusCode) {
return nil
}
return handleErrorResponse(resp)
}
func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
return nil
}
@@ -154,10 +154,11 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
return nil, err return nil, err
} }
switch { // Normally would use client.SuccessStatus, but that would be a cyclic
case resp.StatusCode == 200: // import
if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
hrs.rc = resp.Body hrs.rc = resp.Body
default: } else {
defer resp.Body.Close() defer resp.Body.Close()
return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status) return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status)
} }
@@ -26,13 +26,13 @@ type MetricsTracker interface {
type cachedBlobStatter struct { type cachedBlobStatter struct {
cache distribution.BlobDescriptorService cache distribution.BlobDescriptorService
backend distribution.BlobStatter backend distribution.BlobDescriptorService
tracker MetricsTracker tracker MetricsTracker
} }
// NewCachedBlobStatter creates a new statter which prefers a cache and // NewCachedBlobStatter creates a new statter which prefers a cache and
// falls back to a backend. // falls back to a backend.
func NewCachedBlobStatter(cache distribution.BlobDescriptorService, backend distribution.BlobStatter) distribution.BlobStatter { func NewCachedBlobStatter(cache distribution.BlobDescriptorService, backend distribution.BlobDescriptorService) distribution.BlobDescriptorService {
return &cachedBlobStatter{ return &cachedBlobStatter{
cache: cache, cache: cache,
backend: backend, backend: backend,
@@ -41,7 +41,7 @@ func NewCachedBlobStatter(cache distribution.BlobDescriptorService, backend dist
// NewCachedBlobStatterWithMetrics creates a new statter which prefers a cache and // NewCachedBlobStatterWithMetrics creates a new statter which prefers a cache and
// falls back to a backend. Hits and misses will send to the tracker. // falls back to a backend. Hits and misses will send to the tracker.
func NewCachedBlobStatterWithMetrics(cache distribution.BlobDescriptorService, backend distribution.BlobStatter, tracker MetricsTracker) distribution.BlobStatter { func NewCachedBlobStatterWithMetrics(cache distribution.BlobDescriptorService, backend distribution.BlobDescriptorService, tracker MetricsTracker) distribution.BlobStatter {
return &cachedBlobStatter{ return &cachedBlobStatter{
cache: cache, cache: cache,
backend: backend, backend: backend,
@@ -77,4 +77,25 @@ fallback:
} }
return desc, err return desc, err
}
func (cbds *cachedBlobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
err := cbds.cache.Clear(ctx, dgst)
if err != nil {
return err
}
err = cbds.backend.Clear(ctx, dgst)
if err != nil {
return err
}
return nil
}
func (cbds *cachedBlobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
if err := cbds.cache.SetDescriptor(ctx, dgst, desc); err != nil {
context.GetLogger(ctx).Errorf("error adding descriptor %v to cache: %v", desc.Digest, err)
}
return nil
} }
@@ -44,6 +44,10 @@ func (imbdcp *inMemoryBlobDescriptorCacheProvider) Stat(ctx context.Context, dgs
return imbdcp.global.Stat(ctx, dgst) return imbdcp.global.Stat(ctx, dgst)
} }
func (imbdcp *inMemoryBlobDescriptorCacheProvider) Clear(ctx context.Context, dgst digest.Digest) error {
return imbdcp.global.Clear(ctx, dgst)
}
func (imbdcp *inMemoryBlobDescriptorCacheProvider) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { func (imbdcp *inMemoryBlobDescriptorCacheProvider) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
_, err := imbdcp.Stat(ctx, dgst) _, err := imbdcp.Stat(ctx, dgst)
if err == distribution.ErrBlobUnknown { if err == distribution.ErrBlobUnknown {
@@ -80,6 +84,14 @@ func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Stat(ctx context.Co
return rsimbdcp.repository.Stat(ctx, dgst) return rsimbdcp.repository.Stat(ctx, dgst)
} }
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error {
if rsimbdcp.repository == nil {
return distribution.ErrBlobUnknown
}
return rsimbdcp.repository.Clear(ctx, dgst)
}
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
if rsimbdcp.repository == nil { if rsimbdcp.repository == nil {
// allocate map since we are setting it now. // allocate map since we are setting it now.
@@ -133,6 +145,14 @@ func (mbdc *mapBlobDescriptorCache) Stat(ctx context.Context, dgst digest.Digest
return desc, nil return desc, nil
} }
func (mbdc *mapBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error {
mbdc.mu.Lock()
defer mbdc.mu.Unlock()
delete(mbdc.descriptors, dgst)
return nil
}
func (mbdc *mapBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { func (mbdc *mapBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
if err := dgst.Validate(); err != nil { if err := dgst.Validate(); err != nil {
return err return err
@@ -139,3 +139,40 @@ func checkBlobDescriptorCacheSetAndRead(t *testing.T, ctx context.Context, provi
t.Fatalf("unexpected descriptor: %#v != %#v", desc, expected) t.Fatalf("unexpected descriptor: %#v != %#v", desc, expected)
} }
} }
func checkBlobDescriptorClear(t *testing.T, ctx context.Context, provider BlobDescriptorCacheProvider) {
localDigest := digest.Digest("sha384:abc")
expected := distribution.Descriptor{
Digest: "sha256:abc",
Size: 10,
MediaType: "application/octet-stream"}
cache, err := provider.RepositoryScoped("foo/bar")
if err != nil {
t.Fatalf("unexpected error getting scoped cache: %v", err)
}
if err := cache.SetDescriptor(ctx, localDigest, expected); err != nil {
t.Fatalf("error setting descriptor: %v", err)
}
desc, err := cache.Stat(ctx, localDigest)
if err != nil {
t.Fatalf("unexpected error statting fake2:abc: %v", err)
}
if expected != desc {
t.Fatalf("unexpected descriptor: %#v != %#v", expected, desc)
}
err = cache.Clear(ctx, localDigest)
if err != nil {
t.Fatalf("unexpected error deleting descriptor")
}
nonExistantDigest := digest.Digest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
err = cache.Clear(ctx, nonExistantDigest)
if err == nil {
t.Fatalf("expected error deleting unknown descriptor")
}
}
+4 -3
View File
@@ -8,7 +8,6 @@ import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"syscall" "syscall"
"time" "time"
@@ -30,7 +29,7 @@ var (
// Loggerf can be used to override the default logging destination. Such // Loggerf can be used to override the default logging destination. Such
// log messages in this library should be logged at warning or higher. // log messages in this library should be logged at warning or higher.
Loggerf = log.Printf Loggerf = func(format string, args ...interface{}) {}
) )
// UUID represents a UUID value. UUIDs can be compared and set to other values // UUID represents a UUID value. UUIDs can be compared and set to other values
@@ -49,6 +48,7 @@ func Generate() (u UUID) {
var ( var (
totalBackoff time.Duration totalBackoff time.Duration
count int
retries int retries int
) )
@@ -60,9 +60,10 @@ func Generate() (u UUID) {
time.Sleep(b) time.Sleep(b)
totalBackoff += b totalBackoff += b
_, err := io.ReadFull(rand.Reader, u[:]) n, err := io.ReadFull(rand.Reader, u[count:])
if err != nil { if err != nil {
if retryOnError(err) && retries < maxretries { if retryOnError(err) && retries < maxretries {
count += n
retries++ retries++
Loggerf("error generating version 4 uuid, retrying: %v", err) Loggerf("error generating version 4 uuid, retrying: %v", err)
continue continue
@@ -596,21 +596,18 @@ func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) err
// networks. This step is needed now because driver might have now set the bridge // networks. This step is needed now because driver might have now set the bridge
// name on this config struct. And because we need to check for possible address // name on this config struct. And because we need to check for possible address
// conflicts, so we need to check against operationa lnetworks. // conflicts, so we need to check against operationa lnetworks.
if err := config.conflictsWithNetworks(id, networkList); err != nil { if err = config.conflictsWithNetworks(id, networkList); err != nil {
return err return err
} }
setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error { setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error {
defer func() { if err := network.isolateNetwork(networkList, true); err != nil {
if err != nil { if err := network.isolateNetwork(networkList, false); err != nil {
if err := network.isolateNetwork(networkList, false); err != nil { logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err)
logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err)
}
} }
}() return err
}
err := network.isolateNetwork(networkList, true) return nil
return err
} }
// Prepare the bridge setup configuration // Prepare the bridge setup configuration
@@ -766,17 +763,26 @@ func (d *driver) DeleteNetwork(nid types.UUID) error {
} }
func addToBridge(ifaceName, bridgeName string) error { func addToBridge(ifaceName, bridgeName string) error {
iface, err := net.InterfaceByName(ifaceName) link, err := netlink.LinkByName(ifaceName)
if err != nil { if err != nil {
return fmt.Errorf("could not find interface %s: %v", ifaceName, err) return fmt.Errorf("could not find interface %s: %v", ifaceName, err)
} }
if err = netlink.LinkSetMaster(link,
&netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: bridgeName}}); err != nil {
logrus.Debugf("Failed to add %s to bridge via netlink.Trying ioctl: %v", ifaceName, err)
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return fmt.Errorf("could not find network interface %s: %v", ifaceName, err)
}
master, err := net.InterfaceByName(bridgeName) master, err := net.InterfaceByName(bridgeName)
if err != nil { if err != nil {
return fmt.Errorf("could not find bridge %s: %v", bridgeName, err) return fmt.Errorf("could not find bridge %s: %v", bridgeName, err)
}
return ioctlAddToBridge(iface, master)
} }
return nil
return ioctlAddToBridge(iface, master)
} }
func setHairpinMode(link netlink.Link, enable bool) error { func setHairpinMode(link netlink.Link, enable bool) error {
@@ -947,15 +953,14 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointIn
} }
// v4 address for the sandbox side pipe interface // v4 address for the sandbox side pipe interface
sub := types.GetIPNetCanonical(n.bridge.bridgeIPv4) ip4, err := ipAllocator.RequestIP(n.bridge.bridgeIPv4, nil)
ip4, err := ipAllocator.RequestIP(sub, nil)
if err != nil { if err != nil {
return err return err
} }
ipv4Addr := &net.IPNet{IP: ip4, Mask: n.bridge.bridgeIPv4.Mask} ipv4Addr := &net.IPNet{IP: ip4, Mask: n.bridge.bridgeIPv4.Mask}
// Down the interface before configuring mac address. // Down the interface before configuring mac address.
if err := netlink.LinkSetDown(sbox); err != nil { if err = netlink.LinkSetDown(sbox); err != nil {
return fmt.Errorf("could not set link down for container interface %s: %v", containerIfName, err) return fmt.Errorf("could not set link down for container interface %s: %v", containerIfName, err)
} }
@@ -968,7 +973,7 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointIn
endpoint.macAddress = mac endpoint.macAddress = mac
// Up the host interface after finishing all netlink configuration // Up the host interface after finishing all netlink configuration
if err := netlink.LinkSetUp(host); err != nil { if err = netlink.LinkSetUp(host); err != nil {
return fmt.Errorf("could not set link up for host interface %s: %v", hostIfName, err) return fmt.Errorf("could not set link up for host interface %s: %v", hostIfName, err)
} }
@@ -1074,8 +1079,7 @@ func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
n.releasePorts(ep) n.releasePorts(ep)
// Release the v4 address allocated to this endpoint's sandbox interface // Release the v4 address allocated to this endpoint's sandbox interface
sub := types.GetIPNetCanonical(n.bridge.bridgeIPv4) err = ipAllocator.ReleaseIP(n.bridge.bridgeIPv4, ep.addr.IP)
err = ipAllocator.ReleaseIP(sub, ep.addr.IP)
if err != nil { if err != nil {
return err return err
} }
@@ -1,7 +1,11 @@
package bridge package bridge
import ( import (
"fmt"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
) )
@@ -25,11 +29,25 @@ func setupDevice(config *networkConfiguration, i *bridgeInterface) error {
// Only set the bridge's MAC address if the kernel version is > 3.3, as it // Only set the bridge's MAC address if the kernel version is > 3.3, as it
// was not supported before that. // was not supported before that.
kv, err := kernel.GetKernelVersion() kv, err := kernel.GetKernelVersion()
if err == nil && (kv.Kernel >= 3 && kv.Major >= 3) { if err != nil {
setMac = true logrus.Errorf("Failed to check kernel versions: %v. Will not assign a MAC address to the bridge interface", err)
} else {
setMac = kv.Kernel > 3 || (kv.Kernel == 3 && kv.Major >= 3)
} }
return ioctlCreateBridge(config.BridgeName, setMac) if err = netlink.LinkAdd(i.Link); err != nil {
logrus.Debugf("Failed to create bridge %s via netlink. Trying ioctl", config.BridgeName)
return ioctlCreateBridge(config.BridgeName, setMac)
}
if setMac {
hwAddr := netutils.GenerateRandomMAC()
if err = netlink.LinkSetHardwareAddr(i.Link, hwAddr); err != nil {
return fmt.Errorf("failed to set bridge mac-address %s : %s", hwAddr, err.Error())
}
logrus.Debugf("Setting bridge mac address to %s", hwAddr)
}
return err
} }
// SetupDeviceUp ups the given bridge interface. // SetupDeviceUp ups the given bridge interface.
@@ -8,7 +8,6 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/netutils" "github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
) )
@@ -32,9 +31,9 @@ func init() {
bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{10, byte(i), 42, 1}, Mask: mask}) bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{10, byte(i), 42, 1}, Mask: mask})
} }
// 192.168.[42-44].1/24 // 192.168.[42-44].1/24
mask[2] = 255 mask24 := []byte{255, 255, 255, 0}
for i := 42; i < 45; i++ { for i := 42; i < 45; i++ {
bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{192, 168, byte(i), 1}, Mask: mask}) bridgeNetworks = append(bridgeNetworks, &net.IPNet{IP: []byte{192, 168, byte(i), 1}, Mask: mask24})
} }
} }
@@ -76,8 +75,12 @@ func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
} }
func allocateBridgeIP(config *networkConfiguration, i *bridgeInterface) error { func allocateBridgeIP(config *networkConfiguration, i *bridgeInterface) error {
sub := types.GetIPNetCanonical(i.bridgeIPv4) // Because of the way ipallocator manages the container address space,
ipAllocator.RequestIP(sub, i.bridgeIPv4.IP) // reserve bridge address only if it belongs to the container network
// (if defined), no need otherwise
if config.FixedCIDR == nil || config.FixedCIDR.Contains(i.bridgeIPv4.IP) {
ipAllocator.RequestIP(i.bridgeIPv4, i.bridgeIPv4.IP)
}
return nil return nil
} }
@@ -112,10 +115,13 @@ func setupGatewayIPv4(config *networkConfiguration, i *bridgeInterface) error {
return &ErrInvalidGateway{} return &ErrInvalidGateway{}
} }
// Pass the real network subnet to ip allocator (no host bits set) // Because of the way ipallocator manages the container address space,
sub := types.GetIPNetCanonical(i.bridgeIPv4) // reserve default gw address only if it belongs to the container network
if _, err := ipAllocator.RequestIP(sub, config.DefaultGatewayIPv4); err != nil { // (if defined), no need otherwise
return err if config.FixedCIDR == nil || config.FixedCIDR.Contains(config.DefaultGatewayIPv4) {
if _, err := ipAllocator.RequestIP(i.bridgeIPv4, config.DefaultGatewayIPv4); err != nil {
return err
}
} }
// Store requested default gateway // Store requested default gateway
@@ -66,7 +66,8 @@ func (a *IPAllocator) RegisterSubnet(network *net.IPNet, subnet *net.IPNet) erro
a.mutex.Lock() a.mutex.Lock()
defer a.mutex.Unlock() defer a.mutex.Unlock()
key := network.String() nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask}
key := nw.String()
if _, ok := a.allocatedIPs[key]; ok { if _, ok := a.allocatedIPs[key]; ok {
return ErrNetworkAlreadyRegistered return ErrNetworkAlreadyRegistered
} }
@@ -90,10 +91,11 @@ func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) {
a.mutex.Lock() a.mutex.Lock()
defer a.mutex.Unlock() defer a.mutex.Unlock()
key := network.String() nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask}
key := nw.String()
allocated, ok := a.allocatedIPs[key] allocated, ok := a.allocatedIPs[key]
if !ok { if !ok {
allocated = newAllocatedMap(network) allocated = newAllocatedMap(nw)
a.allocatedIPs[key] = allocated a.allocatedIPs[key] = allocated
} }
@@ -109,7 +111,8 @@ func (a *IPAllocator) ReleaseIP(network *net.IPNet, ip net.IP) error {
a.mutex.Lock() a.mutex.Lock()
defer a.mutex.Unlock() defer a.mutex.Unlock()
if allocated, exists := a.allocatedIPs[network.String()]; exists { nw := &net.IPNet{IP: network.IP.Mask(network.Mask), Mask: network.Mask}
if allocated, exists := a.allocatedIPs[nw.String()]; exists {
delete(allocated.p, ip.String()) delete(allocated.p, ip.String())
} }
return nil return nil
+12 -10
View File
@@ -74,20 +74,22 @@ func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
// NetworkRange calculates the first and last IP addresses in an IPNet // NetworkRange calculates the first and last IP addresses in an IPNet
func NetworkRange(network *net.IPNet) (net.IP, net.IP) { func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
var netIP net.IP if network == nil {
if network.IP.To4() != nil {
netIP = network.IP.To4()
} else if network.IP.To16() != nil {
netIP = network.IP.To16()
} else {
return nil, nil return nil, nil
} }
lastIP := make([]byte, len(netIP), len(netIP)) firstIP := network.IP.Mask(network.Mask)
for i := 0; i < len(netIP); i++ { lastIP := types.GetIPCopy(firstIP)
lastIP[i] = netIP[i] | ^network.Mask[i] for i := 0; i < len(firstIP); i++ {
lastIP[i] = firstIP[i] | ^network.Mask[i]
} }
return netIP.Mask(network.Mask), net.IP(lastIP)
if network.IP.To4() != nil {
firstIP = firstIP.To4()
lastIP = lastIP.To4()
}
return firstIP, lastIP
} }
// GetIfaceAddr returns the first IPv4 address and slice of IPv6 addresses for the specified network interface // GetIfaceAddr returns the first IPv4 address and slice of IPv6 addresses for the specified network interface
+11 -3
View File
@@ -139,10 +139,15 @@ func (s *sandboxData) rmEndpoint(ep *endpoint) {
} }
} }
// We don't check if s.endpoints is empty here because
// it should never be empty during a rmEndpoint call and
// if it is we will rightfully panic here
s.Lock() s.Lock()
if len(s.endpoints) == 0 {
// s.endpoints should never be empty and this is unexpected error condition
// We log an error message to note this down for debugging purposes.
logrus.Errorf("No endpoints in sandbox while trying to remove endpoint %s", ep.Name())
s.Unlock()
return
}
highEpBefore := s.endpoints[0] highEpBefore := s.endpoints[0]
var ( var (
i int i int
@@ -245,7 +250,10 @@ func (c *controller) LeaveAll(id string) error {
} }
sData.sandbox().Destroy() sData.sandbox().Destroy()
c.Lock()
delete(c.sandboxes, sandbox.GenerateKey(id)) delete(c.sandboxes, sandbox.GenerateKey(id))
c.Unlock()
return nil return nil
} }
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -1,9 +1,19 @@
package changelist package changelist
// Scopes for TufChanges are simply the TUF roles.
// Unfortunately because of targets delegations, we can only
// cover the base roles.
const (
ScopeRoot = "root"
ScopeTargets = "targets"
ScopeSnapshot = "snapshot"
ScopeTimestamp = "timestamp"
)
// TufChange represents a change to a TUF repo // TufChange represents a change to a TUF repo
type TufChange struct { type TufChange struct {
// Abbreviated because Go doesn't permit a field and method of the same name // Abbreviated because Go doesn't permit a field and method of the same name
Actn int `json:"action"` Actn string `json:"action"`
Role string `json:"role"` Role string `json:"role"`
ChangeType string `json:"type"` ChangeType string `json:"type"`
ChangePath string `json:"path"` ChangePath string `json:"path"`
@@ -11,7 +21,7 @@ type TufChange struct {
} }
// NewTufChange initializes a tufChange object // NewTufChange initializes a tufChange object
func NewTufChange(action int, role, changeType, changePath string, content []byte) *TufChange { func NewTufChange(action string, role, changeType, changePath string, content []byte) *TufChange {
return &TufChange{ return &TufChange{
Actn: action, Actn: action,
Role: role, Role: role,
@@ -22,7 +32,7 @@ func NewTufChange(action int, role, changeType, changePath string, content []byt
} }
// Action return c.Actn // Action return c.Actn
func (c TufChange) Action() int { func (c TufChange) Action() string {
return c.Actn return c.Actn
} }
@@ -5,6 +5,11 @@ type memChangelist struct {
changes []Change changes []Change
} }
// NewMemChangelist instantiates a new in-memory changelist
func NewMemChangelist() Changelist {
return &memChangelist{}
}
// List returns a list of Changes // List returns a list of Changes
func (cl memChangelist) List() []Change { func (cl memChangelist) List() []Change {
return cl.changes return cl.changes

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