From 48a493ddb72b8e4a1c8c7e7dd94ba5c0ea75053f Mon Sep 17 00:00:00 2001 From: Nitish Gupta Date: Thu, 12 May 2022 02:22:40 +0530 Subject: [PATCH] bug: Update `getFileFilter` to include parent folders - The `include` directive when specified with a file inside a folder `testdir/testfile` did not pass the parent folder to the builder image, which led to the folder being created by docker instead pack causing the permissions of the folder to be `cnb`. - This PR fixes the include logic to get parent folders and add them to the file filter, so they get created via pack. --- pkg/client/build.go | 27 ++++++- pkg/client/build_test.go | 167 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 2 deletions(-) diff --git a/pkg/client/build.go b/pkg/client/build.go index 2b5540b688..8b7ef47ded 100644 --- a/pkg/client/build.go +++ b/pkg/client/build.go @@ -420,13 +420,36 @@ func getFileFilter(descriptor projectTypes.Descriptor) (func(string) bool, error }, nil } if len(descriptor.Build.Include) > 0 { - includes := ignore.CompileIgnoreLines(descriptor.Build.Include...) + includeEntries := []string{} + for _, entry := range descriptor.Build.Include { + parentFolders, err := getParentDirs(entry) + for _, folder := range parentFolders { + includeEntries = append(includeEntries, "!"+folder+"/*") + includeEntries = append(includeEntries, folder) + } + if err != nil { + return nil, err + } + } + includePatterns := append(includeEntries, descriptor.Build.Include...) + includes := ignore.CompileIgnoreLines(includePatterns...) return includes.MatchesPath, nil } - return nil, nil } +func getParentDirs(file string) ([]string, error) { + parent := filepath.Dir(file) + if parent == filepath.VolumeName(file)+`\` || parent == "/" || parent == "." { + return []string{}, nil + } + parentDirs, err := getParentDirs(parent) + if err != nil { + return nil, err + } + return append(parentDirs, parent), nil +} + func lifecycleImageSupported(builderOS string, lifecycleVersion *builder.Version) bool { return lifecycleVersion.Equal(builder.VersionMustParse(prevLifecycleVersionSupportingImage)) || !lifecycleVersion.LessThan(semver.MustParse(minLifecycleVersionSupportingImage)) diff --git a/pkg/client/build_test.go b/pkg/client/build_test.go index f590984322..eaf4c27619 100644 --- a/pkg/client/build_test.go +++ b/pkg/client/build_test.go @@ -2508,6 +2508,167 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) }) }) + + when("#getFileFilter", func() { + it("project descriptor with include directive", func() { + toCheck := []fileFilterCompare{ + { + inputType: "unnested files", + directiveInput: []string{"file1.txt", "file2.txt"}, + patternOutputMap: map[string]bool{ + "parent": false, + "file1.txt": true, + "file2.txt": true, + "parent/file1.txt": true, + "parent/file2.txt": true, + }, + }, + { + inputType: "unnested directories", + directiveInput: []string{"dir1", "dir2"}, + patternOutputMap: map[string]bool{ + "dir1": true, + "dir2": true, + "file1.txt": false, + "dir1/dir2": true, + "dir1/dir2/folder3": true, + "folder/dir1": true, + "dir1/file1.txt": true, + "folder/folder2/dir2": true, + "dir2/folder/file2.txt": true, + }, + }, + { + inputType: "single level nested files", + directiveInput: []string{"parent/file1.txt", "parent/file2.txt"}, + patternOutputMap: map[string]bool{ + "parent": true, + "file1.txt": false, + "file2.txt": false, + "parent/file1.txt": true, + "parent/file2.txt": true, + }, + }, + { + inputType: "double level nested files", + directiveInput: []string{"parent1/file1.txt", "parent1/parent2/file2.txt"}, + patternOutputMap: map[string]bool{ + "parent1": true, + "parent2": false, + "file1.txt": false, + "file2.txt": false, + "parent1/parent2": true, + "parent1/file1.txt": true, + "parent2/file2.txt": false, + "parent1/parent2/file2.txt": true, + }, + }, + { + inputType: "multi level nested files with same dir names", + directiveInput: []string{"parent/samename/diffname/samename/file1.txt"}, + patternOutputMap: map[string]bool{ + "parent": true, + "samename": false, + "diffname": false, + "file1.txt": false, + "parent/samename": true, + "samename/diffname": false, + "diffname/samename": false, + "samename/file1.txt": false, + "parent/samename/diffname": true, + "samename/diffname/samename": false, + "diffname/samename/file1.txt": false, + "parent/samename/diffname/samename": true, + "parent/samename/diffname/samename/file1.txt": true, + }, + }, + } + + for _, checkEntry := range toCheck { + t.Logf("checking directive input type: %s", checkEntry.inputType) + fileFilter, err := getFileFilter(projectTypes.Descriptor{ + Build: projectTypes.Build{ + Include: checkEntry.directiveInput, + }, + }) + for pattern, expectedOutput := range checkEntry.patternOutputMap { + t.Logf("checking pattern %s", pattern) + h.AssertEq(t, fileFilter(pattern), expectedOutput) + } + h.AssertNil(t, err) + } + }) + + it("project descriptor with exclude directive", func() { + toCheck := []fileFilterCompare{ + { + inputType: "unnested files", + directiveInput: []string{"file1.txt", "file2.txt"}, + patternOutputMap: map[string]bool{ + "parent": true, + "file1.txt": false, + "file2.txt": false, + "parent/file1.txt": false, + "parent/file2.txt": false, + }, + }, + { + inputType: "unnested directories", + directiveInput: []string{"dir1", "dir2"}, + patternOutputMap: map[string]bool{ + "dir1": false, + "dir2": false, + "file1.txt": true, + "dir1/dir2": false, + "dir1/dir2/folder3": false, + "folder/dir1": false, + "dir1/file1.txt": false, + "folder/folder2/dir2": false, + "dir2/folder/file2.txt": false, + }, + }, + { + inputType: "single level nested files", + directiveInput: []string{"parent/file1.txt", "parent/file2.txt"}, + patternOutputMap: map[string]bool{ + "parent": true, + "file1.txt": true, + "file2.txt": true, + "parent/file1.txt": false, + "parent/file2.txt": false, + }, + }, + { + inputType: "double level nested files", + directiveInput: []string{"parent1/file1.txt", "parent1/parent2/file2.txt"}, + patternOutputMap: map[string]bool{ + "parent1": true, + "parent2": true, + "file1.txt": true, + "file2.txt": true, + "parent1/parent2": true, + "parent1/file1.txt": false, + "parent2/file2.txt": true, + "parent1/parent2/file2.txt": false, + }, + }, + } + + for _, checkEntry := range toCheck { + t.Logf("checking directive input type: %s", checkEntry.inputType) + fileFilter, err := getFileFilter(projectTypes.Descriptor{ + Build: projectTypes.Build{ + Exclude: checkEntry.directiveInput, + }, + }) + for pattern, expectedOutput := range checkEntry.patternOutputMap { + t.Logf("checking pattern: %s", pattern) + h.AssertEq(t, fileFilter(pattern), expectedOutput) + } + h.AssertNil(t, err) + } + }) + }) } func diffIDForFile(t *testing.T, path string) string { @@ -2623,3 +2784,9 @@ func (f *executeFailsLifecycle) Execute(_ context.Context, opts build.LifecycleO f.Opts = opts return errors.New("") } + +type fileFilterCompare struct { + inputType string + directiveInput []string + patternOutputMap map[string]bool +}