copier.Ensure(): also return parent directories
Have Ensure() also return the parent directories of items that it created, along with information about them that can be used to filter them out of the layer at commit-time. This modifies the signature of Ensure(), but it was added in 1.41.0, and shouldn't (yet) have any external users. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
parent
19041cde31
commit
4f2feb8f47
|
@ -305,7 +305,8 @@ type removeResponse struct{}
|
|||
|
||||
// ensureResponse encodes a response to an Ensure request.
|
||||
type ensureResponse struct {
|
||||
Created []string // paths that were created because they weren't already present
|
||||
Created []string // paths that were created because they weren't already present
|
||||
Noted []EnsureParentPath // preexisting paths that are parents of created items
|
||||
}
|
||||
|
||||
// conditionalRemoveResponse encodes a response to a conditionalRemove request.
|
||||
|
@ -2269,12 +2270,22 @@ type EnsureOptions struct {
|
|||
Paths []EnsurePath
|
||||
}
|
||||
|
||||
// EnsureParentPath is a parent (or grandparent, or...) directory of an item
|
||||
// created by Ensure(), along with information about it, from before the item
|
||||
// in question was created. If the information about this directory hasn't
|
||||
// changed when commit-time rolls around, it's most likely that this directory
|
||||
// is only being considered for inclusion in the layer because it was pulled
|
||||
// up, and it was not actually changed.
|
||||
type EnsureParentPath = ConditionalRemovePath
|
||||
|
||||
// Ensure ensures that the specified mount point targets exist under the root.
|
||||
// If the root directory is not specified, the current root directory is used.
|
||||
// If root is specified and the current OS supports it, and the calling process
|
||||
// has the necessary privileges, the operation is performed in a chrooted
|
||||
// context.
|
||||
func Ensure(root, directory string, options EnsureOptions) ([]string, error) {
|
||||
// Returns a slice with the pathnames of items that needed to be created and a
|
||||
// slice of affected parent directories and information about them.
|
||||
func Ensure(root, directory string, options EnsureOptions) ([]string, []EnsureParentPath, error) {
|
||||
req := request{
|
||||
Request: requestEnsure,
|
||||
Root: root,
|
||||
|
@ -2283,12 +2294,12 @@ func Ensure(root, directory string, options EnsureOptions) ([]string, error) {
|
|||
}
|
||||
resp, err := copier(nil, nil, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.Error != "" {
|
||||
return nil, errors.New(resp.Error)
|
||||
return nil, nil, errors.New(resp.Error)
|
||||
}
|
||||
return resp.Ensure.Created, nil
|
||||
return resp.Ensure.Created, resp.Ensure.Noted, nil
|
||||
}
|
||||
|
||||
func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response {
|
||||
|
@ -2297,6 +2308,7 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
|
|||
}
|
||||
slices.SortFunc(req.EnsureOptions.Paths, func(a, b EnsurePath) int { return strings.Compare(a.Path, b.Path) })
|
||||
var created []string
|
||||
notedByName := map[string]EnsureParentPath{}
|
||||
for _, item := range req.EnsureOptions.Paths {
|
||||
uid, gid := 0, 0
|
||||
if item.Chown != nil {
|
||||
|
@ -2340,11 +2352,25 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
|
|||
if parentPath == "" {
|
||||
parentPath = "."
|
||||
}
|
||||
leaf := filepath.Join(subdir, component)
|
||||
leaf := filepath.Join(parentPath, component)
|
||||
parentInfo, err := os.Stat(filepath.Join(req.Root, parentPath))
|
||||
if err != nil {
|
||||
return errorResponse("copier: ensure: checking datestamps on %q (%d: %v): %v", parentPath, i, components, err)
|
||||
}
|
||||
if parentPath != "." {
|
||||
parentModTime := parentInfo.ModTime().UTC()
|
||||
parentMode := parentInfo.Mode()
|
||||
uid, gid, err := owner(parentInfo)
|
||||
if err != nil {
|
||||
return errorResponse("copier: ensure: error reading owner of %q: %v", parentPath, err)
|
||||
}
|
||||
notedByName[parentPath] = EnsureParentPath{
|
||||
Path: parentPath,
|
||||
ModTime: &parentModTime,
|
||||
Mode: &parentMode,
|
||||
Owner: &idtools.IDPair{UID: uid, GID: gid},
|
||||
}
|
||||
}
|
||||
if i < len(components)-1 || item.Typeflag == tar.TypeDir {
|
||||
err = os.Mkdir(filepath.Join(req.Root, leaf), mode)
|
||||
subdir = leaf
|
||||
|
@ -2386,7 +2412,15 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
|
|||
}
|
||||
}
|
||||
slices.Sort(created)
|
||||
return &response{Error: "", Ensure: ensureResponse{Created: created}}
|
||||
noted := make([]EnsureParentPath, 0, len(notedByName))
|
||||
for _, n := range notedByName {
|
||||
if slices.Contains(created, n.Path) {
|
||||
continue
|
||||
}
|
||||
noted = append(noted, n)
|
||||
}
|
||||
slices.SortFunc(noted, func(a, b EnsureParentPath) int { return strings.Compare(a.Path, b.Path) })
|
||||
return &response{Error: "", Ensure: ensureResponse{Created: created, Noted: noted}}
|
||||
}
|
||||
|
||||
// ConditionalRemovePath is a single item being passed to an ConditionalRemove() call.
|
||||
|
|
|
@ -2045,12 +2045,15 @@ func TestExtendedGlob(t *testing.T) {
|
|||
func testEnsure(t *testing.T) {
|
||||
zero := time.Unix(0, 0)
|
||||
worldReadable := os.FileMode(0o644)
|
||||
ugReadable := os.FileMode(0o750)
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
subdir string
|
||||
options EnsureOptions
|
||||
expected []string
|
||||
description string
|
||||
subdir string
|
||||
mkdirs []string
|
||||
options EnsureOptions
|
||||
expectCreated []string
|
||||
expectNoted []EnsureParentPath
|
||||
}{
|
||||
{
|
||||
description: "base",
|
||||
|
@ -2078,7 +2081,7 @@ func testEnsure(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
expectCreated: []string{
|
||||
"subdir",
|
||||
"subdir/a",
|
||||
"subdir/a/b",
|
||||
|
@ -2087,6 +2090,7 @@ func testEnsure(t *testing.T) {
|
|||
"subdir/a/b/c",
|
||||
"subdir/a/b/d",
|
||||
},
|
||||
expectNoted: []EnsureParentPath{},
|
||||
},
|
||||
{
|
||||
description: "nosubdir",
|
||||
|
@ -2103,21 +2107,93 @@ func testEnsure(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
expectCreated: []string{
|
||||
"a",
|
||||
"a/b",
|
||||
"a/b/c",
|
||||
"a/b/d",
|
||||
},
|
||||
expectNoted: []EnsureParentPath{},
|
||||
},
|
||||
{
|
||||
description: "mkdir-first",
|
||||
subdir: "dir/subdir",
|
||||
mkdirs: []string{"dir", "dir/subdir"},
|
||||
options: EnsureOptions{
|
||||
Paths: []EnsurePath{
|
||||
{
|
||||
Path: filepath.Join(string(os.PathSeparator), "a", "b", "a"),
|
||||
Typeflag: tar.TypeReg,
|
||||
Chmod: &worldReadable,
|
||||
},
|
||||
{
|
||||
Path: filepath.Join("a", "b", "b"),
|
||||
Typeflag: tar.TypeReg,
|
||||
ModTime: &zero,
|
||||
},
|
||||
{
|
||||
Path: filepath.Join(string(os.PathSeparator), "a", "b", "c"),
|
||||
Typeflag: tar.TypeDir,
|
||||
ModTime: &zero,
|
||||
},
|
||||
{
|
||||
Path: filepath.Join("a", "b", "d"),
|
||||
Typeflag: tar.TypeDir,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectCreated: []string{
|
||||
"dir/subdir/a",
|
||||
"dir/subdir/a/b",
|
||||
"dir/subdir/a/b/a",
|
||||
"dir/subdir/a/b/b",
|
||||
"dir/subdir/a/b/c",
|
||||
"dir/subdir/a/b/d",
|
||||
},
|
||||
expectNoted: []EnsureParentPath{
|
||||
{
|
||||
Path: "dir",
|
||||
Mode: &ugReadable,
|
||||
Owner: &idtools.IDPair{UID: 1, GID: 1},
|
||||
// ModTime gets updated when we create dir/subdir, can't check it
|
||||
},
|
||||
{
|
||||
Path: "dir/subdir",
|
||||
Mode: &ugReadable,
|
||||
Owner: &idtools.IDPair{UID: 1, GID: 1},
|
||||
ModTime: &zero,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := range testCases {
|
||||
t.Run(testCases[i].description, func(t *testing.T) {
|
||||
testStarted := time.Now()
|
||||
tmpdir := t.TempDir()
|
||||
created, err := Ensure(tmpdir, testCases[i].subdir, testCases[i].options)
|
||||
for _, mkdir := range testCases[i].mkdirs {
|
||||
err := Mkdir(tmpdir, mkdir, MkdirOptions{
|
||||
ModTimeNew: &zero,
|
||||
ChmodNew: &ugReadable,
|
||||
ChownNew: &idtools.IDPair{UID: 1, GID: 1},
|
||||
})
|
||||
require.NoError(t, err, "unexpected error ensuring")
|
||||
}
|
||||
created, noted, err := Ensure(tmpdir, testCases[i].subdir, testCases[i].options)
|
||||
require.NoError(t, err, "unexpected error ensuring")
|
||||
require.EqualValues(t, testCases[i].expected, created, "did not expect these")
|
||||
require.EqualValues(t, testCases[i].expectCreated, created, "did not expect to create these")
|
||||
require.Equal(t, len(testCases[i].expectNoted), len(noted), "noticed the wrong number of things")
|
||||
for n := range noted {
|
||||
require.Equalf(t, testCases[i].expectNoted[n].Path, noted[n].Path, "noticed item %d path", n)
|
||||
if testCases[i].expectNoted[n].Mode != nil {
|
||||
require.Equalf(t, testCases[i].expectNoted[n].Mode.Perm(), noted[n].Mode.Perm(), "noticed item %q mode", noted[n].Path)
|
||||
}
|
||||
if testCases[i].expectNoted[n].Owner != nil {
|
||||
require.Equalf(t, *testCases[i].expectNoted[n].Owner, *noted[n].Owner, "noticed item %q owner", noted[n].Path)
|
||||
}
|
||||
if testCases[i].expectNoted[n].ModTime != nil {
|
||||
require.Equalf(t, testCases[i].expectNoted[n].ModTime.UnixNano(), noted[n].ModTime.UnixNano(), "noticed item %q mtime", noted[n].Path)
|
||||
}
|
||||
}
|
||||
for _, item := range testCases[i].options.Paths {
|
||||
target := filepath.Join(tmpdir, testCases[i].subdir, item.Path)
|
||||
st, err := os.Stat(target)
|
||||
|
@ -2298,7 +2374,7 @@ func testConditionalRemove(t *testing.T) {
|
|||
Chmod: what.mode,
|
||||
})
|
||||
}
|
||||
created, err := Ensure(tmpdir, testCases[i].subdir, create)
|
||||
created, _, err := Ensure(tmpdir, testCases[i].subdir, create)
|
||||
require.NoErrorf(t, err, "unexpected error creating %#v", create)
|
||||
remove := testCases[i].remove
|
||||
for _, what := range created {
|
||||
|
|
|
@ -2119,7 +2119,7 @@ func (b *Builder) createMountTargets(spec *specs.Spec) ([]copier.ConditionalRemo
|
|||
if len(targets.Paths) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
created, err := copier.Ensure(rootfsPath, rootfsPath, targets)
|
||||
created, _, err := copier.Ensure(rootfsPath, rootfsPath, targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue