diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index c3bcbc6552..677c1e41c5 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -382,6 +382,7 @@ type tarAppender struct { // for hardlink mapping SeenFiles map[uint64]string IDMappings *idtools.IDMappings + ChownOpts *idtools.IDPair // For packing and unpacking whiteout files in the // non standard format. The whiteout files defined @@ -390,12 +391,13 @@ type tarAppender struct { WhiteoutConverter tarWhiteoutConverter } -func newTarAppender(idMapping *idtools.IDMappings, writer io.Writer) *tarAppender { +func newTarAppender(idMapping *idtools.IDMappings, writer io.Writer, chownOpts *idtools.IDPair) *tarAppender { return &tarAppender{ SeenFiles: make(map[uint64]string), TarWriter: tar.NewWriter(writer), Buffer: pools.BufioWriter32KPool.Get(nil), IDMappings: idMapping, + ChownOpts: chownOpts, } } @@ -470,6 +472,12 @@ func (ta *tarAppender) addTarFile(path, name string) error { } } + // explicitly override with ChownOpts + if ta.ChownOpts != nil { + hdr.Uid = ta.ChownOpts.UID + hdr.Gid = ta.ChownOpts.GID + } + if ta.WhiteoutConverter != nil { wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) if err != nil { @@ -692,6 +700,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) ta := newTarAppender( idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps), compressWriter, + options.ChownOpts, ) ta.WhiteoutConverter = getWhiteoutConverter(options.WhiteoutFormat) diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index 3e66a537e3..d6be3507b4 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/docker/docker/pkg/idtools" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -724,6 +725,57 @@ func TestTarUntar(t *testing.T) { } } +func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) { + origin, err := ioutil.TempDir("", "docker-test-tar-chown-opt") + require.NoError(t, err) + + defer os.RemoveAll(origin) + filePath := filepath.Join(origin, "1") + err = ioutil.WriteFile(filePath, []byte("hello world"), 0700) + require.NoError(t, err) + + idMaps := []idtools.IDMap{ + 0: { + ContainerID: 0, + HostID: 0, + Size: 65536, + }, + 1: { + ContainerID: 0, + HostID: 100000, + Size: 65536, + }, + } + + cases := []struct { + opts *TarOptions + expectedUID int + expectedGID int + }{ + {&TarOptions{ChownOpts: &idtools.IDPair{UID: 1337, GID: 42}}, 1337, 42}, + {&TarOptions{ChownOpts: &idtools.IDPair{UID: 100001, GID: 100001}, UIDMaps: idMaps, GIDMaps: idMaps}, 100001, 100001}, + {&TarOptions{ChownOpts: &idtools.IDPair{UID: 0, GID: 0}, NoLchown: false}, 0, 0}, + {&TarOptions{ChownOpts: &idtools.IDPair{UID: 1, GID: 1}, NoLchown: true}, 1, 1}, + {&TarOptions{ChownOpts: &idtools.IDPair{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000}, + } + for _, testCase := range cases { + reader, err := TarWithOptions(filePath, testCase.opts) + require.NoError(t, err) + tr := tar.NewReader(reader) + defer reader.Close() + for { + hdr, err := tr.Next() + if err == io.EOF { + // end of tar archive + break + } + require.NoError(t, err) + assert.Equal(t, hdr.Uid, testCase.expectedUID, "Uid equals expected value") + assert.Equal(t, hdr.Gid, testCase.expectedGID, "Gid equals expected value") + } + } +} + func TestTarWithOptions(t *testing.T) { // TODO Windows: Figure out how to fix this test. if runtime.GOOS == "windows" { diff --git a/pkg/archive/changes.go b/pkg/archive/changes.go index 341c66dea7..d78fe6ac65 100644 --- a/pkg/archive/changes.go +++ b/pkg/archive/changes.go @@ -394,7 +394,7 @@ func ChangesSize(newDir string, changes []Change) int64 { func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (io.ReadCloser, error) { reader, writer := io.Pipe() go func() { - ta := newTarAppender(idtools.NewIDMappingsFromMaps(uidMaps, gidMaps), writer) + ta := newTarAppender(idtools.NewIDMappingsFromMaps(uidMaps, gidMaps), writer, nil) // this buffer is needed for the duration of this piped stream defer pools.BufioWriter32KPool.Put(ta.Buffer)