mirror of
https://github.com/clearlinux/docker.git
synced 2026-09-05 13:11:30 +00:00
Compare commits
69
Commits
master
...
v1.8.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
696147bdfa | ||
|
|
83f6dbe30a | ||
|
|
a97b89b585 | ||
|
|
29ea36a880 | ||
|
|
ed672d1609 | ||
|
|
5916664220 | ||
|
|
da4b336233 | ||
|
|
74df05ccaa | ||
|
|
2c875215b1 | ||
|
|
be40a48c12 | ||
|
|
85f7f7cfc7 | ||
|
|
e15f6fca3f | ||
|
|
d3bbaa70cd | ||
|
|
cc6f6cb2e2 | ||
|
|
c967dd289f | ||
|
|
7895ec25ea | ||
|
|
5b06c94701 | ||
|
|
5851e2da60 | ||
|
|
9eff33735a | ||
|
|
fc7697b050 | ||
|
|
1bf8954d0d | ||
|
|
dfd9f5989a | ||
|
|
d9581e861d | ||
|
|
c383ceaf37 | ||
|
|
948912f692 | ||
|
|
d19b1b927b | ||
|
|
53f5905379 | ||
|
|
60cbf4da6c | ||
|
|
183628388c | ||
|
|
fbd2267e7d | ||
|
|
a16ab243e5 | ||
|
|
b3c3c4cddc | ||
|
|
0fe5aad984 | ||
|
|
0f5e2fd479 | ||
|
|
2f7145b1c5 | ||
|
|
81efe1f32e | ||
|
|
5ba75ac343 | ||
|
|
290987fcb4 | ||
|
|
98855c863d | ||
|
|
b1f394a247 | ||
|
|
a819a60a94 | ||
|
|
33cdc7f2c4 | ||
|
|
117860577c | ||
|
|
b0ac5df367 | ||
|
|
c109095a58 | ||
|
|
d394113dfe | ||
|
|
2af7f63173 | ||
|
|
f156fb7be5 | ||
|
|
559043b953 | ||
|
|
ba8abcb3dd | ||
|
|
ebf396c6e8 | ||
|
|
47d52fb872 | ||
|
|
d167338876 | ||
|
|
e6844381f0 | ||
|
|
589922adf0 | ||
|
|
689c4e6075 | ||
|
|
43da1adedb | ||
|
|
686fe02020 | ||
|
|
1d02be1c7a | ||
|
|
edb60b950a | ||
|
|
e0e852ee6f | ||
|
|
b537508f8c | ||
|
|
37e886eb7b | ||
|
|
50f65742ef | ||
|
|
56d859d052 | ||
|
|
546a704c63 | ||
|
|
fa85dc0030 | ||
|
|
36b6e5884d | ||
|
|
90991ddb9b |
@@ -1,5 +1,96 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.8.0 (2015-08-06)
|
||||||
|
|
||||||
|
### Distribution
|
||||||
|
|
||||||
|
+ Trusted pull, push and build, disabled by default
|
||||||
|
* Make tar layers deterministic between registries
|
||||||
|
* Don't allow deleting the image of running containers
|
||||||
|
* Check if a tag name to load is a valid digest
|
||||||
|
* Allow one character repository names
|
||||||
|
* Add a more accurate error description for invalid tag name
|
||||||
|
* Make build cache ignore mtime
|
||||||
|
|
||||||
|
### Cli
|
||||||
|
|
||||||
|
+ Add support for DOCKER_CONFIG/--config to specify config file dir
|
||||||
|
+ Add --type flag for docker inspect command
|
||||||
|
+ Add formatting options to `docker ps` with `--format`
|
||||||
|
+ Replace `docker -d` with new subcommand `docker daemon`
|
||||||
|
* Zsh completion updates and improvements
|
||||||
|
* Add some missing events to bash completion
|
||||||
|
* Support daemon urls with base paths in `docker -H`
|
||||||
|
* Validate status= filter to docker ps
|
||||||
|
* Display when a container is in --net=host in docker ps
|
||||||
|
* Extend docker inspect to export image metadata related to graph driver
|
||||||
|
* Restore --default-gateway{,-v6} daemon options
|
||||||
|
* Add missing unpublished ports in docker ps
|
||||||
|
* Allow duration strings in `docker events` as --since/--until
|
||||||
|
* Expose more mounts information in `docker inspect`
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
+ Add new Fluentd logging driver
|
||||||
|
+ Allow `docker import` to load from local files
|
||||||
|
+ Add logging driver for GELF via UDP
|
||||||
|
+ Allow to copy files from host to containers with `docker cp`
|
||||||
|
+ Promote volume drivers from experimental to master
|
||||||
|
+ Add rollover log driver, and --log-driver-opts flag
|
||||||
|
+ Add memory swappiness tuning options
|
||||||
|
* Remove cgroup read-only flag when privileged
|
||||||
|
* Make /proc, /sys, & /dev readonly for readonly containers
|
||||||
|
* Add cgroup bind mount by default
|
||||||
|
* Overlay: Export metadata for container and image in `docker inspect`
|
||||||
|
* Devicemapper: external device activation
|
||||||
|
* Devicemapper: Compare uuid of base device on startup
|
||||||
|
* Remove RC4 from the list of registry cipher suites
|
||||||
|
* Add syslog-facility option
|
||||||
|
* LXC execdriver compatibility with recent LXC versions
|
||||||
|
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
|
||||||
|
* Separate plugin sockets and specs locations
|
||||||
|
* Allow TLS connections to plugins
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Add missing 'Names' field to /containers/json API output
|
||||||
|
- Make `docker rmi --dangling` safe when pulling
|
||||||
|
- Devicemapper: Change default basesize to 100G
|
||||||
|
- Go Scheduler issue with sync.Mutex and gcc
|
||||||
|
- Fix issue where Search API endpoint would panic due to empty AuthConfig
|
||||||
|
- Set image canonical names correctly
|
||||||
|
- Check dockerinit only if lxc driver is used
|
||||||
|
- Fix ulimit usage of nproc
|
||||||
|
- Always attach STDIN if -i,--interactive is specified
|
||||||
|
- Show error messages when saving container state fails
|
||||||
|
- Fixed incorrect assumption on --bridge=none treated as disable network
|
||||||
|
- Check for invalid port specifications in host configuration
|
||||||
|
- Fix endpoint leave failure for --net=host mode
|
||||||
|
- Fix goroutine leak in the stats API if the container is not running
|
||||||
|
- Check for apparmor file before reading it
|
||||||
|
- Fix DOCKER_TLS_VERIFY being ignored
|
||||||
|
- Set umask to the default on startup
|
||||||
|
- Correct the message of pause and unpause a non-running container
|
||||||
|
- Adjust disallowed CpuShares in container creation
|
||||||
|
- ZFS: correctly apply selinux context
|
||||||
|
- Display empty string instead of <nil> when IP opt is nil
|
||||||
|
- `docker kill` returns error when container is not running
|
||||||
|
- Fix COPY/ADD quoted/json form
|
||||||
|
- Fix goroutine leak on logs -f with no output
|
||||||
|
- Remove panic in nat package on invalid hostport
|
||||||
|
- Fix container linking in Fedora 22
|
||||||
|
- Fix error caused using default gateways outside of the allocated range
|
||||||
|
- Format times in inspect command with a template as RFC3339Nano
|
||||||
|
- Make registry client to accept 2xx and 3xx http status responses as successful
|
||||||
|
- Fix race issue that caused the daemon to crash with certain layer downloads failed in a specific order.
|
||||||
|
- Fix error when the docker ps format was not valid.
|
||||||
|
- Remove redundant ip forward check.
|
||||||
|
- Fix issue trying to push images to repository mirrors.
|
||||||
|
- Fix error cleaning up network entrypoints when there is an initialization issue.
|
||||||
|
|
||||||
## 1.7.1 (2015-07-14)
|
## 1.7.1 (2015-07-14)
|
||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
|
|||||||
+1
-1
@@ -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" \
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
+57
-9
@@ -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
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -170,9 +170,11 @@ func customFormat(ctx Context, containers []types.Container) {
|
|||||||
format += "\t{{.Size}}"
|
format += "\t{{.Size}}"
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.New("ps template").Parse(format)
|
tmpl, err := template.New("").Parse(format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buffer.WriteString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
|
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
||||||
|
buffer.WriteTo(ctx.Output)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
@@ -181,8 +183,9 @@ func customFormat(ctx Context, containers []types.Container) {
|
|||||||
c: container,
|
c: container,
|
||||||
}
|
}
|
||||||
if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
||||||
buffer = bytes.NewBufferString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
|
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
||||||
break
|
buffer.WriteTo(ctx.Output)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if table && len(header) == 0 {
|
if table && len(header) == 0 {
|
||||||
header = containerCtx.fullHeader()
|
header = containerCtx.fullHeader()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ps
|
package ps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainerContextID(t *testing.T) {
|
func TestContainerPsContext(t *testing.T) {
|
||||||
containerId := stringid.GenerateRandomID()
|
containerId := stringid.GenerateRandomID()
|
||||||
unix := time.Now().Unix()
|
unix := time.Now().Unix()
|
||||||
|
|
||||||
@@ -86,3 +87,16 @@ func TestContainerContextID(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContainerPsFormatError(t *testing.T) {
|
||||||
|
out := bytes.NewBufferString("")
|
||||||
|
ctx := Context{
|
||||||
|
Format: "{{InvalidFunction}}",
|
||||||
|
Output: out,
|
||||||
|
}
|
||||||
|
|
||||||
|
customFormat(ctx, make([]types.Container, 0))
|
||||||
|
if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" {
|
||||||
|
t.Fatalf("Expected format error, got `%v`\n", out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+28
-9
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
FROM debian:jessie
|
FROM debian:jessie
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
FROM debian:stretch
|
FROM debian:stretch
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
FROM debian:wheezy
|
FROM debian:wheezy
|
||||||
RUN echo deb http://http.debian.net/debian wheezy-backports main > /etc/apt/sources.list.d/wheezy-backports.list
|
RUN echo deb http://http.debian.net/debian wheezy-backports main > /etc/apt/sources.list.d/wheezy-backports.list
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ for version in "${versions[@]}"; do
|
|||||||
build-essential # "essential for building Debian packages"
|
build-essential # "essential for building Debian packages"
|
||||||
curl ca-certificates # for downloading Go
|
curl ca-certificates # for downloading Go
|
||||||
debhelper # for easy ".deb" building
|
debhelper # for easy ".deb" building
|
||||||
dh-apparmor # for apparmor debhelper
|
|
||||||
dh-systemd # for systemd debhelper integration
|
dh-systemd # for systemd debhelper integration
|
||||||
git # for "git commit" info in "docker -v"
|
git # for "git commit" info in "docker -v"
|
||||||
libapparmor-dev # for "sys/apparmor.h"
|
libapparmor-dev # for "sys/apparmor.h"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
FROM ubuntu-debootstrap:precise
|
FROM ubuntu-debootstrap:precise
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion build-essential curl ca-certificates debhelper dh-apparmor git libapparmor-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion build-essential curl ca-certificates debhelper git libapparmor-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
FROM ubuntu-debootstrap:trusty
|
FROM ubuntu-debootstrap:trusty
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
FROM ubuntu-debootstrap:vivid
|
FROM ubuntu-debootstrap:vivid
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
FROM ubuntu-debootstrap:wily
|
FROM ubuntu-debootstrap:wily
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV GO_VERSION 1.4.2
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
|||||||
+118
-64
@@ -27,7 +27,7 @@
|
|||||||
# This order should be applied to lists, alternatives and code blocks.
|
# This order should be applied to lists, alternatives and code blocks.
|
||||||
|
|
||||||
__docker_q() {
|
__docker_q() {
|
||||||
docker ${host:+-H "$host"} 2>/dev/null "$@"
|
docker ${host:+-H "$host"} ${config:+--config "$config"} 2>/dev/null "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
__docker_containers_all() {
|
__docker_containers_all() {
|
||||||
@@ -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,24 @@ __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)
|
--config)
|
||||||
_filedir -d
|
_filedir -d
|
||||||
return
|
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 +342,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 +457,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 +742,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 +1353,7 @@ _docker() {
|
|||||||
commit
|
commit
|
||||||
cp
|
cp
|
||||||
create
|
create
|
||||||
|
daemon
|
||||||
diff
|
diff
|
||||||
events
|
events
|
||||||
exec
|
exec
|
||||||
@@ -1323,41 +1390,23 @@ _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
|
--config
|
||||||
--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 config
|
||||||
local host
|
|
||||||
|
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
@@ -1372,7 +1421,12 @@ _docker() {
|
|||||||
(( counter++ ))
|
(( counter++ ))
|
||||||
host="${words[$counter]}"
|
host="${words[$counter]}"
|
||||||
;;
|
;;
|
||||||
$main_options_with_args_glob )
|
# save config so that completion can use custom configuration directories
|
||||||
|
--config)
|
||||||
|
(( counter++ ))
|
||||||
|
config="${words[$counter]}"
|
||||||
|
;;
|
||||||
|
$(__docker_to_extglob "$global_options_with_args") )
|
||||||
(( counter++ ))
|
(( counter++ ))
|
||||||
;;
|
;;
|
||||||
-*)
|
-*)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ After=network.target docker.socket
|
|||||||
Requires=docker.socket
|
Requires=docker.socket
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Type=notify
|
||||||
ExecStart=/usr/bin/docker daemon -H fd://
|
ExecStart=/usr/bin/docker daemon -H fd://
|
||||||
MountFlags=slave
|
MountFlags=slave
|
||||||
LimitNOFILE=1048576
|
LimitNOFILE=1048576
|
||||||
|
|||||||
+110
-83
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -66,9 +66,6 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
|
|||||||
if err := daemon.mergeAndVerifyConfig(config, img); err != nil {
|
if err := daemon.mergeAndVerifyConfig(config, img); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if !config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled {
|
|
||||||
warnings = append(warnings, "IPv4 forwarding is disabled.")
|
|
||||||
}
|
|
||||||
if hostConfig == nil {
|
if hostConfig == nil {
|
||||||
hostConfig = &runconfig.HostConfig{}
|
hostConfig = &runconfig.HostConfig{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// +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,
|
||||||
|
|
||||||
|
deny @{PROC}/sys/fs/** wklx,
|
||||||
|
deny @{PROC}/fs/** wklx,
|
||||||
|
deny @{PROC}/sysrq-trigger rwklx,
|
||||||
|
deny @{PROC}/mem rwklx,
|
||||||
|
deny @{PROC}/kmem rwklx,
|
||||||
|
deny @{PROC}/kcore 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,
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -200,7 +200,6 @@ func (d *driver) setPrivileged(container *configs.Config) (err error) {
|
|||||||
if apparmor.IsEnabled() {
|
if apparmor.IsEnabled() {
|
||||||
container.AppArmorProfile = "unconfined"
|
container.AppArmorProfile = "unconfined"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
apparmorProfiles := []string{"docker-default"}
|
||||||
|
|
||||||
|
// Allow daemon to run if loading failed, but are active
|
||||||
|
// (possibly through another run, manually, or via system startup)
|
||||||
|
for _, policy := range apparmorProfiles {
|
||||||
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-6
@@ -87,8 +87,8 @@ own.
|
|||||||
container with this image.
|
container with this image.
|
||||||
|
|
||||||
The container exposes port 8000 on the localhost so that you can connect and
|
The container exposes port 8000 on the localhost so that you can connect and
|
||||||
see your changes. If you are running Boot2Docker, use the `boot2docker ip`
|
see your changes. If you use Docker Machine, the `docker-machine ip
|
||||||
to get the address of your server.
|
<machine-name>` command gives you the address of your server.
|
||||||
|
|
||||||
6. Check your writing for style and mechanical errors.
|
6. Check your writing for style and mechanical errors.
|
||||||
|
|
||||||
@@ -158,18 +158,20 @@ update the root docs pages by running
|
|||||||
|
|
||||||
$ make AWS_S3_BUCKET=dowideit-docs BUILD_ROOT=yes docs-release
|
$ make AWS_S3_BUCKET=dowideit-docs BUILD_ROOT=yes docs-release
|
||||||
|
|
||||||
### Errors publishing using Boot2Docker
|
### Errors publishing using a Docker Machine VM
|
||||||
|
|
||||||
Sometimes, in a Boot2Docker environment, the publishing procedure returns this
|
Sometimes, in a Windows or Mac environment, the publishing procedure returns this
|
||||||
error:
|
error:
|
||||||
|
|
||||||
Post http:///var/run/docker.sock/build?rm=1&t=docker-docs%3Apost-1.2.0-docs_update-2:
|
Post http:///var/run/docker.sock/build?rm=1&t=docker-docs%3Apost-1.2.0-docs_update-2:
|
||||||
dial unix /var/run/docker.sock: no such file or directory.
|
dial unix /var/run/docker.sock: no such file or directory.
|
||||||
|
|
||||||
If this happens, set the Docker host. Run the following command to set the
|
If this happens, set the Docker host. Run the following command to get the
|
||||||
variables in your shell:
|
variables in your shell:
|
||||||
|
|
||||||
$ eval "$(boot2docker shellinit)"
|
docker-machine env <machine-name>
|
||||||
|
|
||||||
|
Then, set your environment accordingly.
|
||||||
|
|
||||||
## Cherry-picking documentation changes to update an existing release.
|
## Cherry-picking documentation changes to update an existing release.
|
||||||
|
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ image cache.
|
|||||||
> characters of the full image ID - which can be found using
|
> characters of the full image ID - which can be found using
|
||||||
> `docker inspect` or `docker images --no-trunc=true`.
|
> `docker inspect` or `docker images --no-trunc=true`.
|
||||||
|
|
||||||
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
|
|
||||||
> then _do not_ type the `sudo` before the `docker` commands shown in the
|
|
||||||
> documentation's examples.
|
|
||||||
|
|
||||||
## Running an interactive shell
|
## Running an interactive shell
|
||||||
|
|
||||||
To run an interactive shell in the Ubuntu image:
|
To run an interactive shell in the Ubuntu image:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ First generate CA private and public keys:
|
|||||||
State or Province Name (full name) [Some-State]:Queensland
|
State or Province Name (full name) [Some-State]:Queensland
|
||||||
Locality Name (eg, city) []:Brisbane
|
Locality Name (eg, city) []:Brisbane
|
||||||
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Docker Inc
|
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Docker Inc
|
||||||
Organizational Unit Name (eg, section) []:Boot2Docker
|
Organizational Unit Name (eg, section) []:Sales
|
||||||
Common Name (e.g. server FQDN or YOUR name) []:$HOST
|
Common Name (e.g. server FQDN or YOUR name) []:$HOST
|
||||||
Email Address []:Sven@home.org.au
|
Email Address []:Sven@home.org.au
|
||||||
|
|
||||||
|
|||||||
+29
-11
@@ -33,17 +33,33 @@ If you want Docker to start at boot, you should also:
|
|||||||
There are a number of ways to configure the daemon flags and environment variables
|
There are a number of ways to configure the daemon flags and environment variables
|
||||||
for your Docker daemon.
|
for your Docker daemon.
|
||||||
|
|
||||||
If the `docker.service` file is set to use an `EnvironmentFile`
|
The recommended way is to use a systemd drop-in file. These are local files in
|
||||||
(often pointing to `/etc/sysconfig/docker`) then you can modify the
|
the `/etc/systemd/system/docker.service.d` directory. This could also be
|
||||||
referenced file.
|
`/etc/systemd/system/docker.service`, which also works for overriding the
|
||||||
|
defaults from `/lib/systemd/system/docker.service`.
|
||||||
|
|
||||||
Check if the `docker.service` uses an `EnvironmentFile`:
|
However, if you had previously used a package which had an `EnvironmentFile`
|
||||||
|
(often pointing to `/etc/sysconfig/docker`) then for backwards compatibility,
|
||||||
|
you drop a file in the `/etc/systemd/system/docker.service.d`
|
||||||
|
directory including the following:
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
EnvironmentFile=-/etc/sysconfig/docker
|
||||||
|
EnvironmentFile=-/etc/sysconfig/docker-storage
|
||||||
|
EnvironmentFile=-/etc/sysconfig/docker-network
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/usr/bin/docker -d -H fd:// $OPTIONS \
|
||||||
|
$DOCKER_STORAGE_OPTIONS \
|
||||||
|
$DOCKER_NETWORK_OPTIONS \
|
||||||
|
$BLOCK_REGISTRY \
|
||||||
|
$INSECURE_REGISTRY
|
||||||
|
|
||||||
|
To check if the `docker.service` uses an `EnvironmentFile`:
|
||||||
|
|
||||||
$ sudo systemctl show docker | grep EnvironmentFile
|
$ sudo systemctl show docker | grep EnvironmentFile
|
||||||
EnvironmentFile=-/etc/sysconfig/docker (ignore_errors=yes)
|
EnvironmentFile=-/etc/sysconfig/docker (ignore_errors=yes)
|
||||||
|
|
||||||
Alternatively, find out where the service file is located, and look for the
|
Alternatively, find out where the service file is located:
|
||||||
property:
|
|
||||||
|
|
||||||
$ sudo systemctl status docker | grep Loaded
|
$ sudo systemctl status docker | grep Loaded
|
||||||
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled)
|
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled)
|
||||||
@@ -69,18 +85,20 @@ In this example, we'll assume that your `docker.service` file looks something li
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
EnvironmentFile=-/etc/sysconfig/docker
|
ExecStart=/usr/bin/docker daemon -H fd://
|
||||||
ExecStart=/usr/bin/docker daemon -H fd:// $OPTIONS
|
|
||||||
LimitNOFILE=1048576
|
LimitNOFILE=1048576
|
||||||
LimitNPROC=1048576
|
LimitNPROC=1048576
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
Also=docker.socket
|
Also=docker.socket
|
||||||
|
|
||||||
This will allow us to add extra flags to the `/etc/sysconfig/docker` file by
|
This will allow us to add extra flags via a drop-in file (mentioned above) by
|
||||||
setting `OPTIONS`:
|
placing a file containing the following in the `/etc/systemd/system/docker.service.d`
|
||||||
|
directory:
|
||||||
|
|
||||||
OPTIONS="--graph /mnt/docker-data --storage-driver btrfs"
|
[Service]
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/usr/bin/docker daemon -H fd:// --graph /mnt/docker-data --storage-driver btrfs
|
||||||
|
|
||||||
You can also set other environment variables in this file, for example, the
|
You can also set other environment variables in this file, for example, the
|
||||||
`HTTP_PROXY` environment variables described below.
|
`HTTP_PROXY` environment variables described below.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -124,40 +124,36 @@ The Boot2Docker management tool provides several commands:
|
|||||||
|
|
||||||
## Container port redirection
|
## Container port redirection
|
||||||
|
|
||||||
If you are curious, the username for the boot2docker default user is `docker`
|
If you are curious, the username for the Docker default user is `docker` and the
|
||||||
and the password is `tcuser`.
|
password is `tcuser`. The latest version of `docker-machine` sets up a host only
|
||||||
|
network adaptor which provides access to the container's ports.
|
||||||
|
|
||||||
The latest version of `boot2docker` sets up a host only network adaptor which
|
If you run a container with a published port:
|
||||||
provides access to the container's ports.
|
|
||||||
|
|
||||||
If you run a container with an exposed port:
|
$ docker run --rm -i -t -p 80:80 nginx
|
||||||
|
|
||||||
docker run --rm -i -t -p 80:80 nginx
|
Then you should be able to access that nginx server using the IP address
|
||||||
|
reported to you using:
|
||||||
|
|
||||||
Then you should be able to access that nginx server using the IP address reported
|
$ docker-machine ip
|
||||||
to you using:
|
|
||||||
|
|
||||||
boot2docker ip
|
Typically, the IP is 192.168.59.103, but it could get changed by VirtualBox's
|
||||||
|
DHCP implementation.
|
||||||
Typically, it is 192.168.59.103, but it could get changed by VirtualBox's DHCP
|
|
||||||
implementation.
|
|
||||||
|
|
||||||
For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io)
|
|
||||||
|
|
||||||
## Login with PUTTY instead of using the CMD
|
## Login with PUTTY instead of using the CMD
|
||||||
|
|
||||||
Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh`
|
Docker Machine generates and uses the public/private key pair in your
|
||||||
directory so to log in you need to use the private key from this same directory.
|
`%USERPROFILE%\.ssh` directory so to log in you need to use the private key from
|
||||||
|
this same directory. The private key needs to be converted into the format PuTTY
|
||||||
The private key needs to be converted into the format PuTTY uses.
|
uses. You can do this with
|
||||||
|
|
||||||
You can do this with
|
|
||||||
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
|
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
|
||||||
|
|
||||||
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
|
1. Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
|
||||||
`%USERPROFILE%\.ssh\id_boot2docker`
|
`%USERPROFILE%\.ssh\id_boot2docker`
|
||||||
- then click: "Save Private Key".
|
|
||||||
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
|
2. Click "Save Private Key".
|
||||||
|
|
||||||
|
3. Use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
|
||||||
|
|
||||||
## Uninstallation
|
## Uninstallation
|
||||||
|
|
||||||
|
|||||||
@@ -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)**
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ Docker currently runs only on Linux, but you can use VirtualBox to run Docker in
|
|||||||
a virtual machine on your box, and get the best of both worlds. Check out the
|
a virtual machine on your box, and get the best of both worlds. Check out the
|
||||||
[*Mac OS X*](../installation/mac/#macosx) and [*Microsoft
|
[*Mac OS X*](../installation/mac/#macosx) and [*Microsoft
|
||||||
Windows*](../installation/windows/#windows) installation guides. The small Linux
|
Windows*](../installation/windows/#windows) installation guides. The small Linux
|
||||||
distribution boot2docker can be run inside virtual machines on these two
|
distribution Docker Machine can be run inside virtual machines on these two
|
||||||
operating systems.
|
operating systems.
|
||||||
|
|
||||||
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
|
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ program code and documentation code.
|
|||||||
* Run `gofmt -s -w file.go` on each changed file before
|
* Run `gofmt -s -w file.go` on each changed file before
|
||||||
committing your changes. Most editors have plug-ins that do this automatically.
|
committing your changes. Most editors have plug-ins that do this automatically.
|
||||||
|
|
||||||
|
* Run `golint` on each changed file before
|
||||||
|
committing your changes.
|
||||||
|
|
||||||
* Update the documentation when creating or modifying features.
|
* Update the documentation when creating or modifying features.
|
||||||
|
|
||||||
* Commits that fix or close an issue should reference them in the commit message
|
* Commits that fix or close an issue should reference them in the commit message
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ you continue working with your fork on this branch.
|
|||||||
|
|
||||||
## Clean your host of Docker artifacts
|
## Clean your host of Docker artifacts
|
||||||
|
|
||||||
Docker developers run the latest stable release of the Docker software (with Boot2Docker if their machine is Mac OS X). They clean their local
|
Docker developers run the latest stable release of the Docker software (with Docker Machine if their machine is Mac OS X). They clean their local
|
||||||
hosts of unnecessary Docker artifacts such as stopped containers or unused
|
hosts of unnecessary Docker artifacts such as stopped containers or unused
|
||||||
images. Cleaning unnecessary artifacts isn't strictly necessary, but it is
|
images. Cleaning unnecessary artifacts isn't strictly necessary, but it is
|
||||||
good practice, so it is included here.
|
good practice, so it is included here.
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ target="_blank">docker/docker repository</a>.
|
|||||||
|
|
||||||
$ cd ~
|
$ cd ~
|
||||||
|
|
||||||
In Windows, you'll work in your Boot2Docker window instead of Powershell or
|
In Windows, you'll work in your Docker Quickstart Terminal window instead of
|
||||||
a `cmd` window.
|
Powershell or a `cmd` window.
|
||||||
|
|
||||||
6. Create a `repos` directory.
|
6. Create a `repos` directory.
|
||||||
|
|
||||||
|
|||||||
@@ -317,9 +317,9 @@ can browse the docs.
|
|||||||
|
|
||||||
4. Enter the URL in your browser.
|
4. Enter the URL in your browser.
|
||||||
|
|
||||||
If you are running Boot2Docker, replace the default localhost address
|
If you are using Docker Machine, replace the default localhost address
|
||||||
(0.0.0.0) with your DOCKERHOST value. You can get this value at any time by
|
(0.0.0.0) with your DOCKERHOST value. You can get this value at any time by
|
||||||
entering `boot2docker ip` at the command line.
|
entering `docker-machine ip <machine-name>` at the command line.
|
||||||
|
|
||||||
5. Once in the documentation, look for the red notice to verify you are seeing the correct build.
|
5. Once in the documentation, look for the red notice to verify you are seeing the correct build.
|
||||||
|
|
||||||
|
|||||||
@@ -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!**
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ List containers
|
|||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
||||||
|
"Labels": {
|
||||||
|
"com.example.vendor": "Acme",
|
||||||
|
"com.example.license": "GPL",
|
||||||
|
"com.example.version": "1.0"
|
||||||
|
},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
},
|
},
|
||||||
@@ -60,6 +65,7 @@ List containers
|
|||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [],
|
"Ports": [],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
},
|
},
|
||||||
@@ -71,6 +77,7 @@ List containers
|
|||||||
"Created": 1367854154,
|
"Created": 1367854154,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports":[],
|
"Ports":[],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw":12288,
|
"SizeRw":12288,
|
||||||
"SizeRootFs":0
|
"SizeRootFs":0
|
||||||
},
|
},
|
||||||
@@ -82,6 +89,7 @@ List containers
|
|||||||
"Created": 1367854152,
|
"Created": 1367854152,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [],
|
"Ports": [],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ List containers
|
|||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
||||||
|
"Labels": {
|
||||||
|
"com.example.vendor": "Acme",
|
||||||
|
"com.example.license": "GPL",
|
||||||
|
"com.example.version": "1.0"
|
||||||
|
},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
},
|
},
|
||||||
@@ -62,6 +67,7 @@ List containers
|
|||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [],
|
"Ports": [],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
},
|
},
|
||||||
@@ -73,6 +79,7 @@ List containers
|
|||||||
"Created": 1367854154,
|
"Created": 1367854154,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports":[],
|
"Ports":[],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw":12288,
|
"SizeRw":12288,
|
||||||
"SizeRootFs":0
|
"SizeRootFs":0
|
||||||
},
|
},
|
||||||
@@ -84,6 +91,7 @@ List containers
|
|||||||
"Created": 1367854152,
|
"Created": 1367854152,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [],
|
"Ports": [],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ List containers
|
|||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
||||||
|
"Labels": {
|
||||||
|
"com.example.vendor": "Acme",
|
||||||
|
"com.example.license": "GPL",
|
||||||
|
"com.example.version": "1.0"
|
||||||
|
},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
},
|
},
|
||||||
@@ -62,6 +67,7 @@ List containers
|
|||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [],
|
"Ports": [],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
},
|
},
|
||||||
@@ -73,6 +79,7 @@ List containers
|
|||||||
"Created": 1367854154,
|
"Created": 1367854154,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports":[],
|
"Ports":[],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw":12288,
|
"SizeRw":12288,
|
||||||
"SizeRootFs":0
|
"SizeRootFs":0
|
||||||
},
|
},
|
||||||
@@ -84,6 +91,7 @@ List containers
|
|||||||
"Created": 1367854152,
|
"Created": 1367854152,
|
||||||
"Status": "Exit 0",
|
"Status": "Exit 0",
|
||||||
"Ports": [],
|
"Ports": [],
|
||||||
|
"Labels": {},
|
||||||
"SizeRw": 12288,
|
"SizeRw": 12288,
|
||||||
"SizeRootFs": 0
|
"SizeRootFs": 0
|
||||||
}
|
}
|
||||||
@@ -1109,7 +1117,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 +1128,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
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ parent = "smn_cli"
|
|||||||
|
|
||||||
# Using the command line
|
# Using the command line
|
||||||
|
|
||||||
> **Note:** If you are using a remote Docker daemon, such as Boot2Docker,
|
|
||||||
> then _do not_ type the `sudo` before the `docker` commands shown in the
|
|
||||||
> documentation's examples.
|
|
||||||
|
|
||||||
To list available commands, either run `docker` with no parameters
|
To list available commands, either run `docker` with no parameters
|
||||||
or execute `docker help`:
|
or execute `docker help`:
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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 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
|
||||||
|
```
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ Let's create a directory and a `Dockerfile` first.
|
|||||||
$ cd sinatra
|
$ cd sinatra
|
||||||
$ touch Dockerfile
|
$ touch Dockerfile
|
||||||
|
|
||||||
If you are using Boot2Docker on Windows, you may access your host
|
If you are using Docker Machine on Windows, you may access your host
|
||||||
directory by `cd` to `/c/Users/your_user_name`.
|
directory by `cd` to `/c/Users/your_user_name`.
|
||||||
|
|
||||||
Each instruction creates a new layer of the image. Let's look at a simple
|
Each instruction creates a new layer of the image. Let's look at a simple
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ parent = "smn_applied"
|
|||||||
Docker allows you to run applications inside containers. Running an
|
Docker allows you to run applications inside containers. Running an
|
||||||
application inside a container takes a single command: `docker run`.
|
application inside a container takes a single command: `docker run`.
|
||||||
|
|
||||||
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
|
>**Note**: Depending on your Docker system configuration, you may be required to
|
||||||
> then _do not_ type the `sudo` before the `docker` commands shown in the
|
>preface each `docker` command on this page with `sudo`. To avoid this behavior,
|
||||||
> documentation's examples.
|
>your system administrator can create a Unix group called `docker` and add users
|
||||||
|
>to it.
|
||||||
|
|
||||||
## Hello world
|
## Hello world
|
||||||
|
|
||||||
|
|||||||
@@ -90,13 +90,13 @@ You will notice in the above 'Volumes' is specifying the location on the host an
|
|||||||
In addition to creating a volume using the `-v` flag you can also mount a
|
In addition to creating a volume using the `-v` flag you can also mount a
|
||||||
directory from your Docker daemon's host into a container.
|
directory from your Docker daemon's host into a container.
|
||||||
|
|
||||||
> **Note:**
|
>**Note**: If you are using Docker Machine on Mac or Windows, your Docker daemon
|
||||||
> If you are using Boot2Docker, your Docker daemon only has limited access to
|
>only has limited access to your OS X/Windows filesystem. Docker Machine tries
|
||||||
> your OS X/Windows filesystem. Boot2Docker tries to auto-share your `/Users`
|
>to auto-share your `/Users` (OS X) or `C:\Users` (Windows) directory - and so
|
||||||
> (OS X) or `C:\Users` (Windows) directory - and so you can mount files or directories
|
>you can mount files or directories using `docker run -v
|
||||||
> using `docker run -v /Users/<path>:/<container path> ...` (OS X) or
|
>/Users/<path>:/<container path> ...` (OS X) or `docker run -v
|
||||||
> `docker run -v /c/Users/<path>:/<container path ...` (Windows). All other paths
|
>/c/Users/<path>:/<container path ...` (Windows). All other paths come from your
|
||||||
> come from the Boot2Docker virtual machine's filesystem.
|
>virtual machine's filesystem.
|
||||||
|
|
||||||
$ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
|
$ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName)
|
endpoints, err := s.registryService.LookupPullEndpoints(repoInfo.CanonicalName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-30
@@ -1,6 +1,7 @@
|
|||||||
package graph
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -102,13 +103,13 @@ 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
|
out io.Writer // Download progress is written here.
|
||||||
}
|
}
|
||||||
|
|
||||||
type errVerification struct{}
|
type errVerification struct{}
|
||||||
@@ -118,7 +119,7 @@ func (errVerification) Error() string { return "verification failed" }
|
|||||||
func (p *v2Puller) download(di *downloadInfo) {
|
func (p *v2Puller) download(di *downloadInfo) {
|
||||||
logrus.Debugf("pulling blob %q to %s", di.digest, di.img.ID)
|
logrus.Debugf("pulling blob %q to %s", di.digest, di.img.ID)
|
||||||
|
|
||||||
out := p.config.OutStream
|
out := di.out
|
||||||
|
|
||||||
if c, err := p.poolAdd("pull", "img:"+di.img.ID); err != nil {
|
if c, err := p.poolAdd("pull", "img:"+di.img.ID); err != nil {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
@@ -176,9 +177,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))
|
||||||
@@ -190,7 +193,7 @@ func (p *v2Puller) download(di *downloadInfo) {
|
|||||||
di.err <- nil
|
di.err <- nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
|
func (p *v2Puller) pullV2Tag(tag, taggedName string) (verified bool, err error) {
|
||||||
logrus.Debugf("Pulling tag from V2 registry: %q", tag)
|
logrus.Debugf("Pulling tag from V2 registry: %q", tag)
|
||||||
out := p.config.OutStream
|
out := p.config.OutStream
|
||||||
|
|
||||||
@@ -203,7 +206,7 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
verified, err := p.validateManifest(manifest, tag)
|
verified, err = p.validateManifest(manifest, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -211,6 +214,27 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
|
|||||||
logrus.Printf("Image manifest for %s has been verified", taggedName)
|
logrus.Printf("Image manifest for %s has been verified", taggedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// By using a pipeWriter for each of the downloads to write their progress
|
||||||
|
// to, we can avoid an issue where this function returns an error but
|
||||||
|
// leaves behind running download goroutines. By splitting the writer
|
||||||
|
// with a pipe, we can close the pipe if there is any error, consequently
|
||||||
|
// causing each download to cancel due to an error writing to this pipe.
|
||||||
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
go func() {
|
||||||
|
if _, err := io.Copy(out, pipeReader); err != nil {
|
||||||
|
logrus.Errorf("error copying from layer download progress reader: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
// All operations on the pipe are synchronous. This call will wait
|
||||||
|
// until all current readers/writers are done using the pipe then
|
||||||
|
// set the error. All successive reads/writes will return with this
|
||||||
|
// error.
|
||||||
|
pipeWriter.CloseWithError(errors.New("download canceled"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name()))
|
out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name()))
|
||||||
|
|
||||||
downloads := make([]downloadInfo, len(manifest.FSLayers))
|
downloads := make([]downloadInfo, len(manifest.FSLayers))
|
||||||
@@ -241,6 +265,7 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
|
|||||||
out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil))
|
out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil))
|
||||||
|
|
||||||
downloads[i].err = make(chan error)
|
downloads[i].err = make(chan error)
|
||||||
|
downloads[i].out = pipeWriter
|
||||||
go p.download(&downloads[i])
|
go p.download(&downloads[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +277,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 +392,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 +435,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
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName)
|
endpoints, err := s.registryService.LookupPushEndpoints(repoInfo.CanonicalName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -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,
|
||||||
|
|||||||
+81
-17
@@ -51,6 +51,32 @@ echo_docker_as_nonroot() {
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if this is a forked Linux distro
|
||||||
|
check_forked() {
|
||||||
|
# Check for lsb_release command existence, it usually exists in forked distros
|
||||||
|
if command_exists lsb_release; then
|
||||||
|
# Check if the `-u` option is supported
|
||||||
|
lsb_release -a -u > /dev/null 2>&1
|
||||||
|
|
||||||
|
# Check if the command has exited successfully, it means we're in a forked distro
|
||||||
|
if [ "$?" = "0" ]; then
|
||||||
|
# Print info about current distro
|
||||||
|
cat <<-EOF
|
||||||
|
You're using '$lsb_dist' version '$dist_version'.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Get the upstream release info
|
||||||
|
lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[[:space:]]')
|
||||||
|
dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[[:space:]]')
|
||||||
|
|
||||||
|
# Print info about upstream distro
|
||||||
|
cat <<-EOF
|
||||||
|
Upstream release is '$lsb_dist' version '$dist_version'.
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
do_install() {
|
do_install() {
|
||||||
case "$(uname -m)" in
|
case "$(uname -m)" in
|
||||||
*64)
|
*64)
|
||||||
@@ -119,41 +145,79 @@ 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
|
||||||
|
|
||||||
|
# Check if this is a forked Linux distro
|
||||||
|
check_forked
|
||||||
|
|
||||||
|
# Run setup for each distro accordingly
|
||||||
case "$lsb_dist" in
|
case "$lsb_dist" in
|
||||||
amzn)
|
amzn)
|
||||||
(
|
(
|
||||||
@@ -237,8 +301,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}
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ contrib/init/systemd/docker.socket lib/systemd/system/
|
|||||||
contrib/mk* usr/share/docker-engine/contrib/
|
contrib/mk* usr/share/docker-engine/contrib/
|
||||||
contrib/nuke-graph-directory.sh usr/share/docker-engine/contrib/
|
contrib/nuke-graph-directory.sh usr/share/docker-engine/contrib/
|
||||||
contrib/syntax/nano/Dockerfile.nanorc usr/share/nano/
|
contrib/syntax/nano/Dockerfile.nanorc usr/share/nano/
|
||||||
contrib/apparmor/* etc/apparmor.d/
|
|
||||||
|
|||||||
@@ -32,9 +32,5 @@ override_dh_installudev:
|
|||||||
# match our existing priority
|
# match our existing priority
|
||||||
dh_installudev --priority=z80
|
dh_installudev --priority=z80
|
||||||
|
|
||||||
override_dh_install:
|
|
||||||
dh_apparmor --profile-name=docker -pdocker-engine
|
|
||||||
dh_apparmor --profile-name=docker-engine -pdocker-engine
|
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@ --with=bash-completion $(shell command -v dh_systemd_enable > /dev/null 2>&1 && echo --with=systemd)
|
dh $@ --with=bash-completion $(shell command -v dh_systemd_enable > /dev/null 2>&1 && echo --with=systemd)
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ if [ -z "$DOCKER_TEST_HOST" ]; then
|
|||||||
(
|
(
|
||||||
set -x
|
set -x
|
||||||
/etc/init.d/apparmor start
|
/etc/init.d/apparmor start
|
||||||
|
|
||||||
/sbin/apparmor_parser -r -W -T contrib/apparmor/
|
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+12
-10
@@ -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"
|
||||||
|
|||||||
@@ -72,11 +72,6 @@ bundle_ubuntu() {
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
# Include contributed apparmor policy
|
|
||||||
mkdir -p "$DIR/etc/apparmor.d/"
|
|
||||||
cp contrib/apparmor/docker "$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
|
||||||
mkdir -p "$DIR/usr/bin"
|
mkdir -p "$DIR/usr/bin"
|
||||||
@@ -94,11 +89,6 @@ if [ "$1" = 'configure' ] && [ -z "$2" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! { [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; }; then
|
if ! { [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; }; then
|
||||||
# we only need to do this if upstart isn't in charge
|
# we only need to do this if upstart isn't in charge
|
||||||
update-rc.d docker defaults > /dev/null || true
|
update-rc.d docker defaults > /dev/null || true
|
||||||
|
|||||||
@@ -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
@@ -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 bd3eecc96f3c05a4acef1bedcf74397bc6850d22
|
||||||
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 non-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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ func (s *DockerTrustSuite) TestTrustedIsolatedCreate(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestCreateWhenCertExpired(c *check.C) {
|
func (s *DockerTrustSuite) TestCreateWhenCertExpired(c *check.C) {
|
||||||
|
c.Skip("Currently changes system time, causing instability")
|
||||||
repoName := s.setupTrustedImage(c, "trusted-create-expired")
|
repoName := s.setupTrustedImage(c, "trusted-create-expired")
|
||||||
|
|
||||||
// Certificates have 10 years of expiration
|
// Certificates have 10 years of expiration
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ func (s *DockerTrustSuite) TestUntrustedPull(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestPullWhenCertExpired(c *check.C) {
|
func (s *DockerTrustSuite) TestPullWhenCertExpired(c *check.C) {
|
||||||
|
c.Skip("Currently changes system time, causing instability")
|
||||||
repoName := s.setupTrustedImage(c, "trusted-cert-expired")
|
repoName := s.setupTrustedImage(c, "trusted-cert-expired")
|
||||||
|
|
||||||
// Certificates have 10 years of expiration
|
// Certificates have 10 years of expiration
|
||||||
@@ -331,6 +332,7 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) {
|
func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) {
|
||||||
|
c.Skip("Currently changes system time, causing instability")
|
||||||
repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppull/trusted:latest", privateRegistryURL)
|
repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppull/trusted:latest", privateRegistryURL)
|
||||||
// tag the image and upload it to the private registry
|
// tag the image and upload it to the private registry
|
||||||
dockerCmd(c, "tag", "busybox", repoName)
|
dockerCmd(c, "tag", "busybox", repoName)
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -287,6 +287,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithIncorrectPassphraseForNonRoot(c *c
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestTrustedPushWithExpiredSnapshot(c *check.C) {
|
func (s *DockerTrustSuite) TestTrustedPushWithExpiredSnapshot(c *check.C) {
|
||||||
|
c.Skip("Currently changes system time, causing instability")
|
||||||
repoName := fmt.Sprintf("%v/dockercliexpiredsnapshot/trusted:latest", privateRegistryURL)
|
repoName := fmt.Sprintf("%v/dockercliexpiredsnapshot/trusted:latest", privateRegistryURL)
|
||||||
// tag the image and upload it to the private registry
|
// tag the image and upload it to the private registry
|
||||||
dockerCmd(c, "tag", "busybox", repoName)
|
dockerCmd(c, "tag", "busybox", repoName)
|
||||||
@@ -322,6 +323,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithExpiredSnapshot(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestTrustedPushWithExpiredTimestamp(c *check.C) {
|
func (s *DockerTrustSuite) TestTrustedPushWithExpiredTimestamp(c *check.C) {
|
||||||
|
c.Skip("Currently changes system time, causing instability")
|
||||||
repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppush/trusted:latest", privateRegistryURL)
|
repoName := fmt.Sprintf("%v/dockercliexpiredtimestamppush/trusted:latest", privateRegistryURL)
|
||||||
// tag the image and upload it to the private registry
|
// tag the image and upload it to the private registry
|
||||||
dockerCmd(c, "tag", "busybox", repoName)
|
dockerCmd(c, "tag", "busybox", repoName)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2600,6 +2632,7 @@ func (s *DockerTrustSuite) TestUntrustedRun(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestRunWhenCertExpired(c *check.C) {
|
func (s *DockerTrustSuite) TestRunWhenCertExpired(c *check.C) {
|
||||||
|
c.Skip("Currently changes system time, causing instability")
|
||||||
repoName := s.setupTrustedImage(c, "trusted-run-expired")
|
repoName := s.setupTrustedImage(c, "trusted-run-expired")
|
||||||
|
|
||||||
// Certificates have 10 years of expiration
|
// Certificates have 10 years of expiration
|
||||||
@@ -2704,3 +2737,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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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...)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-5
@@ -50,9 +50,15 @@ To see the man page for a command run **man docker <command>**.
|
|||||||
**--default-gateway-v6**=""
|
**--default-gateway-v6**=""
|
||||||
IPv6 address of the container default gateway
|
IPv6 address of the container default gateway
|
||||||
|
|
||||||
|
**--default-ulimit**=[]
|
||||||
|
Set default ulimits for containers.
|
||||||
|
|
||||||
**--dns**=""
|
**--dns**=""
|
||||||
Force Docker to use specific DNS servers
|
Force Docker to use specific DNS servers
|
||||||
|
|
||||||
|
**--dns-search**=[]
|
||||||
|
DNS search domains to use.
|
||||||
|
|
||||||
**-e**, **--exec-driver**=""
|
**-e**, **--exec-driver**=""
|
||||||
Force Docker to use specific exec driver. Default is `native`.
|
Force Docker to use specific exec driver. Default is `native`.
|
||||||
|
|
||||||
@@ -60,7 +66,7 @@ To see the man page for a command run **man docker <command>**.
|
|||||||
Set exec driver options. See EXEC DRIVER OPTIONS.
|
Set exec driver options. See EXEC DRIVER OPTIONS.
|
||||||
|
|
||||||
**--exec-root**=""
|
**--exec-root**=""
|
||||||
Path to use as the root of the Docker execdriver. Default is `/var/run/docker`.
|
Path to use as the root of the Docker exec driver. Default is `/var/run/docker`.
|
||||||
|
|
||||||
**--fixed-cidr**=""
|
**--fixed-cidr**=""
|
||||||
IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip)
|
IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip)
|
||||||
@@ -83,6 +89,9 @@ unix://[/path/to/socket] to use.
|
|||||||
**--icc**=*true*|*false*
|
**--icc**=*true*|*false*
|
||||||
Allow unrestricted inter\-container and Docker daemon host communication. If disabled, containers can still be linked together using **--link** option (see **docker-run(1)**). Default is true.
|
Allow unrestricted inter\-container and Docker daemon host communication. If disabled, containers can still be linked together using **--link** option (see **docker-run(1)**). Default is true.
|
||||||
|
|
||||||
|
**--insecure-registry**=[]
|
||||||
|
Enable insecure registry communication.
|
||||||
|
|
||||||
**--ip**=""
|
**--ip**=""
|
||||||
Default IP address to use when binding container ports. Default is `0.0.0.0`.
|
Default IP address to use when binding container ports. Default is `0.0.0.0`.
|
||||||
|
|
||||||
@@ -131,10 +140,19 @@ unix://[/path/to/socket] to use.
|
|||||||
**--storage-opt**=[]
|
**--storage-opt**=[]
|
||||||
Set storage driver options. See STORAGE DRIVER OPTIONS.
|
Set storage driver options. See STORAGE DRIVER OPTIONS.
|
||||||
|
|
||||||
**-tls**=*true*|*false*
|
**--tls**=*true*|*false*
|
||||||
Use TLS; implied by --tlsverify. Default is false.
|
Use TLS; implied by --tlsverify. Default is false.
|
||||||
|
|
||||||
**-tlsverify**=*true*|*false*
|
**--tlscacert**=~/.docker/ca.pem
|
||||||
|
Trust certs signed only by this CA.
|
||||||
|
|
||||||
|
**--tlscert**=~/.docker/cert.pem
|
||||||
|
Path to TLS certificate file.
|
||||||
|
|
||||||
|
**--tlskey**=~/.docker/key.pem
|
||||||
|
Path to TLS key file.
|
||||||
|
|
||||||
|
**--tlsverify**=*true*|*false*
|
||||||
Use TLS and verify the remote (daemon: verify client, client: verify daemon).
|
Use TLS and verify the remote (daemon: verify client, client: verify daemon).
|
||||||
Default is false.
|
Default is false.
|
||||||
|
|
||||||
@@ -242,6 +260,10 @@ inside it)
|
|||||||
Push an image or a repository to a Docker Registry
|
Push an image or a repository to a Docker Registry
|
||||||
See **docker-push(1)** for full documentation on the **push** command.
|
See **docker-push(1)** for full documentation on the **push** command.
|
||||||
|
|
||||||
|
**rename**
|
||||||
|
Rename a container.
|
||||||
|
See **docker-rename(1)** for full documentation on the **rename** command.
|
||||||
|
|
||||||
**restart**
|
**restart**
|
||||||
Restart a running container
|
Restart a running container
|
||||||
See **docker-restart(1)** for full documentation on the **restart** command.
|
See **docker-restart(1)** for full documentation on the **restart** command.
|
||||||
@@ -411,7 +433,7 @@ Example use: `docker -d --storage-opt dm.loopdatasize=200G`
|
|||||||
**Note**: This option configures devicemapper loopback, which should not be used in production.
|
**Note**: This option configures devicemapper loopback, which should not be used in production.
|
||||||
|
|
||||||
Specifies the size to use when creating the loopback file for the
|
Specifies the size to use when creating the loopback file for the
|
||||||
"metadadata" device which is used for the thin pool. The default size
|
"metadata" device which is used for the thin pool. The default size
|
||||||
is 2G. The file is sparse, so it will not initially take up
|
is 2G. The file is sparse, so it will not initially take up
|
||||||
this much space.
|
this much space.
|
||||||
|
|
||||||
@@ -473,7 +495,7 @@ When `udev` sync support is `true`, then `devicemapper` and `udev` can
|
|||||||
coordinate the activation and deactivation of devices for containers.
|
coordinate the activation and deactivation of devices for containers.
|
||||||
|
|
||||||
When `udev` sync support is `false`, a race condition occurs between
|
When `udev` sync support is `false`, a race condition occurs between
|
||||||
the`devicemapper` and `udev` during create and cleanup. The race
|
the `devicemapper` and `udev` during create and cleanup. The race
|
||||||
condition results in errors and failures. (For information on these
|
condition results in errors and failures. (For information on these
|
||||||
failures, see
|
failures, see
|
||||||
[docker#4036](https://github.com/docker/docker/issues/4036))
|
[docker#4036](https://github.com/docker/docker/issues/4036))
|
||||||
|
|||||||
+41
-17
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user