Rebases and completes initial PR for (prior: --user) --chown flag for
ADD/COPY commands in Dockerfile.
Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com>
| ... | ... |
@@ -56,6 +56,7 @@ type copyInstruction struct {
|
| 56 | 56 |
cmdName string |
| 57 | 57 |
infos []copyInfo |
| 58 | 58 |
dest string |
| 59 |
+ chownStr string |
|
| 59 | 60 |
allowLocalDecompression bool |
| 60 | 61 |
} |
| 61 | 62 |
|
| ... | ... |
@@ -369,6 +370,7 @@ func downloadSource(output io.Writer, stdout io.Writer, srcURL string) (remote b |
| 369 | 369 |
type copyFileOptions struct {
|
| 370 | 370 |
decompress bool |
| 371 | 371 |
archiver *archive.Archiver |
| 372 |
+ chownPair idtools.IDPair |
|
| 372 | 373 |
} |
| 373 | 374 |
|
| 374 | 375 |
func performCopyForInfo(dest copyInfo, source copyInfo, options copyFileOptions) error {
|
| ... | ... |
@@ -388,7 +390,7 @@ func performCopyForInfo(dest copyInfo, source copyInfo, options copyFileOptions) |
| 388 | 388 |
return errors.Wrapf(err, "source path not found") |
| 389 | 389 |
} |
| 390 | 390 |
if src.IsDir() {
|
| 391 |
- return copyDirectory(archiver, srcPath, destPath) |
|
| 391 |
+ return copyDirectory(archiver, srcPath, destPath, options.chownPair) |
|
| 392 | 392 |
} |
| 393 | 393 |
if options.decompress && archive.IsArchivePath(srcPath) && !source.noDecompress {
|
| 394 | 394 |
return archiver.UntarPath(srcPath, destPath) |
| ... | ... |
@@ -405,26 +407,28 @@ func performCopyForInfo(dest copyInfo, source copyInfo, options copyFileOptions) |
| 405 | 405 |
// is a symlink |
| 406 | 406 |
destPath = filepath.Join(destPath, filepath.Base(source.path)) |
| 407 | 407 |
} |
| 408 |
- return copyFile(archiver, srcPath, destPath) |
|
| 408 |
+ return copyFile(archiver, srcPath, destPath, options.chownPair) |
|
| 409 | 409 |
} |
| 410 | 410 |
|
| 411 |
-func copyDirectory(archiver *archive.Archiver, source, dest string) error {
|
|
| 411 |
+func copyDirectory(archiver *archive.Archiver, source, dest string, chownPair idtools.IDPair) error {
|
|
| 412 |
+ destExists, err := isExistingDirectory(dest) |
|
| 413 |
+ if err != nil {
|
|
| 414 |
+ return errors.Wrapf(err, "failed to query destination path") |
|
| 415 |
+ } |
|
| 412 | 416 |
if err := archiver.CopyWithTar(source, dest); err != nil {
|
| 413 | 417 |
return errors.Wrapf(err, "failed to copy directory") |
| 414 | 418 |
} |
| 415 |
- return fixPermissions(source, dest, archiver.IDMappings.RootPair()) |
|
| 419 |
+ return fixPermissions(source, dest, chownPair, !destExists) |
|
| 416 | 420 |
} |
| 417 | 421 |
|
| 418 |
-func copyFile(archiver *archive.Archiver, source, dest string) error {
|
|
| 419 |
- rootIDs := archiver.IDMappings.RootPair() |
|
| 420 |
- |
|
| 421 |
- if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest), 0755, rootIDs); err != nil {
|
|
| 422 |
+func copyFile(archiver *archive.Archiver, source, dest string, chownPair idtools.IDPair) error {
|
|
| 423 |
+ if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest), 0755, chownPair); err != nil {
|
|
| 422 | 424 |
return errors.Wrapf(err, "failed to create new directory") |
| 423 | 425 |
} |
| 424 | 426 |
if err := archiver.CopyFileWithTar(source, dest); err != nil {
|
| 425 | 427 |
return errors.Wrapf(err, "failed to copy file") |
| 426 | 428 |
} |
| 427 |
- return fixPermissions(source, dest, rootIDs) |
|
| 429 |
+ return fixPermissions(source, dest, chownPair, false) |
|
| 428 | 430 |
} |
| 429 | 431 |
|
| 430 | 432 |
func endsInSlash(path string) bool {
|
| ... | ... |
@@ -9,10 +9,16 @@ import ( |
| 9 | 9 |
"github.com/docker/docker/pkg/idtools" |
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 |
-func fixPermissions(source, destination string, rootIDs idtools.IDPair) error {
|
|
| 13 |
- skipChownRoot, err := isExistingDirectory(destination) |
|
| 14 |
- if err != nil {
|
|
| 15 |
- return err |
|
| 12 |
+func fixPermissions(source, destination string, rootIDs idtools.IDPair, overrideSkip bool) error {
|
|
| 13 |
+ var ( |
|
| 14 |
+ skipChownRoot bool |
|
| 15 |
+ err error |
|
| 16 |
+ ) |
|
| 17 |
+ if !overrideSkip {
|
|
| 18 |
+ skipChownRoot, err = isExistingDirectory(destination) |
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ return err |
|
| 21 |
+ } |
|
| 16 | 22 |
} |
| 17 | 23 |
|
| 18 | 24 |
// We Walk on the source rather than on the destination because we don't |
| ... | ... |
@@ -2,7 +2,7 @@ package dockerfile |
| 2 | 2 |
|
| 3 | 3 |
import "github.com/docker/docker/pkg/idtools" |
| 4 | 4 |
|
| 5 |
-func fixPermissions(source, destination string, rootIDs idtools.IDPair) error {
|
|
| 5 |
+func fixPermissions(source, destination string, rootIDs idtools.IDPair, overrideSkip bool) error {
|
|
| 6 | 6 |
// chown is not supported on Windows |
| 7 | 7 |
return nil |
| 8 | 8 |
} |
| ... | ... |
@@ -158,6 +158,7 @@ func add(req dispatchRequest) error {
|
| 158 | 158 |
if err != nil {
|
| 159 | 159 |
return err |
| 160 | 160 |
} |
| 161 |
+ copyInstruction.chownStr = flChown.Value |
|
| 161 | 162 |
copyInstruction.allowLocalDecompression = true |
| 162 | 163 |
|
| 163 | 164 |
return req.builder.performCopy(req.state, copyInstruction) |
| ... | ... |
@@ -189,6 +190,7 @@ func dispatchCopy(req dispatchRequest) error {
|
| 189 | 189 |
if err != nil {
|
| 190 | 190 |
return err |
| 191 | 191 |
} |
| 192 |
+ copyInstruction.chownStr = flChown.Value |
|
| 192 | 193 |
|
| 193 | 194 |
return req.builder.performCopy(req.state, copyInstruction) |
| 194 | 195 |
} |
| ... | ... |
@@ -7,13 +7,18 @@ import ( |
| 7 | 7 |
"crypto/sha256" |
| 8 | 8 |
"encoding/hex" |
| 9 | 9 |
"fmt" |
| 10 |
+ "path/filepath" |
|
| 11 |
+ "strconv" |
|
| 10 | 12 |
"strings" |
| 11 | 13 |
|
| 12 | 14 |
"github.com/docker/docker/api/types" |
| 13 | 15 |
"github.com/docker/docker/api/types/backend" |
| 14 | 16 |
"github.com/docker/docker/api/types/container" |
| 15 | 17 |
"github.com/docker/docker/image" |
| 18 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 16 | 19 |
"github.com/docker/docker/pkg/stringid" |
| 20 |
+ "github.com/docker/docker/pkg/symlink" |
|
| 21 |
+ lcUser "github.com/opencontainers/runc/libcontainer/user" |
|
| 17 | 22 |
"github.com/pkg/errors" |
| 18 | 23 |
) |
| 19 | 24 |
|
| ... | ... |
@@ -107,10 +112,16 @@ func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runC |
| 107 | 107 |
func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error {
|
| 108 | 108 |
srcHash := getSourceHashFromInfos(inst.infos) |
| 109 | 109 |
|
| 110 |
+ var chownComment string |
|
| 111 |
+ if inst.chownStr != "" {
|
|
| 112 |
+ chownComment = fmt.Sprintf("--chown=%s", inst.chownStr)
|
|
| 113 |
+ } |
|
| 114 |
+ commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest)
|
|
| 115 |
+ |
|
| 110 | 116 |
// TODO: should this have been using origPaths instead of srcHash in the comment? |
| 111 | 117 |
runConfigWithCommentCmd := copyRunConfig( |
| 112 | 118 |
state.runConfig, |
| 113 |
- withCmdCommentString(fmt.Sprintf("%s %s in %s ", inst.cmdName, srcHash, inst.dest), b.platform))
|
|
| 119 |
+ withCmdCommentString(commentStr, b.platform)) |
|
| 114 | 120 |
hit, err := b.probeCache(state, runConfigWithCommentCmd) |
| 115 | 121 |
if err != nil || hit {
|
| 116 | 122 |
return err |
| ... | ... |
@@ -125,9 +136,21 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error |
| 125 | 125 |
return err |
| 126 | 126 |
} |
| 127 | 127 |
|
| 128 |
+ chownPair := b.archiver.IDMappings.RootPair() |
|
| 129 |
+ // if a chown was requested, perform the steps to get the uid, gid |
|
| 130 |
+ // translated (if necessary because of user namespaces), and replace |
|
| 131 |
+ // the root pair with the chown pair for copy operations |
|
| 132 |
+ if inst.chownStr != "" {
|
|
| 133 |
+ chownPair, err = parseChownFlag(inst.chownStr, destInfo.root, b.archiver.IDMappings) |
|
| 134 |
+ if err != nil {
|
|
| 135 |
+ return errors.Wrapf(err, "unable to convert uid/gid chown string to host mapping") |
|
| 136 |
+ } |
|
| 137 |
+ } |
|
| 138 |
+ |
|
| 128 | 139 |
opts := copyFileOptions{
|
| 129 | 140 |
decompress: inst.allowLocalDecompression, |
| 130 | 141 |
archiver: b.archiver, |
| 142 |
+ chownPair: chownPair, |
|
| 131 | 143 |
} |
| 132 | 144 |
for _, info := range inst.infos {
|
| 133 | 145 |
if err := performCopyForInfo(destInfo, info, opts); err != nil {
|
| ... | ... |
@@ -137,6 +160,88 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error |
| 137 | 137 |
return b.exportImage(state, imageMount, runConfigWithCommentCmd) |
| 138 | 138 |
} |
| 139 | 139 |
|
| 140 |
+func parseChownFlag(chown, ctrRootPath string, idMappings *idtools.IDMappings) (idtools.IDPair, error) {
|
|
| 141 |
+ var userStr, grpStr string |
|
| 142 |
+ parts := strings.Split(chown, ":") |
|
| 143 |
+ if len(parts) > 2 {
|
|
| 144 |
+ return idtools.IDPair{}, errors.New("invalid chown string format: " + chown)
|
|
| 145 |
+ } |
|
| 146 |
+ if len(parts) == 1 {
|
|
| 147 |
+ // if no group specified, use the user spec as group as well |
|
| 148 |
+ userStr, grpStr = parts[0], parts[0] |
|
| 149 |
+ } else {
|
|
| 150 |
+ userStr, grpStr = parts[0], parts[1] |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ passwdPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "passwd"), ctrRootPath) |
|
| 154 |
+ if err != nil {
|
|
| 155 |
+ return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/passwd path in container rootfs")
|
|
| 156 |
+ } |
|
| 157 |
+ groupPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "group"), ctrRootPath) |
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/group path in container rootfs")
|
|
| 160 |
+ } |
|
| 161 |
+ uid, err := lookupUser(userStr, passwdPath) |
|
| 162 |
+ if err != nil {
|
|
| 163 |
+ return idtools.IDPair{}, errors.Wrapf(err, "can't find uid for user "+userStr)
|
|
| 164 |
+ } |
|
| 165 |
+ gid, err := lookupGroup(grpStr, groupPath) |
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ return idtools.IDPair{}, errors.Wrapf(err, "can't find gid for group "+grpStr)
|
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ // convert as necessary because of user namespaces |
|
| 171 |
+ chownPair, err := idMappings.ToHost(idtools.IDPair{UID: uid, GID: gid})
|
|
| 172 |
+ if err != nil {
|
|
| 173 |
+ return idtools.IDPair{}, errors.Wrapf(err, "unable to convert uid/gid to host mapping")
|
|
| 174 |
+ } |
|
| 175 |
+ return chownPair, nil |
|
| 176 |
+} |
|
| 177 |
+ |
|
| 178 |
+func lookupUser(userStr, filepath string) (int, error) {
|
|
| 179 |
+ // if the string is actually a uid integer, parse to int and return |
|
| 180 |
+ // as we don't need to translate with the help of files |
|
| 181 |
+ uid, err := strconv.Atoi(userStr) |
|
| 182 |
+ if err == nil {
|
|
| 183 |
+ return uid, nil |
|
| 184 |
+ } |
|
| 185 |
+ users, err := lcUser.ParsePasswdFileFilter(filepath, func(u lcUser.User) bool {
|
|
| 186 |
+ if u.Name == userStr {
|
|
| 187 |
+ return true |
|
| 188 |
+ } |
|
| 189 |
+ return false |
|
| 190 |
+ }) |
|
| 191 |
+ if err != nil {
|
|
| 192 |
+ return 0, err |
|
| 193 |
+ } |
|
| 194 |
+ if len(users) == 0 {
|
|
| 195 |
+ return 0, errors.New("no such user: " + userStr)
|
|
| 196 |
+ } |
|
| 197 |
+ return users[0].Uid, nil |
|
| 198 |
+} |
|
| 199 |
+ |
|
| 200 |
+func lookupGroup(groupStr, filepath string) (int, error) {
|
|
| 201 |
+ // if the string is actually a gid integer, parse to int and return |
|
| 202 |
+ // as we don't need to translate with the help of files |
|
| 203 |
+ gid, err := strconv.Atoi(groupStr) |
|
| 204 |
+ if err == nil {
|
|
| 205 |
+ return gid, nil |
|
| 206 |
+ } |
|
| 207 |
+ groups, err := lcUser.ParseGroupFileFilter(filepath, func(g lcUser.Group) bool {
|
|
| 208 |
+ if g.Name == groupStr {
|
|
| 209 |
+ return true |
|
| 210 |
+ } |
|
| 211 |
+ return false |
|
| 212 |
+ }) |
|
| 213 |
+ if err != nil {
|
|
| 214 |
+ return 0, err |
|
| 215 |
+ } |
|
| 216 |
+ if len(groups) == 0 {
|
|
| 217 |
+ return 0, errors.New("no such group: " + groupStr)
|
|
| 218 |
+ } |
|
| 219 |
+ return groups[0].Gid, nil |
|
| 220 |
+} |
|
| 221 |
+ |
|
| 140 | 222 |
func createDestInfo(workingDir string, inst copyInstruction, imageMount *imageMount) (copyInfo, error) {
|
| 141 | 223 |
// Twiddle the destination when it's a relative path - meaning, make it |
| 142 | 224 |
// relative to the WORKINGDIR |
| ... | ... |
@@ -2,6 +2,8 @@ package dockerfile |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 5 | 7 |
"runtime" |
| 6 | 8 |
"testing" |
| 7 | 9 |
|
| ... | ... |
@@ -11,6 +13,7 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/builder" |
| 12 | 12 |
"github.com/docker/docker/builder/remotecontext" |
| 13 | 13 |
"github.com/docker/docker/pkg/archive" |
| 14 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 14 | 15 |
"github.com/stretchr/testify/assert" |
| 15 | 16 |
"github.com/stretchr/testify/require" |
| 16 | 17 |
) |
| ... | ... |
@@ -129,3 +132,130 @@ func TestCopyRunConfig(t *testing.T) {
|
| 129 | 129 |
} |
| 130 | 130 |
|
| 131 | 131 |
} |
| 132 |
+ |
|
| 133 |
+func TestChownFlagParsing(t *testing.T) {
|
|
| 134 |
+ testFiles := map[string]string{
|
|
| 135 |
+ "passwd": `root:x:0:0::/bin:/bin/false |
|
| 136 |
+bin:x:1:1::/bin:/bin/false |
|
| 137 |
+wwwwww:x:21:33::/bin:/bin/false |
|
| 138 |
+unicorn:x:1001:1002::/bin:/bin/false |
|
| 139 |
+ `, |
|
| 140 |
+ "group": `root:x:0: |
|
| 141 |
+bin:x:1: |
|
| 142 |
+wwwwww:x:33: |
|
| 143 |
+unicorn:x:1002: |
|
| 144 |
+somegrp:x:5555: |
|
| 145 |
+othergrp:x:6666: |
|
| 146 |
+ `, |
|
| 147 |
+ } |
|
| 148 |
+ // test mappings for validating use of maps |
|
| 149 |
+ idMaps := []idtools.IDMap{
|
|
| 150 |
+ {
|
|
| 151 |
+ ContainerID: 0, |
|
| 152 |
+ HostID: 100000, |
|
| 153 |
+ Size: 65536, |
|
| 154 |
+ }, |
|
| 155 |
+ } |
|
| 156 |
+ remapped := idtools.NewIDMappingsFromMaps(idMaps, idMaps) |
|
| 157 |
+ unmapped := &idtools.IDMappings{}
|
|
| 158 |
+ |
|
| 159 |
+ contextDir, cleanup := createTestTempDir(t, "", "builder-chown-parse-test") |
|
| 160 |
+ defer cleanup() |
|
| 161 |
+ |
|
| 162 |
+ if err := os.Mkdir(filepath.Join(contextDir, "etc"), 0755); err != nil {
|
|
| 163 |
+ t.Fatalf("error creating test directory: %v", err)
|
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ for filename, content := range testFiles {
|
|
| 167 |
+ createTestTempFile(t, filepath.Join(contextDir, "etc"), filename, content, 0644) |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ // positive tests |
|
| 171 |
+ for _, testcase := range []struct {
|
|
| 172 |
+ name string |
|
| 173 |
+ chownStr string |
|
| 174 |
+ idMapping *idtools.IDMappings |
|
| 175 |
+ expected idtools.IDPair |
|
| 176 |
+ }{
|
|
| 177 |
+ {
|
|
| 178 |
+ name: "UIDNoMap", |
|
| 179 |
+ chownStr: "1", |
|
| 180 |
+ idMapping: unmapped, |
|
| 181 |
+ expected: idtools.IDPair{UID: 1, GID: 1},
|
|
| 182 |
+ }, |
|
| 183 |
+ {
|
|
| 184 |
+ name: "UIDGIDNoMap", |
|
| 185 |
+ chownStr: "0:1", |
|
| 186 |
+ idMapping: unmapped, |
|
| 187 |
+ expected: idtools.IDPair{UID: 0, GID: 1},
|
|
| 188 |
+ }, |
|
| 189 |
+ {
|
|
| 190 |
+ name: "UIDWithMap", |
|
| 191 |
+ chownStr: "0", |
|
| 192 |
+ idMapping: remapped, |
|
| 193 |
+ expected: idtools.IDPair{UID: 100000, GID: 100000},
|
|
| 194 |
+ }, |
|
| 195 |
+ {
|
|
| 196 |
+ name: "UIDGIDWithMap", |
|
| 197 |
+ chownStr: "1:33", |
|
| 198 |
+ idMapping: remapped, |
|
| 199 |
+ expected: idtools.IDPair{UID: 100001, GID: 100033},
|
|
| 200 |
+ }, |
|
| 201 |
+ {
|
|
| 202 |
+ name: "UserNoMap", |
|
| 203 |
+ chownStr: "bin:5555", |
|
| 204 |
+ idMapping: unmapped, |
|
| 205 |
+ expected: idtools.IDPair{UID: 1, GID: 5555},
|
|
| 206 |
+ }, |
|
| 207 |
+ {
|
|
| 208 |
+ name: "GroupWithMap", |
|
| 209 |
+ chownStr: "0:unicorn", |
|
| 210 |
+ idMapping: remapped, |
|
| 211 |
+ expected: idtools.IDPair{UID: 100000, GID: 101002},
|
|
| 212 |
+ }, |
|
| 213 |
+ {
|
|
| 214 |
+ name: "UserOnlyWithMap", |
|
| 215 |
+ chownStr: "unicorn", |
|
| 216 |
+ idMapping: remapped, |
|
| 217 |
+ expected: idtools.IDPair{UID: 101001, GID: 101002},
|
|
| 218 |
+ }, |
|
| 219 |
+ } {
|
|
| 220 |
+ t.Run(testcase.name, func(t *testing.T) {
|
|
| 221 |
+ idPair, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping) |
|
| 222 |
+ require.NoError(t, err, "Failed to parse chown flag: %q", testcase.chownStr) |
|
| 223 |
+ assert.Equal(t, testcase.expected, idPair, "chown flag mapping failure") |
|
| 224 |
+ }) |
|
| 225 |
+ } |
|
| 226 |
+ |
|
| 227 |
+ // error tests |
|
| 228 |
+ for _, testcase := range []struct {
|
|
| 229 |
+ name string |
|
| 230 |
+ chownStr string |
|
| 231 |
+ idMapping *idtools.IDMappings |
|
| 232 |
+ descr string |
|
| 233 |
+ }{
|
|
| 234 |
+ {
|
|
| 235 |
+ name: "BadChownFlagFormat", |
|
| 236 |
+ chownStr: "bob:1:555", |
|
| 237 |
+ idMapping: unmapped, |
|
| 238 |
+ descr: "invalid chown string format: bob:1:555", |
|
| 239 |
+ }, |
|
| 240 |
+ {
|
|
| 241 |
+ name: "UserNoExist", |
|
| 242 |
+ chownStr: "bob", |
|
| 243 |
+ idMapping: unmapped, |
|
| 244 |
+ descr: "can't find uid for user bob: no such user: bob", |
|
| 245 |
+ }, |
|
| 246 |
+ {
|
|
| 247 |
+ name: "GroupNoExist", |
|
| 248 |
+ chownStr: "root:bob", |
|
| 249 |
+ idMapping: unmapped, |
|
| 250 |
+ descr: "can't find gid for group bob: no such group: bob", |
|
| 251 |
+ }, |
|
| 252 |
+ } {
|
|
| 253 |
+ t.Run(testcase.name, func(t *testing.T) {
|
|
| 254 |
+ _, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping) |
|
| 255 |
+ assert.EqualError(t, err, testcase.descr, "Expected error string doesn't match") |
|
| 256 |
+ }) |
|
| 257 |
+ } |
|
| 258 |
+} |
| ... | ... |
@@ -44,7 +44,6 @@ import ( |
| 44 | 44 |
"github.com/docker/libnetwork/options" |
| 45 | 45 |
"github.com/docker/libnetwork/types" |
| 46 | 46 |
agentexec "github.com/docker/swarmkit/agent/exec" |
| 47 |
- "github.com/opencontainers/runc/libcontainer/user" |
|
| 48 | 47 |
"golang.org/x/net/context" |
| 49 | 48 |
) |
| 50 | 49 |
|
| ... | ... |
@@ -332,36 +331,6 @@ func (container *Container) GetRootResourcePath(path string) (string, error) {
|
| 332 | 332 |
return symlink.FollowSymlinkInScope(filepath.Join(container.Root, cleanPath), container.Root) |
| 333 | 333 |
} |
| 334 | 334 |
|
| 335 |
-// ParseUserGrp takes `username` in the format of username, uid, username:groupname, |
|
| 336 |
-// uid:gid, username:gid, or uid:groupname and parses the passwd file in the container |
|
| 337 |
-// to return the ExecUser referred to by `username`. |
|
| 338 |
-func (container *Container) ParseUserGrp(username string) (*user.ExecUser, error) {
|
|
| 339 |
- passwdPath, err := user.GetPasswdPath() |
|
| 340 |
- if err != nil {
|
|
| 341 |
- return nil, err |
|
| 342 |
- } |
|
| 343 |
- passwdPath, err = container.GetResourcePath(passwdPath) |
|
| 344 |
- if err != nil {
|
|
| 345 |
- return nil, err |
|
| 346 |
- } |
|
| 347 |
- |
|
| 348 |
- groupPath, err := user.GetGroupPath() |
|
| 349 |
- if err != nil {
|
|
| 350 |
- return nil, err |
|
| 351 |
- } |
|
| 352 |
- groupPath, err = container.GetResourcePath(groupPath) |
|
| 353 |
- if err != nil {
|
|
| 354 |
- return nil, err |
|
| 355 |
- } |
|
| 356 |
- |
|
| 357 |
- execUser, err := user.GetExecUserPath(username, nil, passwdPath, groupPath) |
|
| 358 |
- if err != nil {
|
|
| 359 |
- return nil, err |
|
| 360 |
- } |
|
| 361 |
- |
|
| 362 |
- return execUser, nil |
|
| 363 |
-} |
|
| 364 |
- |
|
| 365 | 335 |
// ExitOnNext signals to the monitor that it should not restart the container |
| 366 | 336 |
// after we send the kill signal. |
| 367 | 337 |
func (container *Container) ExitOnNext() {
|
| ... | ... |
@@ -410,6 +410,35 @@ func (s *DockerSuite) TestBuildAddRemoteNoDecompress(c *check.C) {
|
| 410 | 410 |
assert.Contains(c, string(out), "Successfully built") |
| 411 | 411 |
} |
| 412 | 412 |
|
| 413 |
+func (s *DockerSuite) TestBuildChownOnCopy(c *check.C) {
|
|
| 414 |
+ testRequires(c, DaemonIsLinux) |
|
| 415 |
+ dockerfile := `FROM busybox |
|
| 416 |
+ RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
|
| 417 |
+ RUN echo 'test1:x:1001:' >> /etc/group |
|
| 418 |
+ RUN echo 'test2:x:1002:' >> /etc/group |
|
| 419 |
+ COPY --chown=test1:1002 . /new_dir |
|
| 420 |
+ RUN ls -l / |
|
| 421 |
+ RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]
|
|
| 422 |
+ RUN [ $(ls -nl / | grep new_dir | awk '{print $3":"$4}') = '1001:1002' ]
|
|
| 423 |
+ ` |
|
| 424 |
+ ctx := fakecontext.New(c, "", |
|
| 425 |
+ fakecontext.WithDockerfile(dockerfile), |
|
| 426 |
+ fakecontext.WithFile("test_file1", "some test content"),
|
|
| 427 |
+ ) |
|
| 428 |
+ defer ctx.Close() |
|
| 429 |
+ |
|
| 430 |
+ res, body, err := request.Post( |
|
| 431 |
+ "/build", |
|
| 432 |
+ request.RawContent(ctx.AsTarReader(c)), |
|
| 433 |
+ request.ContentType("application/x-tar"))
|
|
| 434 |
+ c.Assert(err, checker.IsNil) |
|
| 435 |
+ c.Assert(res.StatusCode, checker.Equals, http.StatusOK) |
|
| 436 |
+ |
|
| 437 |
+ out, err := testutil.ReadBody(body) |
|
| 438 |
+ require.NoError(c, err) |
|
| 439 |
+ assert.Contains(c, string(out), "Successfully built") |
|
| 440 |
+} |
|
| 441 |
+ |
|
| 413 | 442 |
func (s *DockerSuite) TestBuildWithSession(c *check.C) {
|
| 414 | 443 |
testRequires(c, ExperimentalDaemon) |
| 415 | 444 |
|
| ... | ... |
@@ -602,78 +602,6 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, command, command, command, com |
| 602 | 602 |
} |
| 603 | 603 |
} |
| 604 | 604 |
|
| 605 |
-func (s *DockerSuite) TestBuildAddChownFlag(c *check.C) {
|
|
| 606 |
- testRequires(c, DaemonIsLinux) // Linux specific test |
|
| 607 |
- name := "testaddtonewdest" |
|
| 608 |
- ctx, err := fakeContext(`FROM busybox |
|
| 609 |
-RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
|
| 610 |
-RUN echo 'test1:x:1001:' >> /etc/group |
|
| 611 |
-RUN echo 'test2:x:1002:' >> /etc/group |
|
| 612 |
-ADD --chown=test1:1002 . /new_dir |
|
| 613 |
-RUN ls -l / |
|
| 614 |
-RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]`,
|
|
| 615 |
- map[string]string{
|
|
| 616 |
- "test_dir/test_file": "test file", |
|
| 617 |
- }) |
|
| 618 |
- if err != nil {
|
|
| 619 |
- c.Fatal(err) |
|
| 620 |
- } |
|
| 621 |
- defer ctx.Close() |
|
| 622 |
- |
|
| 623 |
- if _, err := buildImageFromContext(name, ctx, true); err != nil {
|
|
| 624 |
- c.Fatal(err) |
|
| 625 |
- } |
|
| 626 |
-} |
|
| 627 |
- |
|
| 628 |
-func (s *DockerDaemonSuite) TestBuildAddChownFlagUserNamespace(c *check.C) {
|
|
| 629 |
- testRequires(c, DaemonIsLinux) // Linux specific test |
|
| 630 |
- |
|
| 631 |
- c.Assert(s.d.StartWithBusybox("--userns-remap", "default"), checker.IsNil)
|
|
| 632 |
- |
|
| 633 |
- name := "testaddtonewdest" |
|
| 634 |
- ctx, err := fakeContext(`FROM busybox |
|
| 635 |
-RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
|
| 636 |
-RUN echo 'test1:x:1001:' >> /etc/group |
|
| 637 |
-RUN echo 'test2:x:1002:' >> /etc/group |
|
| 638 |
-ADD --chown=test1:1002 . /new_dir |
|
| 639 |
-RUN ls -l / |
|
| 640 |
-RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]`,
|
|
| 641 |
- map[string]string{
|
|
| 642 |
- "test_dir/test_file": "test file", |
|
| 643 |
- }) |
|
| 644 |
- if err != nil {
|
|
| 645 |
- c.Fatal(err) |
|
| 646 |
- } |
|
| 647 |
- defer ctx.Close() |
|
| 648 |
- |
|
| 649 |
- if _, err := buildImageFromContext(name, ctx, true); err != nil {
|
|
| 650 |
- c.Fatal(err) |
|
| 651 |
- } |
|
| 652 |
-} |
|
| 653 |
- |
|
| 654 |
-func (s *DockerSuite) TestBuildCopyChownFlag(c *check.C) {
|
|
| 655 |
- testRequires(c, DaemonIsLinux) // Linux specific test |
|
| 656 |
- name := "testaddtonewdest" |
|
| 657 |
- ctx, err := fakeContext(`FROM busybox |
|
| 658 |
-RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd |
|
| 659 |
-RUN echo 'test1:x:1001:' >> /etc/group |
|
| 660 |
-RUN echo 'test2:x:1002:' >> /etc/group |
|
| 661 |
-COPY --chown=test1:1002 . /new_dir |
|
| 662 |
-RUN ls -l / |
|
| 663 |
-RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ]`,
|
|
| 664 |
- map[string]string{
|
|
| 665 |
- "test_dir/test_file": "test file", |
|
| 666 |
- }) |
|
| 667 |
- if err != nil {
|
|
| 668 |
- c.Fatal(err) |
|
| 669 |
- } |
|
| 670 |
- defer ctx.Close() |
|
| 671 |
- |
|
| 672 |
- if _, err := buildImageFromContext(name, ctx, true); err != nil {
|
|
| 673 |
- c.Fatal(err) |
|
| 674 |
- } |
|
| 675 |
-} |
|
| 676 |
- |
|
| 677 | 605 |
func (s *DockerSuite) TestBuildCopyFileWithWhitespaceOnWindows(c *check.C) {
|
| 678 | 606 |
testRequires(c, DaemonIsWindows) |
| 679 | 607 |
dockerfile := `FROM ` + testEnv.MinimalBaseImage() + ` |