Compare commits
79 Commits
v17.12.0-c
...
v17.12.1-c
| Author | SHA1 | Date | |
|---|---|---|---|
| b0992d220f | |||
| 5152611926 | |||
| 893276b2e8 | |||
| 319872e09a | |||
| 71498a13be | |||
| deff200c90 | |||
| b24bd87f20 | |||
| e634ffeb95 | |||
| 80c6fa7153 | |||
| 731f1c37f0 | |||
| 1251f23e0d | |||
| 1c24f4566e | |||
| a27f508a6c | |||
| 31e4ca26a3 | |||
| 5bc96351ab | |||
| ee633783d6 | |||
| 72f6b9a3d7 | |||
| 7cb8389345 | |||
| a22eabf65d | |||
| feff709de8 | |||
| 36e98850f2 | |||
| 675493e24d | |||
| 602216ce56 | |||
| 7ec8b355f2 | |||
| f5152d8714 | |||
| 3869e4896d | |||
| e9f1a359d7 | |||
| 68dbbc33ca | |||
| 8d3d4fa90a | |||
| 246956ffb1 | |||
| 831e67711b | |||
| 0a43e1edf9 | |||
| d70d9c910a | |||
| 3cfc217709 | |||
| fa49979990 | |||
| 62a24759f6 | |||
| 2d24bc5e5f | |||
| d852c51a7d | |||
| c85f7d7628 | |||
| 0d1f2df861 | |||
| 5fcd931d5f | |||
| 38ba0c6ef7 | |||
| b1267c5341 | |||
| bd3930dfb1 | |||
| f312cb1bb1 | |||
| 69b85c633e | |||
| 84d4132c8d | |||
| dbf4d3a8ca | |||
| 092f60f9eb | |||
| b8eb1db1d3 | |||
| 8136093f88 | |||
| 1c517bd52c | |||
| 7a399f3d9a | |||
| ea3ea188f6 | |||
| f0cfc346fd | |||
| 4822efab31 | |||
| f5829ca5cf | |||
| 1d1bcb2fca | |||
| 8c22dc2e68 | |||
| 09d84539fa | |||
| e3eb25711d | |||
| 67d4bb5888 | |||
| 7d2c7004af | |||
| 5507d73275 | |||
| 1e67593a37 | |||
| 25c4322a30 | |||
| 5f1c192c19 | |||
| 276c2ad6a4 | |||
| ae80c6aedf | |||
| 6da2ecc95c | |||
| a7ee159424 | |||
| 092e59ef76 | |||
| bf9d7adabe | |||
| 88f57f81f9 | |||
| c97c6d62c2 | |||
| 2861174d81 | |||
| 8673a4245b | |||
| 5dee703312 | |||
| 351bf41f51 |
56
CHANGELOG.md
56
CHANGELOG.md
@ -5,13 +5,57 @@ information on the list of deprecated flags and APIs please have a look at
|
||||
https://docs.docker.com/engine/deprecated/ where target removal dates can also
|
||||
be found.
|
||||
|
||||
IMPORTANT:
|
||||
|
||||
You should stop all containers and plugins **BEFORE** upgrading to Docker CE 17.12.
|
||||
**IMPORTANT**: You must stop all containers and plugins **BEFORE** upgrading to Docker CE 17.12.
|
||||
See related PR: [moby/moby#35812](https://github.com/moby/moby/pull/35812)
|
||||
|
||||
## 17.12.0-ce (2017-12-DD)
|
||||
## 17.12.1-ce (2018-02-DD)
|
||||
|
||||
### Client
|
||||
- Fix `node-generic-resource` typo [moby/moby#35970](https://github.com/moby/moby/pull/35970) and [moby/moby#36125](https://github.com/moby/moby/pull/36125)
|
||||
* Return errors from daemon on stack deploy configs create/update [docker/cli#757](https://github.com/docker/cli/pull/757)
|
||||
|
||||
### Logging
|
||||
- awslogs: fix batch size calculation for large logs [moby/moby#35726](https://github.com/moby/moby/pull/35726)
|
||||
* Support a proxy in splunk log driver [moby/moby#36220](https://github.com/moby/moby/pull/36220)
|
||||
|
||||
### Networking
|
||||
- Fix ingress network when upgrading from 17.09 to 17.12 [moby/moby#36003](https://github.com/moby/moby/pull/36003)
|
||||
* Add verbose info to partial overlay ID [moby/moby#35989](https://github.com/moby/moby/pull/35989)
|
||||
- Fix IPv6 networking being deconfigured if live-restore is being enabled [docker/libnetwork#2043](https://github.com/docker/libnetwork/pull/2043)
|
||||
- Fix watchMiss thread context [docker/libnetwork#2051](https://github.com/docker/libnetwork/pull/2051)
|
||||
|
||||
### Packaging
|
||||
- Set TasksMax in docker.service [docker/docker-ce-packaging#78](https://github.com/docker/docker-ce-packaging/pull/78)
|
||||
|
||||
### Runtime
|
||||
* Bump Golang to 1.9.4
|
||||
* Bump containerd to 1.0.1
|
||||
- Fix dockerd not being able to reconnect to containerd when it is restarted [moby/moby#36173](https://github.com/moby/moby/pull/36173)
|
||||
- Fix containerd events from being processed twice [moby/moby#35891](https://github.com/moby/moby/issues/35891)
|
||||
- Fix vfs graph driver failure to initialize because of failure to setup fs quota [moby/moby#35827](https://github.com/moby/moby/pull/35827)
|
||||
- Fix regression of health check not using container's working directory [moby/moby#35845](https://github.com/moby/moby/pull/35845)
|
||||
- Honor `DOCKER_RAMDISK` with containerd 1.0 [moby/moby#35957](https://github.com/moby/moby/pull/35957)
|
||||
- Update runc to fix hang during start and exec [moby/moby#36097](https://github.com/moby/moby/pull/36097)
|
||||
- Windows: Vendor of Microsoft/hcsshim @v.0.6.8 partial fix for import layer failing [moby/moby#35924](https://github.com/moby/moby/pull/35924)
|
||||
* Do not make graphdriver homes private mounts [moby/moby#36047](https://github.com/moby/moby/pull/36047)
|
||||
* Use rslave propogation for mounts from daemon root [moby/moby#36055](https://github.com/moby/moby/pull/36055)
|
||||
* Set daemon root to use shared mount propagation [moby/moby#36096](https://github.com/moby/moby/pull/36096)
|
||||
* Validate that mounted paths exist when container is started, not just during creation [moby/moby#35833](https://github.com/moby/moby/pull/35833)
|
||||
* Add `REMOVE` and `ORPHANED` to TaskState [moby/moby#36146](https://github.com/moby/moby/pull/36146)
|
||||
- Fix issue where network inspect does not show Created time for networks in swarm scope [moby/moby#36095](https://github.com/moby/moby/pull/36095)
|
||||
* Nullify container read write layer upon release [moby/moby#36130](https://github.com/moby/moby/pull/36160) and [moby/moby#36343](https://github.com/moby/moby/pull/36242)
|
||||
|
||||
### Swarm
|
||||
* Remove watchMiss from swarm mode [docker/libnetwork#2047](https://github.com/docker/libnetwork/pull/2047)
|
||||
|
||||
## 17.12.0-ce (2017-12-27)
|
||||
|
||||
## Known Issues
|
||||
* AWS logs batch size calculation [moby/moby#35726](https://github.com/moby/moby/pull/35726)
|
||||
* Health check no longer uses the container's working directory [moby/moby#35843](https://github.com/moby/moby/issues/35843)
|
||||
* Errors not returned from client in stack deploy configs [moby/moby#757](https://github.com/docker/cli/pull/757)
|
||||
* Daemon aborts when project quota fails [moby/moby#35827](https://github.com/moby/moby/pull/35827)
|
||||
* Docker cannot use memory limit when using systemd options [moby/moby#35123](https://github.com/moby/moby/issues/35123)
|
||||
|
||||
### Builder
|
||||
|
||||
@ -43,9 +87,8 @@ See related PR: [moby/moby#35812](https://github.com/moby/moby/pull/35812)
|
||||
* Logentries line-only logopt fix to maintain backwards compatibility [moby/moby#35628](https://github.com/moby/moby/pull/35628)
|
||||
+ Add `--until` flag for docker logs [moby/moby#32914](https://github.com/moby/moby/pull/32914)
|
||||
+ Add gelf log driver plugin to Windows build [moby/moby#35073](https://github.com/moby/moby/pull/35073)
|
||||
* Set timeout on splunk batch send [moby/moby#35496](https://github.com/moby/moby/pull/35496)
|
||||
* Set timeout on Splunk batch send [moby/moby#35496](https://github.com/moby/moby/pull/35496)
|
||||
* Update Graylog2/go-gelf [moby/moby#35765](https://github.com/moby/moby/pull/35765)
|
||||
- Fix aws logs batch size calculation [moby/moby#35726](https://github.com/moby/moby/pull/35726)
|
||||
|
||||
### Networking
|
||||
|
||||
@ -105,3 +148,4 @@ See related PR: [moby/moby#35812](https://github.com/moby/moby/pull/35812)
|
||||
+ Add Packaging for Fedora 27 [docker/docker-ce-packaging#59](https://github.com/docker/docker-ce-packaging/pull/59)
|
||||
* Change default versioning scheme to 0.0.0-dev unless specified for packaging [docker/docker-ce-packaging#67](https://github.com/docker/docker-ce-packaging/pull/67)
|
||||
* Pass Version to engine static builds [docker/docker-ce-packaging#70](https://github.com/docker/docker-ce-packaging/pull/70)
|
||||
+ Added support for aarch64 on Debian (stretch/jessie) and Ubuntu Zesty or newer [docker/docker-ce-packaging#35](https://github.com/docker/docker-ce-packaging/pull/35)
|
||||
|
||||
@ -1 +1 @@
|
||||
17.12.0-ce-rc4
|
||||
17.12.1-ce-rc2
|
||||
|
||||
@ -248,13 +248,13 @@ func createConfigs(
|
||||
case err == nil:
|
||||
// config already exists, then we update that
|
||||
if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
|
||||
errors.Wrapf(err, "failed to update config %s", configSpec.Name)
|
||||
return errors.Wrapf(err, "failed to update config %s", configSpec.Name)
|
||||
}
|
||||
case apiclient.IsErrNotFound(err):
|
||||
// config does not exist, then we create a new one.
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
|
||||
if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
|
||||
errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
||||
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
||||
}
|
||||
default:
|
||||
return err
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
FROM dockercore/golang-cross@sha256:2e843a0e4d82b6bab34d2cb7abe26d1a6cda23226ecc3869100c8db553603f9b
|
||||
FROM dockercore/golang-cross:1.9.4@sha256:b8d43ef11ccaa15bec63a1f1fd0c28a0e729074aa62fcfa51f0a5888f3571315
|
||||
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
||||
WORKDIR /go/src/github.com/docker/cli
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
FROM golang:1.9.2-alpine3.6
|
||||
FROM golang:1.9.4-alpine3.6
|
||||
|
||||
RUN apk add -U git make bash coreutils ca-certificates
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM golang:1.9.2-alpine3.6
|
||||
FROM golang:1.9.4-alpine3.6
|
||||
|
||||
RUN apk add -U git
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ RUN apt-get update && apt-get install -y \
|
||||
# will need updating, to avoid errors. Ping #docker-maintainers on IRC
|
||||
# with a heads-up.
|
||||
# IMPORTANT: When updating this please note that stdlib archive/tar pkg is vendored
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" \
|
||||
| tar -xzC /usr/local
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ RUN apt-get update && apt-get install -y \
|
||||
|
||||
# Install Go
|
||||
# IMPORTANT: When updating this please note that stdlib archive/tar pkg is vendored
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-arm64.tar.gz" \
|
||||
| tar -xzC /usr/local
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ RUN apt-get update && apt-get install -y \
|
||||
|
||||
# Install Go
|
||||
# IMPORTANT: When updating this please note that stdlib archive/tar pkg is vendored
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-armv6l.tar.gz" \
|
||||
| tar -xzC /usr/local
|
||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
## Step 1: Build tests
|
||||
FROM golang:1.9.2-alpine3.6 as builder
|
||||
FROM golang:1.9.4-alpine3.6 as builder
|
||||
|
||||
RUN apk add --update \
|
||||
bash \
|
||||
|
||||
@ -64,7 +64,7 @@ RUN apt-get update && apt-get install -y \
|
||||
# Install Go
|
||||
# NOTE: official ppc64le go binaries weren't available until go 1.6.4 and 1.7.4
|
||||
# IMPORTANT: When updating this please note that stdlib archive/tar pkg is vendored
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-ppc64le.tar.gz" \
|
||||
| tar -xzC /usr/local
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ RUN apt-get update && apt-get install -y \
|
||||
--no-install-recommends
|
||||
|
||||
# IMPORTANT: When updating this please note that stdlib archive/tar pkg is vendored
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-s390x.tar.gz" \
|
||||
| tar -xzC /usr/local
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# will need updating, to avoid errors. Ping #docker-maintainers on IRC
|
||||
# with a heads-up.
|
||||
# IMPORTANT: When updating this please note that stdlib archive/tar pkg is vendored
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" \
|
||||
| tar -xzC /usr/local
|
||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||
|
||||
@ -161,7 +161,7 @@ SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPref
|
||||
# Environment variable notes:
|
||||
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
# - FROM_DOCKERFILE is used for detection of building within a container.
|
||||
ENV GO_VERSION=1.9.2 `
|
||||
ENV GO_VERSION=1.9.4 `
|
||||
GIT_VERSION=2.11.1 `
|
||||
GOPATH=C:\go `
|
||||
FROM_DOCKERFILE=1
|
||||
|
||||
@ -177,6 +177,13 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
// return the network. Skipped using isMatchingScope because it is true if the scope
|
||||
// is not set which would be case if the client API v1.30
|
||||
if strings.HasPrefix(nwk.ID, term) || (netconst.SwarmScope == scope) {
|
||||
// If we have a previous match "backend", return it, we need verbose when enabled
|
||||
// ex: overlay/partial_ID or name/swarm_scope
|
||||
if nwv, ok := listByPartialID[nwk.ID]; ok {
|
||||
nwk = nwv
|
||||
} else if nwv, ok := listByFullName[nwk.ID]; ok {
|
||||
nwk = nwv
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, nwk)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2788,6 +2788,8 @@ definitions:
|
||||
- "shutdown"
|
||||
- "failed"
|
||||
- "rejected"
|
||||
- "remove"
|
||||
- "orphaned"
|
||||
|
||||
Task:
|
||||
type: "object"
|
||||
|
||||
@ -36,6 +36,10 @@ const (
|
||||
TaskStateFailed TaskState = "failed"
|
||||
// TaskStateRejected REJECTED
|
||||
TaskStateRejected TaskState = "rejected"
|
||||
// TaskStateRemove REMOVE
|
||||
TaskStateRemove TaskState = "remove"
|
||||
// TaskStateOrphaned ORPHANED
|
||||
TaskStateOrphaned TaskState = "orphaned"
|
||||
)
|
||||
|
||||
// Task represents a task.
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
@ -23,10 +22,8 @@ import (
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/go-connections/nat"
|
||||
lcUser "github.com/opencontainers/runc/libcontainer/user"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -216,82 +213,6 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
|
||||
return b.exportImage(state, imageMount, runConfigWithCommentCmd)
|
||||
}
|
||||
|
||||
func parseChownFlag(chown, ctrRootPath string, idMappings *idtools.IDMappings) (idtools.IDPair, error) {
|
||||
var userStr, grpStr string
|
||||
parts := strings.Split(chown, ":")
|
||||
if len(parts) > 2 {
|
||||
return idtools.IDPair{}, errors.New("invalid chown string format: " + chown)
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
// if no group specified, use the user spec as group as well
|
||||
userStr, grpStr = parts[0], parts[0]
|
||||
} else {
|
||||
userStr, grpStr = parts[0], parts[1]
|
||||
}
|
||||
|
||||
passwdPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "passwd"), ctrRootPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/passwd path in container rootfs")
|
||||
}
|
||||
groupPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "group"), ctrRootPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/group path in container rootfs")
|
||||
}
|
||||
uid, err := lookupUser(userStr, passwdPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't find uid for user "+userStr)
|
||||
}
|
||||
gid, err := lookupGroup(grpStr, groupPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't find gid for group "+grpStr)
|
||||
}
|
||||
|
||||
// convert as necessary because of user namespaces
|
||||
chownPair, err := idMappings.ToHost(idtools.IDPair{UID: uid, GID: gid})
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "unable to convert uid/gid to host mapping")
|
||||
}
|
||||
return chownPair, nil
|
||||
}
|
||||
|
||||
func lookupUser(userStr, filepath string) (int, error) {
|
||||
// if the string is actually a uid integer, parse to int and return
|
||||
// as we don't need to translate with the help of files
|
||||
uid, err := strconv.Atoi(userStr)
|
||||
if err == nil {
|
||||
return uid, nil
|
||||
}
|
||||
users, err := lcUser.ParsePasswdFileFilter(filepath, func(u lcUser.User) bool {
|
||||
return u.Name == userStr
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return 0, errors.New("no such user: " + userStr)
|
||||
}
|
||||
return users[0].Uid, nil
|
||||
}
|
||||
|
||||
func lookupGroup(groupStr, filepath string) (int, error) {
|
||||
// if the string is actually a gid integer, parse to int and return
|
||||
// as we don't need to translate with the help of files
|
||||
gid, err := strconv.Atoi(groupStr)
|
||||
if err == nil {
|
||||
return gid, nil
|
||||
}
|
||||
groups, err := lcUser.ParseGroupFileFilter(filepath, func(g lcUser.Group) bool {
|
||||
return g.Name == groupStr
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return 0, errors.New("no such group: " + groupStr)
|
||||
}
|
||||
return groups[0].Gid, nil
|
||||
}
|
||||
|
||||
func createDestInfo(workingDir string, inst copyInstruction, imageMount *imageMount, platform string) (copyInfo, error) {
|
||||
// Twiddle the destination when it's a relative path - meaning, make it
|
||||
// relative to the WORKINGDIR
|
||||
|
||||
88
components/engine/builder/dockerfile/internals_linux.go
Normal file
88
components/engine/builder/dockerfile/internals_linux.go
Normal file
@ -0,0 +1,88 @@
|
||||
package dockerfile
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
lcUser "github.com/opencontainers/runc/libcontainer/user"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func parseChownFlag(chown, ctrRootPath string, idMappings *idtools.IDMappings) (idtools.IDPair, error) {
|
||||
var userStr, grpStr string
|
||||
parts := strings.Split(chown, ":")
|
||||
if len(parts) > 2 {
|
||||
return idtools.IDPair{}, errors.New("invalid chown string format: " + chown)
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
// if no group specified, use the user spec as group as well
|
||||
userStr, grpStr = parts[0], parts[0]
|
||||
} else {
|
||||
userStr, grpStr = parts[0], parts[1]
|
||||
}
|
||||
|
||||
passwdPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "passwd"), ctrRootPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/passwd path in container rootfs")
|
||||
}
|
||||
groupPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "group"), ctrRootPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/group path in container rootfs")
|
||||
}
|
||||
uid, err := lookupUser(userStr, passwdPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't find uid for user "+userStr)
|
||||
}
|
||||
gid, err := lookupGroup(grpStr, groupPath)
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "can't find gid for group "+grpStr)
|
||||
}
|
||||
|
||||
// convert as necessary because of user namespaces
|
||||
chownPair, err := idMappings.ToHost(idtools.IDPair{UID: uid, GID: gid})
|
||||
if err != nil {
|
||||
return idtools.IDPair{}, errors.Wrapf(err, "unable to convert uid/gid to host mapping")
|
||||
}
|
||||
return chownPair, nil
|
||||
}
|
||||
|
||||
func lookupUser(userStr, filepath string) (int, error) {
|
||||
// if the string is actually a uid integer, parse to int and return
|
||||
// as we don't need to translate with the help of files
|
||||
uid, err := strconv.Atoi(userStr)
|
||||
if err == nil {
|
||||
return uid, nil
|
||||
}
|
||||
users, err := lcUser.ParsePasswdFileFilter(filepath, func(u lcUser.User) bool {
|
||||
return u.Name == userStr
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return 0, errors.New("no such user: " + userStr)
|
||||
}
|
||||
return users[0].Uid, nil
|
||||
}
|
||||
|
||||
func lookupGroup(groupStr, filepath string) (int, error) {
|
||||
// if the string is actually a gid integer, parse to int and return
|
||||
// as we don't need to translate with the help of files
|
||||
gid, err := strconv.Atoi(groupStr)
|
||||
if err == nil {
|
||||
return gid, nil
|
||||
}
|
||||
groups, err := lcUser.ParseGroupFileFilter(filepath, func(g lcUser.Group) bool {
|
||||
return g.Name == groupStr
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return 0, errors.New("no such group: " + groupStr)
|
||||
}
|
||||
return groups[0].Gid, nil
|
||||
}
|
||||
138
components/engine/builder/dockerfile/internals_linux_test.go
Normal file
138
components/engine/builder/dockerfile/internals_linux_test.go
Normal file
@ -0,0 +1,138 @@
|
||||
package dockerfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChownFlagParsing(t *testing.T) {
|
||||
testFiles := map[string]string{
|
||||
"passwd": `root:x:0:0::/bin:/bin/false
|
||||
bin:x:1:1::/bin:/bin/false
|
||||
wwwwww:x:21:33::/bin:/bin/false
|
||||
unicorn:x:1001:1002::/bin:/bin/false
|
||||
`,
|
||||
"group": `root:x:0:
|
||||
bin:x:1:
|
||||
wwwwww:x:33:
|
||||
unicorn:x:1002:
|
||||
somegrp:x:5555:
|
||||
othergrp:x:6666:
|
||||
`,
|
||||
}
|
||||
// test mappings for validating use of maps
|
||||
idMaps := []idtools.IDMap{
|
||||
{
|
||||
ContainerID: 0,
|
||||
HostID: 100000,
|
||||
Size: 65536,
|
||||
},
|
||||
}
|
||||
remapped := idtools.NewIDMappingsFromMaps(idMaps, idMaps)
|
||||
unmapped := &idtools.IDMappings{}
|
||||
|
||||
contextDir, cleanup := createTestTempDir(t, "", "builder-chown-parse-test")
|
||||
defer cleanup()
|
||||
|
||||
if err := os.Mkdir(filepath.Join(contextDir, "etc"), 0755); err != nil {
|
||||
t.Fatalf("error creating test directory: %v", err)
|
||||
}
|
||||
|
||||
for filename, content := range testFiles {
|
||||
createTestTempFile(t, filepath.Join(contextDir, "etc"), filename, content, 0644)
|
||||
}
|
||||
|
||||
// positive tests
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
chownStr string
|
||||
idMapping *idtools.IDMappings
|
||||
expected idtools.IDPair
|
||||
}{
|
||||
{
|
||||
name: "UIDNoMap",
|
||||
chownStr: "1",
|
||||
idMapping: unmapped,
|
||||
expected: idtools.IDPair{UID: 1, GID: 1},
|
||||
},
|
||||
{
|
||||
name: "UIDGIDNoMap",
|
||||
chownStr: "0:1",
|
||||
idMapping: unmapped,
|
||||
expected: idtools.IDPair{UID: 0, GID: 1},
|
||||
},
|
||||
{
|
||||
name: "UIDWithMap",
|
||||
chownStr: "0",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 100000, GID: 100000},
|
||||
},
|
||||
{
|
||||
name: "UIDGIDWithMap",
|
||||
chownStr: "1:33",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 100001, GID: 100033},
|
||||
},
|
||||
{
|
||||
name: "UserNoMap",
|
||||
chownStr: "bin:5555",
|
||||
idMapping: unmapped,
|
||||
expected: idtools.IDPair{UID: 1, GID: 5555},
|
||||
},
|
||||
{
|
||||
name: "GroupWithMap",
|
||||
chownStr: "0:unicorn",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 100000, GID: 101002},
|
||||
},
|
||||
{
|
||||
name: "UserOnlyWithMap",
|
||||
chownStr: "unicorn",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 101001, GID: 101002},
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
idPair, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping)
|
||||
require.NoError(t, err, "Failed to parse chown flag: %q", testcase.chownStr)
|
||||
assert.Equal(t, testcase.expected, idPair, "chown flag mapping failure")
|
||||
})
|
||||
}
|
||||
|
||||
// error tests
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
chownStr string
|
||||
idMapping *idtools.IDMappings
|
||||
descr string
|
||||
}{
|
||||
{
|
||||
name: "BadChownFlagFormat",
|
||||
chownStr: "bob:1:555",
|
||||
idMapping: unmapped,
|
||||
descr: "invalid chown string format: bob:1:555",
|
||||
},
|
||||
{
|
||||
name: "UserNoExist",
|
||||
chownStr: "bob",
|
||||
idMapping: unmapped,
|
||||
descr: "can't find uid for user bob: no such user: bob",
|
||||
},
|
||||
{
|
||||
name: "GroupNoExist",
|
||||
chownStr: "root:bob",
|
||||
idMapping: unmapped,
|
||||
descr: "can't find gid for group bob: no such group: bob",
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
_, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping)
|
||||
assert.EqualError(t, err, testcase.descr, "Expected error string doesn't match")
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,6 @@ package dockerfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
@ -13,7 +11,6 @@ import (
|
||||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/builder/remotecontext"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -171,130 +168,3 @@ func TestDeepCopyRunConfig(t *testing.T) {
|
||||
copy.Shell[0] = "sh"
|
||||
assert.Equal(t, fullMutableRunConfig(), runConfig)
|
||||
}
|
||||
|
||||
func TestChownFlagParsing(t *testing.T) {
|
||||
testFiles := map[string]string{
|
||||
"passwd": `root:x:0:0::/bin:/bin/false
|
||||
bin:x:1:1::/bin:/bin/false
|
||||
wwwwww:x:21:33::/bin:/bin/false
|
||||
unicorn:x:1001:1002::/bin:/bin/false
|
||||
`,
|
||||
"group": `root:x:0:
|
||||
bin:x:1:
|
||||
wwwwww:x:33:
|
||||
unicorn:x:1002:
|
||||
somegrp:x:5555:
|
||||
othergrp:x:6666:
|
||||
`,
|
||||
}
|
||||
// test mappings for validating use of maps
|
||||
idMaps := []idtools.IDMap{
|
||||
{
|
||||
ContainerID: 0,
|
||||
HostID: 100000,
|
||||
Size: 65536,
|
||||
},
|
||||
}
|
||||
remapped := idtools.NewIDMappingsFromMaps(idMaps, idMaps)
|
||||
unmapped := &idtools.IDMappings{}
|
||||
|
||||
contextDir, cleanup := createTestTempDir(t, "", "builder-chown-parse-test")
|
||||
defer cleanup()
|
||||
|
||||
if err := os.Mkdir(filepath.Join(contextDir, "etc"), 0755); err != nil {
|
||||
t.Fatalf("error creating test directory: %v", err)
|
||||
}
|
||||
|
||||
for filename, content := range testFiles {
|
||||
createTestTempFile(t, filepath.Join(contextDir, "etc"), filename, content, 0644)
|
||||
}
|
||||
|
||||
// positive tests
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
chownStr string
|
||||
idMapping *idtools.IDMappings
|
||||
expected idtools.IDPair
|
||||
}{
|
||||
{
|
||||
name: "UIDNoMap",
|
||||
chownStr: "1",
|
||||
idMapping: unmapped,
|
||||
expected: idtools.IDPair{UID: 1, GID: 1},
|
||||
},
|
||||
{
|
||||
name: "UIDGIDNoMap",
|
||||
chownStr: "0:1",
|
||||
idMapping: unmapped,
|
||||
expected: idtools.IDPair{UID: 0, GID: 1},
|
||||
},
|
||||
{
|
||||
name: "UIDWithMap",
|
||||
chownStr: "0",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 100000, GID: 100000},
|
||||
},
|
||||
{
|
||||
name: "UIDGIDWithMap",
|
||||
chownStr: "1:33",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 100001, GID: 100033},
|
||||
},
|
||||
{
|
||||
name: "UserNoMap",
|
||||
chownStr: "bin:5555",
|
||||
idMapping: unmapped,
|
||||
expected: idtools.IDPair{UID: 1, GID: 5555},
|
||||
},
|
||||
{
|
||||
name: "GroupWithMap",
|
||||
chownStr: "0:unicorn",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 100000, GID: 101002},
|
||||
},
|
||||
{
|
||||
name: "UserOnlyWithMap",
|
||||
chownStr: "unicorn",
|
||||
idMapping: remapped,
|
||||
expected: idtools.IDPair{UID: 101001, GID: 101002},
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
idPair, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping)
|
||||
require.NoError(t, err, "Failed to parse chown flag: %q", testcase.chownStr)
|
||||
assert.Equal(t, testcase.expected, idPair, "chown flag mapping failure")
|
||||
})
|
||||
}
|
||||
|
||||
// error tests
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
chownStr string
|
||||
idMapping *idtools.IDMappings
|
||||
descr string
|
||||
}{
|
||||
{
|
||||
name: "BadChownFlagFormat",
|
||||
chownStr: "bob:1:555",
|
||||
idMapping: unmapped,
|
||||
descr: "invalid chown string format: bob:1:555",
|
||||
},
|
||||
{
|
||||
name: "UserNoExist",
|
||||
chownStr: "bob",
|
||||
idMapping: unmapped,
|
||||
descr: "can't find uid for user bob: no such user: bob",
|
||||
},
|
||||
{
|
||||
name: "GroupNoExist",
|
||||
chownStr: "root:bob",
|
||||
idMapping: unmapped,
|
||||
descr: "can't find gid for group bob: no such group: bob",
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
_, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping)
|
||||
assert.EqualError(t, err, testcase.descr, "Expected error string doesn't match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package dockerfile
|
||||
|
||||
import "github.com/docker/docker/pkg/idtools"
|
||||
|
||||
func parseChownFlag(chown, ctrRootPath string, idMappings *idtools.IDMappings) (idtools.IDPair, error) {
|
||||
return idMappings.RootPair(), nil
|
||||
}
|
||||
@ -67,7 +67,7 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
|
||||
|
||||
flags.StringVar(&conf.MetricsAddress, "metrics-addr", "", "Set default address and port to serve the metrics api on")
|
||||
|
||||
flags.Var(opts.NewListOptsRef(&conf.NodeGenericResources, opts.ValidateSingleGenericResource), "node-generic-resource", "Advertise user-defined resource")
|
||||
flags.Var(opts.NewNamedListOptsRef("node-generic-resources", &conf.NodeGenericResources, opts.ValidateSingleGenericResource), "node-generic-resource", "Advertise user-defined resource")
|
||||
|
||||
flags.IntVar(&conf.NetworkControlPlaneMTU, "network-control-plane-mtu", config.DefaultNetworkMtu, "Network Control plane MTU")
|
||||
|
||||
|
||||
@ -61,6 +61,22 @@ func TestLoadDaemonCliConfigWithConflicts(t *testing.T) {
|
||||
testutil.ErrorContains(t, err, "as a flag and in the configuration file: labels")
|
||||
}
|
||||
|
||||
func TestLoadDaemonCliWithConflictingNodeGenericResources(t *testing.T) {
|
||||
tempFile := fs.NewFile(t, "config", fs.WithContent(`{"node-generic-resources": ["foo=bar", "bar=baz"]}`))
|
||||
defer tempFile.Remove()
|
||||
configFile := tempFile.Path()
|
||||
|
||||
opts := defaultOptions(configFile)
|
||||
flags := opts.flags
|
||||
|
||||
assert.NoError(t, flags.Set("config-file", configFile))
|
||||
assert.NoError(t, flags.Set("node-generic-resource", "r1=bar"))
|
||||
assert.NoError(t, flags.Set("node-generic-resource", "r2=baz"))
|
||||
|
||||
_, err := loadDaemonCliConfig(opts)
|
||||
testutil.ErrorContains(t, err, "as a flag and in the configuration file: node-generic-resources")
|
||||
}
|
||||
|
||||
func TestLoadDaemonCliWithConflictingLabels(t *testing.T) {
|
||||
opts := defaultOptions("")
|
||||
flags := opts.flags
|
||||
|
||||
@ -1,18 +1,5 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
_ "github.com/docker/docker/autogen/winresources/dockerd"
|
||||
)
|
||||
|
||||
//go:cgo_import_dynamic main.dummy CommandLineToArgvW%2 "shell32.dll"
|
||||
|
||||
var dummy uintptr
|
||||
|
||||
func init() {
|
||||
// Ensure that this import is not removed by the linker. This is used to
|
||||
// ensure that shell32.dll is loaded by the system loader, preventing
|
||||
// go#15286 from triggering on Nano Server TP5.
|
||||
atomic.LoadUintptr(&dummy)
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ FROM aarch64/debian:jessie
|
||||
RUN echo deb http://ftp.debian.org/debian jessie-backports main > /etc/apt/sources.list.d/backports.list
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev libseccomp-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-arm64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM aarch64/debian:stretch
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-dev libseccomp-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-arm64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM aarch64/ubuntu:trusty
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-arm64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM aarch64/ubuntu:xenial
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-dev libseccomp-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-arm64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config vim-common libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list.d
|
||||
RUN apt-get update && apt-get install -y -t wheezy-backports btrfs-tools --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM ubuntu:trusty
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM ubuntu:xenial
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config vim-common libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM ubuntu:yakkety
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config vim-common libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM ubuntu:zesty
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config vim-common libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
# GOARM is the ARM architecture version which is unrelated to the above Golang version
|
||||
ENV GOARM 6
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
|
||||
|
||||
@ -6,7 +6,7 @@ FROM armhf/ubuntu:trusty
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM armhf/ubuntu:xenial
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config vim-common libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM armhf/ubuntu:yakkety
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config vim-common libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM ppc64le/ubuntu:trusty
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-ppc64le.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM ppc64le/ubuntu:xenial
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libseccomp-dev libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-ppc64le.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM ppc64le/ubuntu:yakkety
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev pkg-config vim-common libseccomp-dev libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-ppc64le.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM s390x/ubuntu:xenial
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config libsystemd-dev vim-common --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-s390x.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ FROM s390x/ubuntu:yakkety
|
||||
|
||||
RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential cmake curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libseccomp-dev pkg-config libsystemd-dev vim-common --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-s390x.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ FROM amazonlinux:latest
|
||||
RUN yum groupinstall -y "Development Tools"
|
||||
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ RUN yum groupinstall -y "Development Tools"
|
||||
RUN yum -y swap -- remove systemd-container systemd-container-libs -- install systemd systemd-libs
|
||||
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ RUN dnf -y upgrade
|
||||
RUN dnf install -y @development-tools fedora-packager
|
||||
RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ RUN dnf -y upgrade
|
||||
RUN dnf install -y @development-tools fedora-packager
|
||||
RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ FROM opensuse:13.2
|
||||
RUN zypper --non-interactive install ca-certificates* curl gzip rpm-build
|
||||
RUN zypper --non-interactive install libbtrfs-devel device-mapper-devel glibc-static libselinux-devel pkg-config selinux-policy selinux-policy-devel systemd-devel tar git cmake vim systemd-rpm-macros
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ RUN yum install -y kernel-uek-devel-4.1.12-32.el6uek
|
||||
RUN yum groupinstall -y "Development Tools"
|
||||
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel pkgconfig selinux-policy selinux-policy-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ FROM oraclelinux:7
|
||||
RUN yum groupinstall -y "Development Tools"
|
||||
RUN yum install -y --enablerepo=ol7_optional_latest btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ FROM photon:1.0
|
||||
RUN tdnf install -y wget curl ca-certificates gzip make rpm-build sed gcc linux-api-headers glibc-devel binutils libseccomp elfutils
|
||||
RUN tdnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkg-config selinux-policy selinux-policy-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ RUN yum groupinstall --skip-broken -y "Development Tools"
|
||||
RUN yum -y swap -- remove systemd-container systemd-container-libs -- install systemd systemd-libs
|
||||
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fSL "https://golang.org/dl/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ RUN yum groupinstall -y "Development Tools"
|
||||
RUN yum -y swap -- remove systemd-container systemd-container-libs -- install systemd systemd-libs
|
||||
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-ppc64le.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ RUN dnf -y upgrade
|
||||
RUN dnf install -y @development-tools fedora-packager
|
||||
RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-ppc64le.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ RUN zypper addrepo -n ppc64le-updates -f https://download.opensuse.org/ports/upd
|
||||
RUN zypper --non-interactive install ca-certificates* curl gzip rpm-build
|
||||
RUN zypper --non-interactive install libbtrfs-devel device-mapper-devel glibc-static libselinux-devel pkg-config selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git cmake vim
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-ppc64le.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ FROM sinenomine/clefos-base-s390x
|
||||
RUN touch /var/lib/rpm/* && yum groupinstall -y "Development Tools"
|
||||
RUN touch /var/lib/rpm/* && yum install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git cmake vim-common
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-s390x.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ RUN zypper ar https://download.opensuse.org/ports/zsystems/tumbleweed/repo/oss/
|
||||
RUN zypper --non-interactive install ca-certificates* curl gzip rpm-build
|
||||
RUN zypper --non-interactive install libbtrfs-devel device-mapper-devel glibc-static libselinux-devel pkg-config selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git cmake vim systemd-rpm-macros
|
||||
|
||||
ENV GO_VERSION 1.9.2
|
||||
ENV GO_VERSION 1.9.4
|
||||
RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-s390x.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
|
||||
@ -22,6 +22,9 @@ func (daemon *Daemon) ContainerChanges(name string) ([]archive.Change, error) {
|
||||
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
if container.RWLayer == nil {
|
||||
return nil, errors.New("RWLayer of container " + name + " is unexpectedly nil")
|
||||
}
|
||||
c, err := container.RWLayer.Changes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -168,6 +168,7 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
|
||||
Ingress: IsIngressNetwork(&n),
|
||||
Labels: n.Spec.Annotations.Labels,
|
||||
}
|
||||
nr.Created, _ = gogotypes.TimestampFromProto(n.Meta.CreatedAt)
|
||||
|
||||
if n.Spec.GetNetwork() != "" {
|
||||
nr.ConfigFrom = networktypes.ConfigReference{
|
||||
|
||||
34
components/engine/daemon/cluster/convert/network_test.go
Normal file
34
components/engine/daemon/cluster/convert/network_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
swarmapi "github.com/docker/swarmkit/api"
|
||||
gogotypes "github.com/gogo/protobuf/types"
|
||||
)
|
||||
|
||||
func TestNetworkConvertBasicNetworkFromGRPCCreatedAt(t *testing.T) {
|
||||
expected, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jan 10, 2018 at 7:54pm (PST)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createdAt, err := gogotypes.TimestampProto(expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nw := swarmapi.Network{
|
||||
Meta: swarmapi.Meta{
|
||||
Version: swarmapi.Version{
|
||||
Index: 1,
|
||||
},
|
||||
CreatedAt: createdAt,
|
||||
},
|
||||
}
|
||||
|
||||
n := BasicNetworkFromGRPC(nw)
|
||||
if !n.Created.Equal(expected) {
|
||||
t.Fatalf("expected time %s; received %s", expected, n.Created)
|
||||
}
|
||||
}
|
||||
@ -146,6 +146,11 @@ func (e *executor) Configure(ctx context.Context, node *api.Node) error {
|
||||
attachments[na.Network.ID] = na.Addresses[0]
|
||||
}
|
||||
|
||||
if (ingressNA == nil) && (node.Attachment != nil) {
|
||||
ingressNA = node.Attachment
|
||||
attachments[ingressNA.Network.ID] = ingressNA.Addresses[0]
|
||||
}
|
||||
|
||||
if ingressNA == nil {
|
||||
e.backend.ReleaseIngress()
|
||||
return e.backend.GetAttachmentStore().ResetAttachments(attachments)
|
||||
|
||||
@ -19,6 +19,7 @@ import (
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/pkg/errors"
|
||||
@ -293,6 +294,14 @@ func (daemon *Daemon) verifyContainerSettings(platform string, hostConfig *conta
|
||||
return nil, errors.Errorf("can't create 'AutoRemove' container with restart policy")
|
||||
}
|
||||
|
||||
// Validate mounts; check if host directories still exist
|
||||
parser := volume.NewParser(platform)
|
||||
for _, cfg := range hostConfig.Mounts {
|
||||
if err := parser.ValidateMountConfig(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, extraHost := range hostConfig.ExtraHosts {
|
||||
if _, err := opts.ValidateExtraHost(extraHost); err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -1056,6 +1056,9 @@ func (daemon *Daemon) Shutdown() error {
|
||||
// Mount sets container.BaseFS
|
||||
// (is it not set coming in? why is it unset?)
|
||||
func (daemon *Daemon) Mount(container *container.Container) error {
|
||||
if container.RWLayer == nil {
|
||||
return errors.New("RWLayer of container " + container.ID + " is unexpectedly nil")
|
||||
}
|
||||
dir, err := container.RWLayer.Mount(container.GetMountLabel())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -1078,6 +1081,9 @@ func (daemon *Daemon) Mount(container *container.Container) error {
|
||||
|
||||
// Unmount unsets the container base filesystem
|
||||
func (daemon *Daemon) Unmount(container *container.Container) error {
|
||||
if container.RWLayer == nil {
|
||||
return errors.New("RWLayer of container " + container.ID + " is unexpectedly nil")
|
||||
}
|
||||
if err := container.RWLayer.Unmount(); err != nil {
|
||||
logrus.Errorf("Error unmounting container %s: %s", container.ID, err)
|
||||
return err
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -67,9 +68,29 @@ func (daemon *Daemon) cleanupMountsFromReaderByID(reader io.Reader, id string, u
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanupMounts umounts shm/mqueue mounts for old containers
|
||||
// cleanupMounts umounts used by container resources and the daemon root mount
|
||||
func (daemon *Daemon) cleanupMounts() error {
|
||||
return daemon.cleanupMountsByID("")
|
||||
if err := daemon.cleanupMountsByID(""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
infos, err := mount.GetMounts()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error reading mount table for cleanup")
|
||||
}
|
||||
|
||||
info := getMountInfo(infos, daemon.root)
|
||||
// `info.Root` here is the root mountpoint of the passed in path (`daemon.root`).
|
||||
// The ony cases that need to be cleaned up is when the daemon has performed a
|
||||
// `mount --bind /daemon/root /daemon/root && mount --make-shared /daemon/root`
|
||||
// This is only done when the daemon is started up and `/daemon/root` is not
|
||||
// already on a shared mountpoint.
|
||||
if !shouldUnmountRoot(daemon.root, info) {
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.WithField("mountpoint", daemon.root).Debug("unmounting daemon root")
|
||||
return mount.Unmount(daemon.root)
|
||||
}
|
||||
|
||||
func getCleanPatterns(id string) (regexps []*regexp.Regexp) {
|
||||
@ -91,3 +112,16 @@ func getCleanPatterns(id string) (regexps []*regexp.Regexp) {
|
||||
func getRealPath(path string) (string, error) {
|
||||
return fileutils.ReadSymlinkedDirectory(path)
|
||||
}
|
||||
|
||||
func shouldUnmountRoot(root string, info *mount.Info) bool {
|
||||
if info == nil {
|
||||
return false
|
||||
}
|
||||
if info.Mountpoint != root {
|
||||
return false
|
||||
}
|
||||
if !strings.HasSuffix(root, info.Root) {
|
||||
return false
|
||||
}
|
||||
return hasMountinfoOption(info.Optional, sharedPropagationOption)
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/oci"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -164,3 +165,66 @@ func TestValidateContainerIsolationLinux(t *testing.T) {
|
||||
_, err := d.verifyContainerSettings("linux", &containertypes.HostConfig{Isolation: containertypes.IsolationHyperV}, nil, false)
|
||||
assert.EqualError(t, err, "invalid isolation 'hyperv' on linux")
|
||||
}
|
||||
|
||||
func TestShouldUnmountRoot(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
root string
|
||||
info *mount.Info
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
desc: "root is at /",
|
||||
root: "/docker",
|
||||
info: &mount.Info{Root: "/docker", Mountpoint: "/docker"},
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
desc: "not a mountpoint",
|
||||
root: "/docker",
|
||||
info: nil,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
desc: "root is at in a submount from `/`",
|
||||
root: "/foo/docker",
|
||||
info: &mount.Info{Root: "/docker", Mountpoint: "/foo/docker"},
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
desc: "root is mounted in from a parent mount namespace same root dir", // dind is an example of this
|
||||
root: "/docker",
|
||||
info: &mount.Info{Root: "/docker/volumes/1234657/_data", Mountpoint: "/docker"},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
desc: "root is mounted in from a parent mount namespace different root dir",
|
||||
root: "/foo/bar",
|
||||
info: &mount.Info{Root: "/docker/volumes/1234657/_data", Mountpoint: "/foo/bar"},
|
||||
expect: false,
|
||||
},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
for _, options := range []struct {
|
||||
desc string
|
||||
Optional string
|
||||
expect bool
|
||||
}{
|
||||
{desc: "shared", Optional: "shared:", expect: true},
|
||||
{desc: "slave", Optional: "slave:", expect: false},
|
||||
{desc: "private", Optional: "private:", expect: false},
|
||||
} {
|
||||
t.Run(options.desc, func(t *testing.T) {
|
||||
expect := options.expect
|
||||
if expect {
|
||||
expect = test.expect
|
||||
}
|
||||
if test.info != nil {
|
||||
test.info.Optional = options.Optional
|
||||
}
|
||||
assert.Equal(t, expect, shouldUnmountRoot(test.root, test.info))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ import (
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/parsers/kernel"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
@ -1228,6 +1229,12 @@ func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ensureSharedOrSlave(config.Root); err != nil {
|
||||
if err := mount.MakeShared(config.Root); err != nil {
|
||||
logrus.WithError(err).WithField("dir", config.Root).Warn("Could not set daemon root propagation to shared, this is not generally critical but may cause some functionality to not work or fallback to less desirable behavior")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -124,6 +124,7 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
|
||||
container.SetRemovalError(e)
|
||||
return e
|
||||
}
|
||||
container.RWLayer = nil
|
||||
}
|
||||
|
||||
if err := system.EnsureRemoveAll(container.Root); err != nil {
|
||||
|
||||
@ -136,10 +136,6 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mountpk.MakePrivate(root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate the dir structure
|
||||
for _, p := range paths {
|
||||
if err := idtools.MkdirAllAndChown(path.Join(root, p), 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil {
|
||||
@ -610,7 +606,7 @@ func (a *Driver) Cleanup() error {
|
||||
logrus.Debugf("aufs error unmounting %s: %s", m, err)
|
||||
}
|
||||
}
|
||||
return mountpk.Unmount(a.root)
|
||||
return mountpk.RecursiveUnmount(a.root)
|
||||
}
|
||||
|
||||
func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err error) {
|
||||
|
||||
@ -29,7 +29,6 @@ import (
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/go-units"
|
||||
@ -77,10 +76,6 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt, userDiskQuota, err := parseOptions(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -167,7 +162,7 @@ func (d *Driver) Cleanup() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return mount.Unmount(d.home)
|
||||
return nil
|
||||
}
|
||||
|
||||
func free(p *C.char) {
|
||||
|
||||
@ -42,10 +42,6 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
DeviceSet: deviceSet,
|
||||
home: home,
|
||||
@ -127,7 +123,7 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) {
|
||||
func (d *Driver) Cleanup() error {
|
||||
err := d.DeviceSet.Shutdown(d.home)
|
||||
|
||||
if err2 := mount.Unmount(d.home); err == nil {
|
||||
if err2 := mount.RecursiveUnmount(d.home); err == nil {
|
||||
err = err2
|
||||
}
|
||||
|
||||
|
||||
@ -164,10 +164,6 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
home: home,
|
||||
uidMaps: uidMaps,
|
||||
@ -248,7 +244,7 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) {
|
||||
// is being shutdown. For now, we just have to unmount the bind mounted
|
||||
// we had created.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return mount.Unmount(d.home)
|
||||
return mount.RecursiveUnmount(d.home)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
|
||||
@ -200,10 +200,6 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
home: home,
|
||||
uidMaps: uidMaps,
|
||||
@ -334,7 +330,7 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) {
|
||||
// is being shutdown. For now, we just have to unmount the bind mounted
|
||||
// we had created.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return mount.Unmount(d.home)
|
||||
return mount.RecursiveUnmount(d.home)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
|
||||
@ -350,11 +350,17 @@ func makeBackingFsDev(home string) (string, error) {
|
||||
backingFsBlockDev := path.Join(home, "backingFsBlockDev")
|
||||
// Re-create just in case someone copied the home directory over to a new device
|
||||
unix.Unlink(backingFsBlockDev)
|
||||
if err := unix.Mknod(backingFsBlockDev, unix.S_IFBLK|0600, int(stat.Dev)); err != nil {
|
||||
err := unix.Mknod(backingFsBlockDev, unix.S_IFBLK|0600, int(stat.Dev))
|
||||
switch err {
|
||||
case nil:
|
||||
return backingFsBlockDev, nil
|
||||
|
||||
case unix.ENOSYS:
|
||||
return "", ErrQuotaNotSupported
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("Failed to mknod %s: %v", backingFsBlockDev, err)
|
||||
}
|
||||
|
||||
return backingFsBlockDev, nil
|
||||
}
|
||||
|
||||
func hasQuotaSupport(backingFsBlockDev string) (bool, error) {
|
||||
|
||||
@ -35,9 +35,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := setupDriverQuota(d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setupDriverQuota(d)
|
||||
|
||||
return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
|
||||
}
|
||||
|
||||
@ -2,20 +2,21 @@
|
||||
|
||||
package vfs
|
||||
|
||||
import "github.com/docker/docker/daemon/graphdriver/quota"
|
||||
import (
|
||||
"github.com/docker/docker/daemon/graphdriver/quota"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type driverQuota struct {
|
||||
quotaCtl *quota.Control
|
||||
}
|
||||
|
||||
func setupDriverQuota(driver *Driver) error {
|
||||
func setupDriverQuota(driver *Driver) {
|
||||
if quotaCtl, err := quota.NewControl(driver.home); err == nil {
|
||||
driver.quotaCtl = quotaCtl
|
||||
} else if err != quota.ErrQuotaNotSupported {
|
||||
return err
|
||||
logrus.Warnf("Unable to setup quota: %v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setupQuota(dir string, size uint64) error {
|
||||
|
||||
@ -108,9 +108,6 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri
|
||||
return nil, fmt.Errorf("Failed to create '%s': %v", base, err)
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(base); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Driver{
|
||||
dataset: rootDataset,
|
||||
options: options,
|
||||
@ -181,7 +178,8 @@ func (d *Driver) String() string {
|
||||
return "zfs"
|
||||
}
|
||||
|
||||
// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
|
||||
// Cleanup is called on daemon shutdown, it is a no-op for ZFS.
|
||||
// TODO(@cpuguy83): Walk layer tree and check mounts?
|
||||
func (d *Driver) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -80,6 +80,7 @@ func (p *cmdProbe) run(ctx context.Context, d *Daemon, cntr *container.Container
|
||||
execConfig.Tty = false
|
||||
execConfig.Privileged = false
|
||||
execConfig.User = cntr.Config.User
|
||||
execConfig.WorkingDir = cntr.Config.WorkingDir
|
||||
|
||||
linkedEnv, err := d.setupLinkedContainers(cntr)
|
||||
if err != nil {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -183,14 +184,24 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
|
||||
|
||||
contJSONBase.GraphDriver.Name = container.Driver
|
||||
|
||||
if container.RWLayer == nil {
|
||||
if container.Dead {
|
||||
return contJSONBase, nil
|
||||
}
|
||||
return nil, systemError{errors.New("RWLayer of container " + container.ID + " is unexpectedly nil")}
|
||||
}
|
||||
|
||||
graphDriverData, err := container.RWLayer.Metadata()
|
||||
// If container is marked as Dead, the container's graphdriver metadata
|
||||
// could have been removed, it will cause error if we try to get the metadata,
|
||||
// we can ignore the error if the container is dead.
|
||||
if err != nil && !container.Dead {
|
||||
return nil, systemError{err}
|
||||
if err != nil {
|
||||
if !container.Dead {
|
||||
return nil, systemError{err}
|
||||
}
|
||||
} else {
|
||||
contJSONBase.GraphDriver.Data = graphDriverData
|
||||
}
|
||||
contJSONBase.GraphDriver.Data = graphDriverData
|
||||
|
||||
return contJSONBase, nil
|
||||
}
|
||||
|
||||
33
components/engine/daemon/inspect_test.go
Normal file
33
components/engine/daemon/inspect_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package daemon // import "github.com/docker/docker/daemon"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/daemon/config"
|
||||
"github.com/docker/docker/daemon/exec"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetInspectData(t *testing.T) {
|
||||
c := &container.Container{
|
||||
ID: "inspect-me",
|
||||
HostConfig: &containertypes.HostConfig{},
|
||||
State: container.NewState(),
|
||||
ExecCommands: exec.NewStore(),
|
||||
}
|
||||
|
||||
d := &Daemon{
|
||||
linkIndex: newLinkIndex(),
|
||||
configStore: &config.Config{},
|
||||
}
|
||||
|
||||
_, err := d.getInspectData(c)
|
||||
assert.Error(t, err)
|
||||
|
||||
c.Dead = true
|
||||
_, err = d.getInspectData(c)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -95,6 +95,17 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// eventBatch holds the events that are batched for submission and the
|
||||
// associated data about it.
|
||||
//
|
||||
// Warning: this type is not threadsafe and must not be used
|
||||
// concurrently. This type is expected to be consumed in a single go
|
||||
// routine and never concurrently.
|
||||
type eventBatch struct {
|
||||
batch []wrappedEvent
|
||||
bytes int
|
||||
}
|
||||
|
||||
// New creates an awslogs logger using the configuration passed in on the
|
||||
// context. Supported context configuration variables are awslogs-region,
|
||||
// awslogs-group, awslogs-stream, awslogs-create-group, awslogs-multiline-pattern
|
||||
@ -389,32 +400,32 @@ var newTicker = func(freq time.Duration) *time.Ticker {
|
||||
// Logs, the processEvents method is called. If a multiline pattern is not
|
||||
// configured, log events are submitted to the processEvents method immediately.
|
||||
func (l *logStream) collectBatch() {
|
||||
timer := newTicker(batchPublishFrequency)
|
||||
var events []wrappedEvent
|
||||
ticker := newTicker(batchPublishFrequency)
|
||||
var eventBuffer []byte
|
||||
var eventBufferTimestamp int64
|
||||
var batch = newEventBatch()
|
||||
for {
|
||||
select {
|
||||
case t := <-timer.C:
|
||||
case t := <-ticker.C:
|
||||
// If event buffer is older than batch publish frequency flush the event buffer
|
||||
if eventBufferTimestamp > 0 && len(eventBuffer) > 0 {
|
||||
eventBufferAge := t.UnixNano()/int64(time.Millisecond) - eventBufferTimestamp
|
||||
eventBufferExpired := eventBufferAge > int64(batchPublishFrequency)/int64(time.Millisecond)
|
||||
eventBufferNegative := eventBufferAge < 0
|
||||
if eventBufferExpired || eventBufferNegative {
|
||||
events = l.processEvent(events, eventBuffer, eventBufferTimestamp)
|
||||
l.processEvent(batch, eventBuffer, eventBufferTimestamp)
|
||||
eventBuffer = eventBuffer[:0]
|
||||
}
|
||||
}
|
||||
l.publishBatch(events)
|
||||
events = events[:0]
|
||||
l.publishBatch(batch)
|
||||
batch.reset()
|
||||
case msg, more := <-l.messages:
|
||||
if !more {
|
||||
// Flush event buffer and release resources
|
||||
events = l.processEvent(events, eventBuffer, eventBufferTimestamp)
|
||||
l.processEvent(batch, eventBuffer, eventBufferTimestamp)
|
||||
eventBuffer = eventBuffer[:0]
|
||||
l.publishBatch(events)
|
||||
events = events[:0]
|
||||
l.publishBatch(batch)
|
||||
batch.reset()
|
||||
return
|
||||
}
|
||||
if eventBufferTimestamp == 0 {
|
||||
@ -425,7 +436,7 @@ func (l *logStream) collectBatch() {
|
||||
if l.multilinePattern.Match(unprocessedLine) || len(eventBuffer)+len(unprocessedLine) > maximumBytesPerEvent {
|
||||
// This is a new log event or we will exceed max bytes per event
|
||||
// so flush the current eventBuffer to events and reset timestamp
|
||||
events = l.processEvent(events, eventBuffer, eventBufferTimestamp)
|
||||
l.processEvent(batch, eventBuffer, eventBufferTimestamp)
|
||||
eventBufferTimestamp = msg.Timestamp.UnixNano() / int64(time.Millisecond)
|
||||
eventBuffer = eventBuffer[:0]
|
||||
}
|
||||
@ -434,7 +445,7 @@ func (l *logStream) collectBatch() {
|
||||
eventBuffer = append(eventBuffer, processedLine...)
|
||||
logger.PutMessage(msg)
|
||||
} else {
|
||||
events = l.processEvent(events, unprocessedLine, msg.Timestamp.UnixNano()/int64(time.Millisecond))
|
||||
l.processEvent(batch, unprocessedLine, msg.Timestamp.UnixNano()/int64(time.Millisecond))
|
||||
logger.PutMessage(msg)
|
||||
}
|
||||
}
|
||||
@ -450,8 +461,7 @@ func (l *logStream) collectBatch() {
|
||||
// bytes per event (defined in maximumBytesPerEvent). There is a fixed per-event
|
||||
// byte overhead (defined in perEventBytes) which is accounted for in split- and
|
||||
// batch-calculations.
|
||||
func (l *logStream) processEvent(events []wrappedEvent, unprocessedLine []byte, timestamp int64) []wrappedEvent {
|
||||
bytes := 0
|
||||
func (l *logStream) processEvent(batch *eventBatch, unprocessedLine []byte, timestamp int64) {
|
||||
for len(unprocessedLine) > 0 {
|
||||
// Split line length so it does not exceed the maximum
|
||||
lineBytes := len(unprocessedLine)
|
||||
@ -459,38 +469,33 @@ func (l *logStream) processEvent(events []wrappedEvent, unprocessedLine []byte,
|
||||
lineBytes = maximumBytesPerEvent
|
||||
}
|
||||
line := unprocessedLine[:lineBytes]
|
||||
unprocessedLine = unprocessedLine[lineBytes:]
|
||||
if (len(events) >= maximumLogEventsPerPut) || (bytes+lineBytes+perEventBytes > maximumBytesPerPut) {
|
||||
// Publish an existing batch if it's already over the maximum number of events or if adding this
|
||||
// event would push it over the maximum number of total bytes.
|
||||
l.publishBatch(events)
|
||||
events = events[:0]
|
||||
bytes = 0
|
||||
}
|
||||
events = append(events, wrappedEvent{
|
||||
|
||||
event := wrappedEvent{
|
||||
inputLogEvent: &cloudwatchlogs.InputLogEvent{
|
||||
Message: aws.String(string(line)),
|
||||
Timestamp: aws.Int64(timestamp),
|
||||
},
|
||||
insertOrder: len(events),
|
||||
})
|
||||
bytes += (lineBytes + perEventBytes)
|
||||
insertOrder: batch.count(),
|
||||
}
|
||||
|
||||
added := batch.add(event, lineBytes)
|
||||
if added {
|
||||
unprocessedLine = unprocessedLine[lineBytes:]
|
||||
} else {
|
||||
l.publishBatch(batch)
|
||||
batch.reset()
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// publishBatch calls PutLogEvents for a given set of InputLogEvents,
|
||||
// accounting for sequencing requirements (each request must reference the
|
||||
// sequence token returned by the previous request).
|
||||
func (l *logStream) publishBatch(events []wrappedEvent) {
|
||||
if len(events) == 0 {
|
||||
func (l *logStream) publishBatch(batch *eventBatch) {
|
||||
if batch.isEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
// events in a batch must be sorted by timestamp
|
||||
// see http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html
|
||||
sort.Sort(byTimestamp(events))
|
||||
cwEvents := unwrapEvents(events)
|
||||
cwEvents := unwrapEvents(batch.events())
|
||||
|
||||
nextSequenceToken, err := l.putLogEvents(cwEvents, l.sequenceToken)
|
||||
|
||||
@ -615,3 +620,70 @@ func unwrapEvents(events []wrappedEvent) []*cloudwatchlogs.InputLogEvent {
|
||||
}
|
||||
return cwEvents
|
||||
}
|
||||
|
||||
func newEventBatch() *eventBatch {
|
||||
return &eventBatch{
|
||||
batch: make([]wrappedEvent, 0),
|
||||
bytes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// events returns a slice of wrappedEvents sorted in order of their
|
||||
// timestamps and then by their insertion order (see `byTimestamp`).
|
||||
//
|
||||
// Warning: this method is not threadsafe and must not be used
|
||||
// concurrently.
|
||||
func (b *eventBatch) events() []wrappedEvent {
|
||||
sort.Sort(byTimestamp(b.batch))
|
||||
return b.batch
|
||||
}
|
||||
|
||||
// add adds an event to the batch of events accounting for the
|
||||
// necessary overhead for an event to be logged. An error will be
|
||||
// returned if the event cannot be added to the batch due to service
|
||||
// limits.
|
||||
//
|
||||
// Warning: this method is not threadsafe and must not be used
|
||||
// concurrently.
|
||||
func (b *eventBatch) add(event wrappedEvent, size int) bool {
|
||||
addBytes := size + perEventBytes
|
||||
|
||||
// verify we are still within service limits
|
||||
switch {
|
||||
case len(b.batch)+1 > maximumLogEventsPerPut:
|
||||
return false
|
||||
case b.bytes+addBytes > maximumBytesPerPut:
|
||||
return false
|
||||
}
|
||||
|
||||
b.bytes += addBytes
|
||||
b.batch = append(b.batch, event)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// count is the number of batched events. Warning: this method
|
||||
// is not threadsafe and must not be used concurrently.
|
||||
func (b *eventBatch) count() int {
|
||||
return len(b.batch)
|
||||
}
|
||||
|
||||
// size is the total number of bytes that the batch represents.
|
||||
//
|
||||
// Warning: this method is not threadsafe and must not be used
|
||||
// concurrently.
|
||||
func (b *eventBatch) size() int {
|
||||
return b.bytes
|
||||
}
|
||||
|
||||
func (b *eventBatch) isEmpty() bool {
|
||||
zeroEvents := b.count() == 0
|
||||
zeroSize := b.size() == 0
|
||||
return zeroEvents && zeroSize
|
||||
}
|
||||
|
||||
// reset prepares the batch for reuse.
|
||||
func (b *eventBatch) reset() {
|
||||
b.bytes = 0
|
||||
b.batch = b.batch[:0]
|
||||
}
|
||||
|
||||
@ -49,6 +49,15 @@ func (l *logStream) logGenerator(lineCount int, multilineCount int) {
|
||||
}
|
||||
}
|
||||
|
||||
func testEventBatch(events []wrappedEvent) *eventBatch {
|
||||
batch := newEventBatch()
|
||||
for _, event := range events {
|
||||
eventlen := len([]byte(*event.inputLogEvent.Message))
|
||||
batch.add(event, eventlen)
|
||||
}
|
||||
return batch
|
||||
}
|
||||
|
||||
func TestNewAWSLogsClientUserAgentHandler(t *testing.T) {
|
||||
info := logger.Info{
|
||||
Config: map[string]string{
|
||||
@ -212,7 +221,7 @@ func TestPublishBatchSuccess(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stream.publishBatch(events)
|
||||
stream.publishBatch(testEventBatch(events))
|
||||
if stream.sequenceToken == nil {
|
||||
t.Fatal("Expected non-nil sequenceToken")
|
||||
}
|
||||
@ -257,7 +266,7 @@ func TestPublishBatchError(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stream.publishBatch(events)
|
||||
stream.publishBatch(testEventBatch(events))
|
||||
if stream.sequenceToken == nil {
|
||||
t.Fatal("Expected non-nil sequenceToken")
|
||||
}
|
||||
@ -291,7 +300,7 @@ func TestPublishBatchInvalidSeqSuccess(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stream.publishBatch(events)
|
||||
stream.publishBatch(testEventBatch(events))
|
||||
if stream.sequenceToken == nil {
|
||||
t.Fatal("Expected non-nil sequenceToken")
|
||||
}
|
||||
@ -354,7 +363,7 @@ func TestPublishBatchAlreadyAccepted(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stream.publishBatch(events)
|
||||
stream.publishBatch(testEventBatch(events))
|
||||
if stream.sequenceToken == nil {
|
||||
t.Fatal("Expected non-nil sequenceToken")
|
||||
}
|
||||
@ -859,7 +868,8 @@ func TestCollectBatchMaxEvents(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectBatchMaxTotalBytes(t *testing.T) {
|
||||
mockClient := newMockClientBuffered(1)
|
||||
expectedPuts := 2
|
||||
mockClient := newMockClientBuffered(expectedPuts)
|
||||
stream := &logStream{
|
||||
client: mockClient,
|
||||
logGroupName: groupName,
|
||||
@ -867,11 +877,14 @@ func TestCollectBatchMaxTotalBytes(t *testing.T) {
|
||||
sequenceToken: aws.String(sequenceToken),
|
||||
messages: make(chan *logger.Message),
|
||||
}
|
||||
mockClient.putLogEventsResult <- &putLogEventsResult{
|
||||
successResult: &cloudwatchlogs.PutLogEventsOutput{
|
||||
NextSequenceToken: aws.String(nextSequenceToken),
|
||||
},
|
||||
for i := 0; i < expectedPuts; i++ {
|
||||
mockClient.putLogEventsResult <- &putLogEventsResult{
|
||||
successResult: &cloudwatchlogs.PutLogEventsOutput{
|
||||
NextSequenceToken: aws.String(nextSequenceToken),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var ticks = make(chan time.Time)
|
||||
newTicker = func(_ time.Duration) *time.Ticker {
|
||||
return &time.Ticker{
|
||||
@ -881,32 +894,57 @@ func TestCollectBatchMaxTotalBytes(t *testing.T) {
|
||||
|
||||
go stream.collectBatch()
|
||||
|
||||
longline := strings.Repeat("A", maximumBytesPerPut)
|
||||
numPayloads := maximumBytesPerPut / (maximumBytesPerEvent + perEventBytes)
|
||||
// maxline is the maximum line that could be submitted after
|
||||
// accounting for its overhead.
|
||||
maxline := strings.Repeat("A", maximumBytesPerPut-(perEventBytes*numPayloads))
|
||||
// This will be split and batched up to the `maximumBytesPerPut'
|
||||
// (+/- `maximumBytesPerEvent'). This /should/ be aligned, but
|
||||
// should also tolerate an offset within that range.
|
||||
stream.Log(&logger.Message{
|
||||
Line: []byte(longline + "B"),
|
||||
Line: []byte(maxline[:len(maxline)/2]),
|
||||
Timestamp: time.Time{},
|
||||
})
|
||||
stream.Log(&logger.Message{
|
||||
Line: []byte(maxline[len(maxline)/2:]),
|
||||
Timestamp: time.Time{},
|
||||
})
|
||||
stream.Log(&logger.Message{
|
||||
Line: []byte("B"),
|
||||
Timestamp: time.Time{},
|
||||
})
|
||||
|
||||
// no ticks
|
||||
// no ticks, guarantee batch by size (and chan close)
|
||||
stream.Close()
|
||||
|
||||
argument := <-mockClient.putLogEventsArgument
|
||||
if argument == nil {
|
||||
t.Fatal("Expected non-nil PutLogEventsInput")
|
||||
}
|
||||
bytes := 0
|
||||
|
||||
// Should total to the maximum allowed bytes.
|
||||
eventBytes := 0
|
||||
for _, event := range argument.LogEvents {
|
||||
bytes += len(*event.Message)
|
||||
eventBytes += len(*event.Message)
|
||||
}
|
||||
if bytes > maximumBytesPerPut {
|
||||
t.Errorf("Expected <= %d bytes but was %d", maximumBytesPerPut, bytes)
|
||||
eventsOverhead := len(argument.LogEvents) * perEventBytes
|
||||
payloadTotal := eventBytes + eventsOverhead
|
||||
// lowestMaxBatch allows the payload to be offset if the messages
|
||||
// don't lend themselves to align with the maximum event size.
|
||||
lowestMaxBatch := maximumBytesPerPut - maximumBytesPerEvent
|
||||
|
||||
if payloadTotal > maximumBytesPerPut {
|
||||
t.Errorf("Expected <= %d bytes but was %d", maximumBytesPerPut, payloadTotal)
|
||||
}
|
||||
if payloadTotal < lowestMaxBatch {
|
||||
t.Errorf("Batch to be no less than %d but was %d", lowestMaxBatch, payloadTotal)
|
||||
}
|
||||
|
||||
argument = <-mockClient.putLogEventsArgument
|
||||
if len(argument.LogEvents) != 1 {
|
||||
t.Errorf("Expected LogEvents to contain 1 elements, but contains %d", len(argument.LogEvents))
|
||||
}
|
||||
message := *argument.LogEvents[0].Message
|
||||
message := *argument.LogEvents[len(argument.LogEvents)-1].Message
|
||||
if message[len(message)-1:] != "B" {
|
||||
t.Errorf("Expected message to be %s but was %s", "B", message[len(message)-1:])
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
package awslogs
|
||||
|
||||
import "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
|
||||
)
|
||||
|
||||
type mockcwlogsclient struct {
|
||||
createLogGroupArgument chan *cloudwatchlogs.CreateLogGroupInput
|
||||
@ -67,7 +71,30 @@ func (m *mockcwlogsclient) PutLogEvents(input *cloudwatchlogs.PutLogEventsInput)
|
||||
LogGroupName: input.LogGroupName,
|
||||
LogStreamName: input.LogStreamName,
|
||||
}
|
||||
|
||||
// Intended mock output
|
||||
output := <-m.putLogEventsResult
|
||||
|
||||
// Checked enforced limits in mock
|
||||
totalBytes := 0
|
||||
for _, evt := range events {
|
||||
if evt.Message == nil {
|
||||
continue
|
||||
}
|
||||
eventBytes := len([]byte(*evt.Message))
|
||||
if eventBytes > maximumBytesPerEvent {
|
||||
// exceeded per event message size limits
|
||||
return nil, fmt.Errorf("maximum bytes per event exceeded: Event too large %d, max allowed: %d", eventBytes, maximumBytesPerEvent)
|
||||
}
|
||||
// total event bytes including overhead
|
||||
totalBytes += eventBytes + perEventBytes
|
||||
}
|
||||
|
||||
if totalBytes > maximumBytesPerPut {
|
||||
// exceeded per put maximum size limit
|
||||
return nil, fmt.Errorf("maximum bytes per put exceeded: Upload too large %d, max allowed: %d", totalBytes, maximumBytesPerPut)
|
||||
}
|
||||
|
||||
return output.successResult, output.errorResult
|
||||
}
|
||||
|
||||
|
||||
@ -215,6 +215,7 @@ func New(info logger.Info) (logger.Logger, error) {
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
|
||||
@ -4,12 +4,14 @@ import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
"github.com/gotestyourself/gotestyourself/env"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -82,6 +84,36 @@ func TestNewMissedToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWithProxy(t *testing.T) {
|
||||
proxy := "http://proxy.testing:8888"
|
||||
reset := env.Patch(t, "HTTP_PROXY", proxy)
|
||||
defer reset()
|
||||
|
||||
// must not be localhost
|
||||
splunkURL := "http://example.com:12345"
|
||||
logger, err := New(logger.Info{
|
||||
Config: map[string]string{
|
||||
splunkURLKey: splunkURL,
|
||||
splunkTokenKey: "token",
|
||||
splunkVerifyConnectionKey: "false",
|
||||
},
|
||||
ContainerID: "containeriid",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
splunkLogger := logger.(*splunkLoggerInline)
|
||||
|
||||
proxyFunc := splunkLogger.transport.Proxy
|
||||
require.NotNil(t, proxyFunc)
|
||||
|
||||
req, err := http.NewRequest("GET", splunkURL, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyURL, err := proxyFunc(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxyURL)
|
||||
require.Equal(t, proxy, proxyURL.String())
|
||||
}
|
||||
|
||||
// Test default settings
|
||||
func TestDefault(t *testing.T) {
|
||||
hec := NewHTTPEventCollectorMock(t)
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@ -419,52 +420,46 @@ func getSourceMount(source string) (string, string, error) {
|
||||
return "", "", fmt.Errorf("Could not find source mount of %s", source)
|
||||
}
|
||||
|
||||
const (
|
||||
sharedPropagationOption = "shared:"
|
||||
slavePropagationOption = "master:"
|
||||
)
|
||||
|
||||
// hasMountinfoOption checks if any of the passed any of the given option values
|
||||
// are set in the passed in option string.
|
||||
func hasMountinfoOption(opts string, vals ...string) bool {
|
||||
for _, opt := range strings.Split(opts, " ") {
|
||||
for _, val := range vals {
|
||||
if strings.HasPrefix(opt, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure mount point on which path is mounted, is shared.
|
||||
func ensureShared(path string) error {
|
||||
sharedMount := false
|
||||
|
||||
sourceMount, optionalOpts, err := getSourceMount(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure source mount point is shared.
|
||||
optsSplit := strings.Split(optionalOpts, " ")
|
||||
for _, opt := range optsSplit {
|
||||
if strings.HasPrefix(opt, "shared:") {
|
||||
sharedMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !sharedMount {
|
||||
return fmt.Errorf("path %s is mounted on %s but it is not a shared mount", path, sourceMount)
|
||||
if !hasMountinfoOption(optionalOpts, sharedPropagationOption) {
|
||||
return errors.Errorf("path %s is mounted on %s but it is not a shared mount", path, sourceMount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure mount point on which path is mounted, is either shared or slave.
|
||||
func ensureSharedOrSlave(path string) error {
|
||||
sharedMount := false
|
||||
slaveMount := false
|
||||
|
||||
sourceMount, optionalOpts, err := getSourceMount(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure source mount point is shared.
|
||||
optsSplit := strings.Split(optionalOpts, " ")
|
||||
for _, opt := range optsSplit {
|
||||
if strings.HasPrefix(opt, "shared:") {
|
||||
sharedMount = true
|
||||
break
|
||||
} else if strings.HasPrefix(opt, "master:") {
|
||||
slaveMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !sharedMount && !slaveMount {
|
||||
return fmt.Errorf("path %s is mounted on %s but it is not a shared or slave mount", path, sourceMount)
|
||||
if !hasMountinfoOption(optionalOpts, sharedPropagationOption, slavePropagationOption) {
|
||||
return errors.Errorf("path %s is mounted on %s but it is not a shared or slave mount", path, sourceMount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -604,7 +599,8 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
|
||||
//
|
||||
// For private volumes any root propagation value should work.
|
||||
pFlag := mountPropagationMap[m.Propagation]
|
||||
if pFlag == mount.SHARED || pFlag == mount.RSHARED {
|
||||
switch pFlag {
|
||||
case mount.SHARED, mount.RSHARED:
|
||||
if err := ensureShared(m.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -612,13 +608,34 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED {
|
||||
s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.SHARED]
|
||||
}
|
||||
} else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
|
||||
case mount.SLAVE, mount.RSLAVE:
|
||||
var fallback bool
|
||||
if err := ensureSharedOrSlave(m.Source); err != nil {
|
||||
return err
|
||||
// For backwards compatability purposes, treat mounts from the daemon root
|
||||
// as special since we automatically add rslave propagation to these mounts
|
||||
// when the user did not set anything, so we should fallback to the old
|
||||
// behavior which is to use private propagation which is normally the
|
||||
// default.
|
||||
if !strings.HasPrefix(m.Source, daemon.root) && !strings.HasPrefix(daemon.root, m.Source) {
|
||||
return err
|
||||
}
|
||||
|
||||
cm, ok := c.MountPoints[m.Destination]
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
if cm.Spec.BindOptions != nil && cm.Spec.BindOptions.Propagation != "" {
|
||||
// This means the user explicitly set a propagation, do not fallback in that case.
|
||||
return err
|
||||
}
|
||||
fallback = true
|
||||
logrus.WithField("container", c.ID).WithField("source", m.Source).Warn("Falling back to default propagation for bind source in daemon root")
|
||||
}
|
||||
rootpg := mountPropagationMap[s.Linux.RootfsPropagation]
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
|
||||
s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.RSLAVE]
|
||||
if !fallback {
|
||||
rootpg := mountPropagationMap[s.Linux.RootfsPropagation]
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
|
||||
s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.RSLAVE]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@ -145,6 +146,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
||||
// Reverse order, expecting parent most first
|
||||
s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
|
||||
}
|
||||
if c.RWLayer == nil {
|
||||
return nil, errors.New("RWLayer of container " + c.ID + " is unexpectedly nil")
|
||||
}
|
||||
m, err := c.RWLayer.Metadata()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer metadata - %s", err)
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/volume"
|
||||
@ -145,6 +146,13 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needsSlavePropagation, err := daemon.validateBindDaemonRoot(bind.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needsSlavePropagation {
|
||||
bind.Propagation = mount.PropagationRSlave
|
||||
}
|
||||
|
||||
// #10618
|
||||
_, tmpfsExists := hostConfig.Tmpfs[bind.Destination]
|
||||
@ -177,6 +185,13 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
||||
if err != nil {
|
||||
return validationError{err}
|
||||
}
|
||||
needsSlavePropagation, err := daemon.validateBindDaemonRoot(mp.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needsSlavePropagation {
|
||||
mp.Propagation = mount.PropagationRSlave
|
||||
}
|
||||
|
||||
if binds[mp.Destination] {
|
||||
return duplicateMountPointError(cfg.Target)
|
||||
|
||||
35
components/engine/daemon/volumes_linux.go
Normal file
35
components/engine/daemon/volumes_linux.go
Normal file
@ -0,0 +1,35 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// validateBindDaemonRoot ensures that if a given mountpoint's source is within
|
||||
// the daemon root path, that the propagation is setup to prevent a container
|
||||
// from holding private refereneces to a mount within the daemon root, which
|
||||
// can cause issues when the daemon attempts to remove the mountpoint.
|
||||
func (daemon *Daemon) validateBindDaemonRoot(m mount.Mount) (bool, error) {
|
||||
if m.Type != mount.TypeBind {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check if the source is within the daemon root, or if the daemon root is within the source
|
||||
if !strings.HasPrefix(m.Source, daemon.root) && !strings.HasPrefix(daemon.root, m.Source) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if m.BindOptions == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
switch m.BindOptions.Propagation {
|
||||
case mount.PropagationRSlave, mount.PropagationRShared, "":
|
||||
return m.BindOptions.Propagation == "", nil
|
||||
default:
|
||||
}
|
||||
|
||||
return false, validationError{errors.Errorf(`invalid mount config: must use either propagation mode "rslave" or "rshared" when mount source is within the daemon root, daemon root: %q, bind mount source: %q, propagation: %q`, daemon.root, m.Source, m.BindOptions.Propagation)}
|
||||
}
|
||||
56
components/engine/daemon/volumes_linux_test.go
Normal file
56
components/engine/daemon/volumes_linux_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
func TestBindDaemonRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := &Daemon{root: "/a/b/c/daemon"}
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
opts *mount.BindOptions
|
||||
needsProp bool
|
||||
err bool
|
||||
}{
|
||||
{desc: "nil propagation settings", opts: nil, needsProp: true, err: false},
|
||||
{desc: "empty propagation settings", opts: &mount.BindOptions{}, needsProp: true, err: false},
|
||||
{desc: "private propagation", opts: &mount.BindOptions{Propagation: mount.PropagationPrivate}, err: true},
|
||||
{desc: "rprivate propagation", opts: &mount.BindOptions{Propagation: mount.PropagationRPrivate}, err: true},
|
||||
{desc: "slave propagation", opts: &mount.BindOptions{Propagation: mount.PropagationSlave}, err: true},
|
||||
{desc: "rslave propagation", opts: &mount.BindOptions{Propagation: mount.PropagationRSlave}, err: false, needsProp: false},
|
||||
{desc: "shared propagation", opts: &mount.BindOptions{Propagation: mount.PropagationShared}, err: true},
|
||||
{desc: "rshared propagation", opts: &mount.BindOptions{Propagation: mount.PropagationRSlave}, err: false, needsProp: false},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
test := test
|
||||
for desc, source := range map[string]string{
|
||||
"source is root": d.root,
|
||||
"source is subpath": filepath.Join(d.root, "a", "b"),
|
||||
"source is parent": filepath.Dir(d.root),
|
||||
"source is /": "/",
|
||||
} {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
mount := mount.Mount{
|
||||
Type: mount.TypeBind,
|
||||
Source: source,
|
||||
BindOptions: test.opts,
|
||||
}
|
||||
needsProp, err := d.validateBindDaemonRoot(mount)
|
||||
if (err != nil) != test.err {
|
||||
t.Fatalf("expected err=%v, got: %v", test.err, err)
|
||||
}
|
||||
if test.err {
|
||||
return
|
||||
}
|
||||
if test.needsProp != needsProp {
|
||||
t.Fatalf("expected needsProp=%v, got: %v", test.needsProp, needsProp)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ package daemon
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/volume"
|
||||
@ -46,3 +47,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
|
||||
func setBindModeIfNull(bind *volume.MountPoint) {
|
||||
return
|
||||
}
|
||||
|
||||
func (daemon *Daemon) validateBindDaemonRoot(m mount.Mount) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
TOMLV_COMMIT=9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
|
||||
|
||||
# When updating RUNC_COMMIT, also update runc in vendor.conf accordingly
|
||||
RUNC_COMMIT=b2567b37d7b75eb4cf325b77297b140ea686ce8f
|
||||
CONTAINERD_COMMIT=89623f28b87a6004d4b785663257362d1658a729 # v1.0.0
|
||||
RUNC_COMMIT=9f9c96235cc97674e935002fc3d78361b696a69e
|
||||
CONTAINERD_COMMIT=9b55aab90508bd389d7654c4baf173a981477d55 # v1.0.1
|
||||
TINI_COMMIT=949e6facb77383876aeff8a6944dde66b3089574
|
||||
LIBNETWORK_COMMIT=7b2b1feb1de4817d522cc372af149ff48d25028e
|
||||
VNDR_COMMIT=a6e196d8b4b0cbbdc29aebdb20c59ac6926bb384
|
||||
|
||||
@ -36,7 +36,6 @@ import (
|
||||
"github.com/docker/libnetwork/types"
|
||||
"github.com/go-check/check"
|
||||
"github.com/gotestyourself/gotestyourself/icmd"
|
||||
libcontainerUser "github.com/opencontainers/runc/libcontainer/user"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@ -751,7 +750,7 @@ func (s *DockerSuite) TestRunUserByIDBig(c *check.C) {
|
||||
if err == nil {
|
||||
c.Fatal("No error, but must be.", out)
|
||||
}
|
||||
if !strings.Contains(strings.ToUpper(out), strings.ToUpper(libcontainerUser.ErrRange.Error())) {
|
||||
if !strings.Contains(strings.ToLower(out), "uids and gids must be in range") {
|
||||
c.Fatalf("expected error about uids range, got %s", out)
|
||||
}
|
||||
}
|
||||
@ -764,7 +763,7 @@ func (s *DockerSuite) TestRunUserByIDNegative(c *check.C) {
|
||||
if err == nil {
|
||||
c.Fatal("No error, but must be.", out)
|
||||
}
|
||||
if !strings.Contains(strings.ToUpper(out), strings.ToUpper(libcontainerUser.ErrRange.Error())) {
|
||||
if !strings.Contains(strings.ToLower(out), "uids and gids must be in range") {
|
||||
c.Fatalf("expected error about uids range, got %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
61
components/engine/integration/container/health_test.go
Normal file
61
components/engine/integration/container/health_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/integration/util/request"
|
||||
"github.com/gotestyourself/gotestyourself/poll"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestHealthCheckWorkdir verifies that health-checks inherit the containers'
|
||||
// working-dir.
|
||||
func TestHealthCheckWorkdir(t *testing.T) {
|
||||
defer setupTest(t)()
|
||||
ctx := context.Background()
|
||||
client := request.NewAPIClient(t)
|
||||
|
||||
c, err := client.ContainerCreate(ctx,
|
||||
&container.Config{
|
||||
Image: "busybox",
|
||||
Tty: true,
|
||||
WorkingDir: "/foo",
|
||||
Cmd: strslice.StrSlice([]string{"top"}),
|
||||
Healthcheck: &container.HealthConfig{
|
||||
Test: []string{"CMD-SHELL", "if [ \"$PWD\" = \"/foo\" ]; then exit 0; else exit 1; fi;"},
|
||||
Interval: 50 * time.Millisecond,
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
&container.HostConfig{},
|
||||
&network.NetworkingConfig{},
|
||||
"healthtest",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, pollForHealthStatus(ctx, client, c.ID, types.Healthy), poll.WithDelay(100*time.Millisecond))
|
||||
}
|
||||
|
||||
func pollForHealthStatus(ctx context.Context, client client.APIClient, containerID string, healthStatus string) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
inspect, err := client.ContainerInspect(ctx, containerID)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return poll.Error(err)
|
||||
case inspect.State.Health.Status == healthStatus:
|
||||
return poll.Success()
|
||||
default:
|
||||
return poll.Continue("waiting for container to become %s", healthStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
139
components/engine/integration/container/mounts_linux_test.go
Normal file
139
components/engine/integration/container/mounts_linux_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/integration/util/request"
|
||||
)
|
||||
|
||||
func TestMountDaemonRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := request.NewAPIClient(t)
|
||||
ctx := context.Background()
|
||||
info, err := client.Info(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
propagation mount.Propagation
|
||||
expected mount.Propagation
|
||||
}{
|
||||
{
|
||||
desc: "default",
|
||||
propagation: "",
|
||||
expected: mount.PropagationRSlave,
|
||||
},
|
||||
{
|
||||
desc: "private",
|
||||
propagation: mount.PropagationPrivate,
|
||||
},
|
||||
{
|
||||
desc: "rprivate",
|
||||
propagation: mount.PropagationRPrivate,
|
||||
},
|
||||
{
|
||||
desc: "slave",
|
||||
propagation: mount.PropagationSlave,
|
||||
},
|
||||
{
|
||||
desc: "rslave",
|
||||
propagation: mount.PropagationRSlave,
|
||||
expected: mount.PropagationRSlave,
|
||||
},
|
||||
{
|
||||
desc: "shared",
|
||||
propagation: mount.PropagationShared,
|
||||
},
|
||||
{
|
||||
desc: "rshared",
|
||||
propagation: mount.PropagationRShared,
|
||||
expected: mount.PropagationRShared,
|
||||
},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
test := test
|
||||
t.Parallel()
|
||||
|
||||
propagationSpec := fmt.Sprintf(":%s", test.propagation)
|
||||
if test.propagation == "" {
|
||||
propagationSpec = ""
|
||||
}
|
||||
bindSpecRoot := info.DockerRootDir + ":" + "/foo" + propagationSpec
|
||||
bindSpecSub := filepath.Join(info.DockerRootDir, "containers") + ":/foo" + propagationSpec
|
||||
|
||||
for name, hc := range map[string]*container.HostConfig{
|
||||
"bind root": {Binds: []string{bindSpecRoot}},
|
||||
"bind subpath": {Binds: []string{bindSpecSub}},
|
||||
"mount root": {
|
||||
Mounts: []mount.Mount{
|
||||
{
|
||||
Type: mount.TypeBind,
|
||||
Source: info.DockerRootDir,
|
||||
Target: "/foo",
|
||||
BindOptions: &mount.BindOptions{Propagation: test.propagation},
|
||||
},
|
||||
},
|
||||
},
|
||||
"mount subpath": {
|
||||
Mounts: []mount.Mount{
|
||||
{
|
||||
Type: mount.TypeBind,
|
||||
Source: filepath.Join(info.DockerRootDir, "containers"),
|
||||
Target: "/foo",
|
||||
BindOptions: &mount.BindOptions{Propagation: test.propagation},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
hc := hc
|
||||
t.Parallel()
|
||||
|
||||
c, err := client.ContainerCreate(ctx, &container.Config{
|
||||
Image: "busybox",
|
||||
Cmd: []string{"true"},
|
||||
}, hc, nil, "")
|
||||
|
||||
if err != nil {
|
||||
if test.expected != "" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// expected an error, so this is ok and should not continue
|
||||
return
|
||||
}
|
||||
if test.expected == "" {
|
||||
t.Fatal("expected create to fail")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
inspect, err := client.ContainerInspect(ctx, c.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(inspect.Mounts) != 1 {
|
||||
t.Fatalf("unexpected number of mounts: %+v", inspect.Mounts)
|
||||
}
|
||||
|
||||
m := inspect.Mounts[0]
|
||||
if m.Propagation != test.expected {
|
||||
t.Fatalf("got unexpected propagation mode, expected %q, got: %v", test.expected, m.Propagation)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
221
components/engine/integration/network/inspect_test.go
Normal file
221
components/engine/integration/network/inspect_test.go
Normal file
@ -0,0 +1,221 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/integration-cli/daemon"
|
||||
"github.com/docker/docker/integration-cli/request"
|
||||
"github.com/gotestyourself/gotestyourself/poll"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const defaultSwarmPort = 2477
|
||||
const dockerdBinary = "dockerd"
|
||||
|
||||
func TestInspectNetwork(t *testing.T) {
|
||||
defer setupTest(t)()
|
||||
d := newSwarm(t)
|
||||
defer d.Stop(t)
|
||||
client, err := request.NewClientForHost(d.Sock())
|
||||
require.NoError(t, err)
|
||||
|
||||
overlayName := "overlay1"
|
||||
networkCreate := types.NetworkCreate{
|
||||
CheckDuplicate: true,
|
||||
Driver: "overlay",
|
||||
}
|
||||
|
||||
netResp, err := client.NetworkCreate(context.Background(), overlayName, networkCreate)
|
||||
require.NoError(t, err)
|
||||
overlayID := netResp.ID
|
||||
|
||||
var instances uint64 = 4
|
||||
serviceName := "TestService"
|
||||
serviceSpec := swarmServiceSpec(serviceName, instances)
|
||||
serviceSpec.TaskTemplate.Networks = append(serviceSpec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: overlayName})
|
||||
|
||||
serviceResp, err := client.ServiceCreate(context.Background(), serviceSpec, types.ServiceCreateOptions{
|
||||
QueryRegistry: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pollSettings := func(config *poll.Settings) {
|
||||
if runtime.GOARCH == "arm" {
|
||||
config.Timeout = 30 * time.Second
|
||||
config.Delay = 100 * time.Millisecond
|
||||
}
|
||||
}
|
||||
|
||||
serviceID := serviceResp.ID
|
||||
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances), pollSettings)
|
||||
|
||||
_, _, err = client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test inspect verbose with full NetworkID
|
||||
networkVerbose, err := client.NetworkInspect(context.Background(), overlayID, types.NetworkInspectOptions{
|
||||
Verbose: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, validNetworkVerbose(networkVerbose, serviceName, instances))
|
||||
|
||||
// Test inspect verbose with partial NetworkID
|
||||
networkVerbose, err = client.NetworkInspect(context.Background(), overlayID[0:11], types.NetworkInspectOptions{
|
||||
Verbose: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, validNetworkVerbose(networkVerbose, serviceName, instances))
|
||||
|
||||
// Test inspect verbose with Network name and swarm scope
|
||||
networkVerbose, err = client.NetworkInspect(context.Background(), overlayName, types.NetworkInspectOptions{
|
||||
Verbose: true,
|
||||
Scope: "swarm",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, validNetworkVerbose(networkVerbose, serviceName, instances))
|
||||
|
||||
err = client.ServiceRemove(context.Background(), serviceID)
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, serviceIsRemoved(client, serviceID), pollSettings)
|
||||
poll.WaitOn(t, noTasks(client), pollSettings)
|
||||
|
||||
serviceResp, err = client.ServiceCreate(context.Background(), serviceSpec, types.ServiceCreateOptions{
|
||||
QueryRegistry: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
serviceID2 := serviceResp.ID
|
||||
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID2, instances), pollSettings)
|
||||
|
||||
err = client.ServiceRemove(context.Background(), serviceID2)
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, serviceIsRemoved(client, serviceID2), pollSettings)
|
||||
poll.WaitOn(t, noTasks(client), pollSettings)
|
||||
|
||||
err = client.NetworkRemove(context.Background(), overlayID)
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, networkIsRemoved(client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
|
||||
}
|
||||
|
||||
func newSwarm(t *testing.T) *daemon.Swarm {
|
||||
d := &daemon.Swarm{
|
||||
Daemon: daemon.New(t, "", dockerdBinary, daemon.Config{
|
||||
Experimental: testEnv.DaemonInfo.ExperimentalBuild,
|
||||
}),
|
||||
// TODO: better method of finding an unused port
|
||||
Port: defaultSwarmPort,
|
||||
}
|
||||
// TODO: move to a NewSwarm constructor
|
||||
d.ListenAddr = fmt.Sprintf("0.0.0.0:%d", d.Port)
|
||||
|
||||
// avoid networking conflicts
|
||||
args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"}
|
||||
d.StartWithBusybox(t, args...)
|
||||
|
||||
require.NoError(t, d.Init(swarm.InitRequest{}))
|
||||
return d
|
||||
}
|
||||
|
||||
func swarmServiceSpec(name string, replicas uint64) swarm.ServiceSpec {
|
||||
return swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: name,
|
||||
},
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{
|
||||
Image: "busybox:latest",
|
||||
Command: []string{"/bin/top"},
|
||||
},
|
||||
},
|
||||
Mode: swarm.ServiceMode{
|
||||
Replicated: &swarm.ReplicatedService{
|
||||
Replicas: &replicas,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func serviceRunningTasksCount(client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("service", serviceID)
|
||||
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
|
||||
Filters: filter,
|
||||
})
|
||||
switch {
|
||||
case err != nil:
|
||||
return poll.Error(err)
|
||||
case len(tasks) == int(instances):
|
||||
for _, task := range tasks {
|
||||
if task.Status.State != swarm.TaskStateRunning {
|
||||
return poll.Continue("waiting for tasks to enter run state")
|
||||
}
|
||||
}
|
||||
return poll.Success()
|
||||
default:
|
||||
return poll.Continue("task count at %d waiting for %d", len(tasks), instances)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func networkIsRemoved(client client.NetworkAPIClient, networkID string) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
_, err := client.NetworkInspect(context.Background(), networkID, types.NetworkInspectOptions{})
|
||||
if err == nil {
|
||||
return poll.Continue("waiting for network %s to be removed", networkID)
|
||||
}
|
||||
return poll.Success()
|
||||
}
|
||||
}
|
||||
|
||||
func serviceIsRemoved(client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("service", serviceID)
|
||||
_, err := client.TaskList(context.Background(), types.TaskListOptions{
|
||||
Filters: filter,
|
||||
})
|
||||
if err == nil {
|
||||
return poll.Continue("waiting for service %s to be deleted", serviceID)
|
||||
}
|
||||
return poll.Success()
|
||||
}
|
||||
}
|
||||
|
||||
func noTasks(client client.ServiceAPIClient) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
filter := filters.NewArgs()
|
||||
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
|
||||
Filters: filter,
|
||||
})
|
||||
switch {
|
||||
case err != nil:
|
||||
return poll.Error(err)
|
||||
case len(tasks) == 0:
|
||||
return poll.Success()
|
||||
default:
|
||||
return poll.Continue("task count at %d waiting for 0", len(tasks))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if Service and Tasks info are part of the inspect verbose response
|
||||
func validNetworkVerbose(network types.NetworkResource, service string, instances uint64) bool {
|
||||
if service, ok := network.Services[service]; ok {
|
||||
if len(service.Tasks) == int(instances) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -113,8 +113,21 @@ type client struct {
|
||||
containers map[string]*container
|
||||
}
|
||||
|
||||
func (c *client) setRemote(remote *containerd.Client) {
|
||||
c.Lock()
|
||||
c.remote = remote
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
func (c *client) getRemote() *containerd.Client {
|
||||
c.RLock()
|
||||
remote := c.remote
|
||||
c.RUnlock()
|
||||
return remote
|
||||
}
|
||||
|
||||
func (c *client) Version(ctx context.Context) (containerd.Version, error) {
|
||||
return c.remote.Version(ctx)
|
||||
return c.getRemote().Version(ctx)
|
||||
}
|
||||
|
||||
func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallback) (alive bool, pid int, err error) {
|
||||
@ -188,7 +201,7 @@ func (c *client) Create(ctx context.Context, id string, ociSpec *specs.Spec, run
|
||||
|
||||
c.logger.WithField("bundle", bdir).WithField("root", ociSpec.Root.Path).Debug("bundle dir created")
|
||||
|
||||
cdCtr, err := c.remote.NewContainer(ctx, id,
|
||||
cdCtr, err := c.getRemote().NewContainer(ctx, id,
|
||||
containerd.WithSpec(ociSpec),
|
||||
// TODO(mlaventure): when containerd support lcow, revisit runtime value
|
||||
containerd.WithRuntime(fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS), runtimeOptions))
|
||||
@ -231,7 +244,7 @@ func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin
|
||||
// remove the checkpoint when we're done
|
||||
defer func() {
|
||||
if cp != nil {
|
||||
err := c.remote.ContentStore().Delete(context.Background(), cp.Digest)
|
||||
err := c.getRemote().ContentStore().Delete(context.Background(), cp.Digest)
|
||||
if err != nil {
|
||||
c.logger.WithError(err).WithFields(logrus.Fields{
|
||||
"ref": checkpointDir,
|
||||
@ -262,8 +275,9 @@ func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin
|
||||
func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {
|
||||
info.Checkpoint = cp
|
||||
info.Options = &runctypes.CreateOptions{
|
||||
IoUid: uint32(uid),
|
||||
IoGid: uint32(gid),
|
||||
IoUid: uint32(uid),
|
||||
IoGid: uint32(gid),
|
||||
NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -533,14 +547,14 @@ func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDi
|
||||
}
|
||||
// Whatever happens, delete the checkpoint from containerd
|
||||
defer func() {
|
||||
err := c.remote.ImageService().Delete(context.Background(), img.Name())
|
||||
err := c.getRemote().ImageService().Delete(context.Background(), img.Name())
|
||||
if err != nil {
|
||||
c.logger.WithError(err).WithField("digest", img.Target().Digest).
|
||||
Warnf("failed to delete checkpoint image")
|
||||
}
|
||||
}()
|
||||
|
||||
b, err := content.ReadBlob(ctx, c.remote.ContentStore(), img.Target().Digest)
|
||||
b, err := content.ReadBlob(ctx, c.getRemote().ContentStore(), img.Target().Digest)
|
||||
if err != nil {
|
||||
return wrapSystemError(errors.Wrapf(err, "failed to retrieve checkpoint data"))
|
||||
}
|
||||
@ -560,7 +574,7 @@ func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDi
|
||||
return wrapSystemError(errors.Wrapf(err, "invalid checkpoint"))
|
||||
}
|
||||
|
||||
rat, err := c.remote.ContentStore().ReaderAt(ctx, cpDesc.Digest)
|
||||
rat, err := c.getRemote().ContentStore().ReaderAt(ctx, cpDesc.Digest)
|
||||
if err != nil {
|
||||
return wrapSystemError(errors.Wrapf(err, "failed to get checkpoint reader"))
|
||||
}
|
||||
@ -713,16 +727,19 @@ func (c *client) processEventStream(ctx context.Context) {
|
||||
}
|
||||
}()
|
||||
|
||||
eventStream, err = c.remote.EventService().Subscribe(ctx, &eventsapi.SubscribeRequest{
|
||||
eventStream, err = c.getRemote().EventService().Subscribe(ctx, &eventsapi.SubscribeRequest{
|
||||
Filters: []string{
|
||||
"namespace==" + c.namespace,
|
||||
"topic~=/tasks/",
|
||||
// Filter on both namespace *and* topic. To create an "and" filter,
|
||||
// this must be a single, comma-separated string
|
||||
"namespace==" + c.namespace + ",topic~=|^/tasks/|",
|
||||
},
|
||||
}, grpc.FailFast(false))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.logger.WithField("namespace", c.namespace).Debug("processing event stream")
|
||||
|
||||
var oomKilled bool
|
||||
for {
|
||||
ev, err = eventStream.Recv()
|
||||
@ -826,7 +843,7 @@ func (c *client) processEventStream(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (c *client) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) {
|
||||
writer, err := c.remote.ContentStore().Writer(ctx, ref, 0, "")
|
||||
writer, err := c.getRemote().ContentStore().Writer(ctx, ref, 0, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user