Skip to content

Commit 2ab5981

Browse files
Merge pull request #6306 from openshift-cherrypick-robot/cherry-pick-6296-to-release-1.41
[release-1.41] Exclude pulled up parent directories at commit-time
2 parents 1854b44 + dee9f3d commit 2ab5981

File tree

5 files changed

+167
-18
lines changed

5 files changed

+167
-18
lines changed

copier/copier.go

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ type removeResponse struct{}
305305

306306
// ensureResponse encodes a response to an Ensure request.
307307
type ensureResponse struct {
308-
Created []string // paths that were created because they weren't already present
308+
Created []string // paths that were created because they weren't already present
309+
Noted []EnsureParentPath // preexisting paths that are parents of created items
309310
}
310311

311312
// conditionalRemoveResponse encodes a response to a conditionalRemove request.
@@ -479,6 +480,7 @@ func Put(root string, directory string, options PutOptions, bulkReader io.Reader
479480
// MkdirOptions controls parts of Mkdir()'s behavior.
480481
type MkdirOptions struct {
481482
UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs when creating directories
483+
ModTimeNew *time.Time // set mtime and atime of newly-created directories
482484
ChownNew *idtools.IDPair // set ownership of newly-created directories
483485
ChmodNew *os.FileMode // set permissions on newly-created directories
484486
}
@@ -2199,6 +2201,7 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
21992201
}
22002202

22012203
subdir := ""
2204+
var created []string
22022205
for _, component := range strings.Split(rel, string(os.PathSeparator)) {
22032206
subdir = filepath.Join(subdir, component)
22042207
path := filepath.Join(req.Root, subdir)
@@ -2209,6 +2212,7 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
22092212
if err = chmod(path, dirMode); err != nil {
22102213
return errorResponse("copier: mkdir: error setting permissions on %q to 0%o: %v", path, dirMode)
22112214
}
2215+
created = append(created, path)
22122216
} else {
22132217
// FreeBSD can return EISDIR for "mkdir /":
22142218
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=59739.
@@ -2217,6 +2221,17 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
22172221
}
22182222
}
22192223
}
2224+
// set timestamps last, in case we needed to create some nested directories, which would
2225+
// update the timestamps on directories that we'd just set timestamps on, if we had done
2226+
// that immediately
2227+
if req.MkdirOptions.ModTimeNew != nil {
2228+
when := *req.MkdirOptions.ModTimeNew
2229+
for _, newDirectory := range created {
2230+
if err = lutimes(false, newDirectory, when, when); err != nil {
2231+
return errorResponse("copier: mkdir: error setting datestamp on %q: %v", newDirectory, err)
2232+
}
2233+
}
2234+
}
22202235

22212236
return &response{Error: "", Mkdir: mkdirResponse{}}, nil, nil
22222237
}
@@ -2255,12 +2270,22 @@ type EnsureOptions struct {
22552270
Paths []EnsurePath
22562271
}
22572272

2273+
// EnsureParentPath is a parent (or grandparent, or...) directory of an item
2274+
// created by Ensure(), along with information about it, from before the item
2275+
// in question was created. If the information about this directory hasn't
2276+
// changed when commit-time rolls around, it's most likely that this directory
2277+
// is only being considered for inclusion in the layer because it was pulled
2278+
// up, and it was not actually changed.
2279+
type EnsureParentPath = ConditionalRemovePath
2280+
22582281
// Ensure ensures that the specified mount point targets exist under the root.
22592282
// If the root directory is not specified, the current root directory is used.
22602283
// If root is specified and the current OS supports it, and the calling process
22612284
// has the necessary privileges, the operation is performed in a chrooted
22622285
// context.
2263-
func Ensure(root, directory string, options EnsureOptions) ([]string, error) {
2286+
// Returns a slice with the pathnames of items that needed to be created and a
2287+
// slice of affected parent directories and information about them.
2288+
func Ensure(root, directory string, options EnsureOptions) ([]string, []EnsureParentPath, error) {
22642289
req := request{
22652290
Request: requestEnsure,
22662291
Root: root,
@@ -2269,12 +2294,12 @@ func Ensure(root, directory string, options EnsureOptions) ([]string, error) {
22692294
}
22702295
resp, err := copier(nil, nil, req)
22712296
if err != nil {
2272-
return nil, err
2297+
return nil, nil, err
22732298
}
22742299
if resp.Error != "" {
2275-
return nil, errors.New(resp.Error)
2300+
return nil, nil, errors.New(resp.Error)
22762301
}
2277-
return resp.Ensure.Created, nil
2302+
return resp.Ensure.Created, resp.Ensure.Noted, nil
22782303
}
22792304

22802305
func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response {
@@ -2283,6 +2308,7 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
22832308
}
22842309
slices.SortFunc(req.EnsureOptions.Paths, func(a, b EnsurePath) int { return strings.Compare(a.Path, b.Path) })
22852310
var created []string
2311+
notedByName := map[string]EnsureParentPath{}
22862312
for _, item := range req.EnsureOptions.Paths {
22872313
uid, gid := 0, 0
22882314
if item.Chown != nil {
@@ -2326,11 +2352,25 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
23262352
if parentPath == "" {
23272353
parentPath = "."
23282354
}
2329-
leaf := filepath.Join(subdir, component)
2355+
leaf := filepath.Join(parentPath, component)
23302356
parentInfo, err := os.Stat(filepath.Join(req.Root, parentPath))
23312357
if err != nil {
23322358
return errorResponse("copier: ensure: checking datestamps on %q (%d: %v): %v", parentPath, i, components, err)
23332359
}
2360+
if parentPath != "." {
2361+
parentModTime := parentInfo.ModTime().UTC()
2362+
parentMode := parentInfo.Mode()
2363+
uid, gid, err := owner(parentInfo)
2364+
if err != nil {
2365+
return errorResponse("copier: ensure: error reading owner of %q: %v", parentPath, err)
2366+
}
2367+
notedByName[parentPath] = EnsureParentPath{
2368+
Path: parentPath,
2369+
ModTime: &parentModTime,
2370+
Mode: &parentMode,
2371+
Owner: &idtools.IDPair{UID: uid, GID: gid},
2372+
}
2373+
}
23342374
if i < len(components)-1 || item.Typeflag == tar.TypeDir {
23352375
err = os.Mkdir(filepath.Join(req.Root, leaf), mode)
23362376
subdir = leaf
@@ -2372,7 +2412,15 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
23722412
}
23732413
}
23742414
slices.Sort(created)
2375-
return &response{Error: "", Ensure: ensureResponse{Created: created}}
2415+
noted := make([]EnsureParentPath, 0, len(notedByName))
2416+
for _, n := range notedByName {
2417+
if slices.Contains(created, n.Path) {
2418+
continue
2419+
}
2420+
noted = append(noted, n)
2421+
}
2422+
slices.SortFunc(noted, func(a, b EnsureParentPath) int { return strings.Compare(a.Path, b.Path) })
2423+
return &response{Error: "", Ensure: ensureResponse{Created: created, Noted: noted}}
23762424
}
23772425

23782426
// ConditionalRemovePath is a single item being passed to an ConditionalRemove() call.

copier/copier_test.go

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,12 +2045,15 @@ func TestExtendedGlob(t *testing.T) {
20452045
func testEnsure(t *testing.T) {
20462046
zero := time.Unix(0, 0)
20472047
worldReadable := os.FileMode(0o644)
2048+
ugReadable := os.FileMode(0o750)
20482049

20492050
testCases := []struct {
2050-
description string
2051-
subdir string
2052-
options EnsureOptions
2053-
expected []string
2051+
description string
2052+
subdir string
2053+
mkdirs []string
2054+
options EnsureOptions
2055+
expectCreated []string
2056+
expectNoted []EnsureParentPath
20542057
}{
20552058
{
20562059
description: "base",
@@ -2078,7 +2081,7 @@ func testEnsure(t *testing.T) {
20782081
},
20792082
},
20802083
},
2081-
expected: []string{
2084+
expectCreated: []string{
20822085
"subdir",
20832086
"subdir/a",
20842087
"subdir/a/b",
@@ -2087,6 +2090,7 @@ func testEnsure(t *testing.T) {
20872090
"subdir/a/b/c",
20882091
"subdir/a/b/d",
20892092
},
2093+
expectNoted: []EnsureParentPath{},
20902094
},
20912095
{
20922096
description: "nosubdir",
@@ -2103,21 +2107,93 @@ func testEnsure(t *testing.T) {
21032107
},
21042108
},
21052109
},
2106-
expected: []string{
2110+
expectCreated: []string{
21072111
"a",
21082112
"a/b",
21092113
"a/b/c",
21102114
"a/b/d",
21112115
},
2116+
expectNoted: []EnsureParentPath{},
2117+
},
2118+
{
2119+
description: "mkdir-first",
2120+
subdir: "dir/subdir",
2121+
mkdirs: []string{"dir", "dir/subdir"},
2122+
options: EnsureOptions{
2123+
Paths: []EnsurePath{
2124+
{
2125+
Path: filepath.Join(string(os.PathSeparator), "a", "b", "a"),
2126+
Typeflag: tar.TypeReg,
2127+
Chmod: &worldReadable,
2128+
},
2129+
{
2130+
Path: filepath.Join("a", "b", "b"),
2131+
Typeflag: tar.TypeReg,
2132+
ModTime: &zero,
2133+
},
2134+
{
2135+
Path: filepath.Join(string(os.PathSeparator), "a", "b", "c"),
2136+
Typeflag: tar.TypeDir,
2137+
ModTime: &zero,
2138+
},
2139+
{
2140+
Path: filepath.Join("a", "b", "d"),
2141+
Typeflag: tar.TypeDir,
2142+
},
2143+
},
2144+
},
2145+
expectCreated: []string{
2146+
"dir/subdir/a",
2147+
"dir/subdir/a/b",
2148+
"dir/subdir/a/b/a",
2149+
"dir/subdir/a/b/b",
2150+
"dir/subdir/a/b/c",
2151+
"dir/subdir/a/b/d",
2152+
},
2153+
expectNoted: []EnsureParentPath{
2154+
{
2155+
Path: "dir",
2156+
Mode: &ugReadable,
2157+
Owner: &idtools.IDPair{UID: 1, GID: 1},
2158+
// ModTime gets updated when we create dir/subdir, can't check it
2159+
},
2160+
{
2161+
Path: "dir/subdir",
2162+
Mode: &ugReadable,
2163+
Owner: &idtools.IDPair{UID: 1, GID: 1},
2164+
ModTime: &zero,
2165+
},
2166+
},
21122167
},
21132168
}
21142169
for i := range testCases {
21152170
t.Run(testCases[i].description, func(t *testing.T) {
21162171
testStarted := time.Now()
21172172
tmpdir := t.TempDir()
2118-
created, err := Ensure(tmpdir, testCases[i].subdir, testCases[i].options)
2173+
for _, mkdir := range testCases[i].mkdirs {
2174+
err := Mkdir(tmpdir, mkdir, MkdirOptions{
2175+
ModTimeNew: &zero,
2176+
ChmodNew: &ugReadable,
2177+
ChownNew: &idtools.IDPair{UID: 1, GID: 1},
2178+
})
2179+
require.NoError(t, err, "unexpected error ensuring")
2180+
}
2181+
created, noted, err := Ensure(tmpdir, testCases[i].subdir, testCases[i].options)
21192182
require.NoError(t, err, "unexpected error ensuring")
2120-
require.EqualValues(t, testCases[i].expected, created, "did not expect these")
2183+
require.EqualValues(t, testCases[i].expectCreated, created, "did not expect to create these")
2184+
require.Equal(t, len(testCases[i].expectNoted), len(noted), "noticed the wrong number of things")
2185+
for n := range noted {
2186+
require.Equalf(t, testCases[i].expectNoted[n].Path, noted[n].Path, "noticed item %d path", n)
2187+
if testCases[i].expectNoted[n].Mode != nil {
2188+
require.Equalf(t, testCases[i].expectNoted[n].Mode.Perm(), noted[n].Mode.Perm(), "noticed item %q mode", noted[n].Path)
2189+
}
2190+
if testCases[i].expectNoted[n].Owner != nil {
2191+
require.Equalf(t, *testCases[i].expectNoted[n].Owner, *noted[n].Owner, "noticed item %q owner", noted[n].Path)
2192+
}
2193+
if testCases[i].expectNoted[n].ModTime != nil {
2194+
require.Equalf(t, testCases[i].expectNoted[n].ModTime.UnixNano(), noted[n].ModTime.UnixNano(), "noticed item %q mtime", noted[n].Path)
2195+
}
2196+
}
21212197
for _, item := range testCases[i].options.Paths {
21222198
target := filepath.Join(tmpdir, testCases[i].subdir, item.Path)
21232199
st, err := os.Stat(target)
@@ -2298,7 +2374,7 @@ func testConditionalRemove(t *testing.T) {
22982374
Chmod: what.mode,
22992375
})
23002376
}
2301-
created, err := Ensure(tmpdir, testCases[i].subdir, create)
2377+
created, _, err := Ensure(tmpdir, testCases[i].subdir, create)
23022378
require.NoErrorf(t, err, "unexpected error creating %#v", create)
23032379
remove := testCases[i].remove
23042380
for _, what := range created {

image.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,7 @@ func (b *Builder) makeContainerImageRef(options CommitOptions) (*containerImageR
14621462
if options.CompatLayerOmissions == types.OptionalBoolTrue {
14631463
layerExclusions = append(layerExclusions, compatLayerExclusions...)
14641464
}
1465+
logrus.Debugf("excluding these items from committed layer: %#v", layerExclusions)
14651466

14661467
manifestType := options.PreferredManifestType
14671468
if manifestType == "" {

run_common.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,11 +2119,12 @@ func (b *Builder) createMountTargets(spec *specs.Spec) ([]copier.ConditionalRemo
21192119
if len(targets.Paths) == 0 {
21202120
return nil, nil
21212121
}
2122-
created, err := copier.Ensure(rootfsPath, rootfsPath, targets)
2122+
created, noted, err := copier.Ensure(rootfsPath, rootfsPath, targets)
21232123
if err != nil {
21242124
return nil, err
21252125
}
21262126
logrus.Debugf("created mount targets at %v", created)
2127+
logrus.Debugf("parents of mount targets at %+v", noted)
21272128
var remove []copier.ConditionalRemovePath
21282129
for _, target := range created {
21292130
cleanedTarget := strings.Trim(path.Clean(filepath.ToSlash(target)), "/")
@@ -2154,7 +2155,7 @@ func (b *Builder) createMountTargets(spec *specs.Spec) ([]copier.ConditionalRemo
21542155
if err := os.Mkdir(filepath.Join(cdir, containerExcludesDir), 0o700); err != nil && !errors.Is(err, os.ErrExist) {
21552156
return nil, fmt.Errorf("creating exclusions directory: %w", err)
21562157
}
2157-
encoded, err := json.Marshal(remove)
2158+
encoded, err := json.Marshal(append(slices.Clone(noted), remove...))
21582159
if err != nil {
21592160
return nil, fmt.Errorf("encoding list of items to exclude at commit-time: %w", err)
21602161
}

tests/bud.bats

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8479,3 +8479,26 @@ _EOF
84798479
run_buildah build --layers -f $BUDFILES/from-scratch/Containerfile2 $BUDFILES/from-scratch/
84808480
assert "$output" !~ "Using cache"
84818481
}
8482+
8483+
@test "bud --layers should not include pulled up parent directories of mount points" {
8484+
_prefetch busybox
8485+
local contextdir=${TEST_SCRATCH_DIR}/context
8486+
mkdir $contextdir
8487+
mkdir $contextdir/dev
8488+
mkdir $contextdir/proc
8489+
mkdir $contextdir/sys
8490+
8491+
cat > $contextdir/Dockerfile << _EOF
8492+
FROM scratch
8493+
COPY / /
8494+
COPY --from=quay.io/libpod/busybox / /
8495+
RUN rm -f /Dockerfile
8496+
RUN --mount=type=bind,ro,src=/Dockerfile,target=/var/spool/mail/tmpfile touch /newfile
8497+
_EOF
8498+
run_buildah build --layers -t oci:${TEST_SCRATCH_DIR}/oci-image ${contextdir}
8499+
lastlayer=$(oci_image_last_diff ${TEST_SCRATCH_DIR}/oci-image)
8500+
run tar tf ${TEST_SCRATCH_DIR}/oci-image/"${lastlayer}"
8501+
echo "$output"
8502+
assert "$status" = "0"
8503+
assert "${#lines[*]}" = "1"
8504+
}

0 commit comments

Comments
 (0)