Repository: rancher/fleet
Pull request #2617 (2024-07-08 11:03:02)

Adds support for folder/* patterns in .fleetignore
As of now, the `folder/*` was not supported by Fleet.

This PR adds support for it, giving the option to ignore folders (not files) with a given name.

For example:

This folder structure:

```
root
   dir1
     test
       file1
       file2
   file3
test
file4
```

with the following `.fleetignore` file:
```
test/*
```

would produce the following recource list:
```
root/file3
root/test
root/file4
```

`test` folder would be ignored but `test` file would not.

The difference with a `.fleetignore` file like this:
```
test
```

is that both the `test` flile and `dir1/test` folder would be ignored.

Refers to: https://github.com/rancher/fleet/issues/2175




Patch:
diff --git a/internal/bundlereader/loaddirectory.go b/internal/bundlereader/loaddirectory.go
index 3944de9e..f63e7f27 100644
--- a/internal/bundlereader/loaddirectory.go
+++ b/internal/bundlereader/loaddirectory.go
@@ -36,18 +36,28 @@ type ignoreTree struct {
 }
 
 // isIgnored checks whether any path within xt matches path, and returns true if so.
-func (xt *ignoreTree) isIgnored(path string) (bool, error) {
+func (xt *ignoreTree) isIgnored(path string, info fs.DirEntry) (bool, error) {
 	steps := xt.findNode(path, false, nil)
 
 	for _, step := range steps {
 		for _, ignoredPath := range step.ignoredPaths {
-			toIgnore, err := filepath.Match(ignoredPath, filepath.Base(path))
-			if err != nil {
-				return false, err
-			}
+			if isAllFilesInDirPattern(ignoredPath) {
+				// ignores a folder
+				if info.IsDir() {
+					dirNameInPattern := strings.TrimSuffix(ignoredPath, "/*")
+					if dirNameInPattern == filepath.Base(path) {
+						return true, nil
+					}
+				}
+			} else {
+				toIgnore, err := filepath.Match(ignoredPath, filepath.Base(path))
+				if err != nil {
+					return false, err
+				}
 
-			if toIgnore {
-				return true, nil
+				if toIgnore {
+					return true, nil
+				}
 			}
 		}
 	}
@@ -55,6 +65,11 @@ func (xt *ignoreTree) isIgnored(path string) (bool, error) {
 	return false, nil
 }
 
+func isAllFilesInDirPattern(path string) bool {
+	match, _ := regexp.MatchString("^.+/\\*", path)
+	return match
+}
+
 // addNode reads a `.fleetignore` file in dir's root and adds each of its entries to ignored paths for dir.
 // Returns an error if a `.fleetignore` file exists for dir but reading it fails.
 func (xt *ignoreTree) addNode(dir string) error {
@@ -260,7 +275,7 @@ func GetContent(ctx context.Context, base, source, version string, auth Auth, di
 			return err
 		}
 
-		ignore, err := ignoredPaths.isIgnored(path)
+		ignore, err := ignoredPaths.isIgnored(path, info)
 		if err != nil {
 			return err
 		}


Test patch:
diff --git a/internal/bundlereader/loaddirectory_test.go b/internal/bundlereader/loaddirectory_test.go
index b459f0df..5e0bc8a6 100644
--- a/internal/bundlereader/loaddirectory_test.go
+++ b/internal/bundlereader/loaddirectory_test.go
@@ -321,6 +321,207 @@ func TestGetContent(t *testing.T) {
 				"bar/something2.yaml": []byte("something2"),
 			},
 		},
+		{
+			name: "root .fleetignore contains folder/* entries",
+			directoryStructure: fsNode{
+				isDir: true,
+				name:  "root-fleetignore-all-files-in-dir",
+				children: []fsNode{
+					{
+						name:     "something.yaml",
+						contents: "foo",
+					},
+					{
+						name:     ".fleetignore",
+						contents: "foo/*\n",
+					},
+					{
+						name:  "foo",
+						isDir: true,
+						children: []fsNode{
+							{
+								name:     "ignore-always.yaml",
+								contents: "will be ignored",
+							},
+							{
+								name:     "something.yaml",
+								contents: "will be ignored",
+							},
+						},
+					},
+					{
+						name:  "bar",
+						isDir: true,
+						children: []fsNode{
+							{
+								name:     "something.yaml",
+								contents: "something",
+							},
+							{
+								name:     "something2.yaml",
+								contents: "something2",
+							},
+							{
+								name:  "foo",
+								isDir: true,
+								children: []fsNode{
+									{
+										name:     "ignore.yaml",
+										contents: "will be ignored",
+									},
+									{
+										name:     "ignore2.yaml",
+										contents: "will be ignored",
+									},
+									{
+										name:     "something.yaml",
+										contents: "will be ignored",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedFiles: map[string][]byte{
+				"something.yaml":      []byte("foo"),
+				"bar/something.yaml":  []byte("something"),
+				"bar/something2.yaml": []byte("something2"),
+			},
+		},
+		{
+			name: "non root .fleetignore contains folder/* entries",
+			directoryStructure: fsNode{
+				isDir: true,
+				name:  "non-root-fleetignore-all-files-in-dir",
+				children: []fsNode{
+					{
+						name:     "something.yaml",
+						contents: "foo",
+					},
+					{
+						name:  "foo",
+						isDir: true,
+						children: []fsNode{
+							{
+								name:     "something1.yaml",
+								contents: "something1",
+							},
+							{
+								name:     "something2.yaml",
+								contents: "something2",
+							},
+						},
+					},
+					{
+						name:  "bar",
+						isDir: true,
+						children: []fsNode{
+							{
+								name:     "something.yaml",
+								contents: "something",
+							},
+							{
+								name:     "something2.yaml",
+								contents: "something2",
+							},
+							{
+								name:     ".fleetignore",
+								contents: "foo/*\n",
+							},
+							{
+								name:  "foo",
+								isDir: true,
+								children: []fsNode{
+									{
+										name:     "ignore.yaml",
+										contents: "will be ignored",
+									},
+									{
+										name:     "ignore2.yaml",
+										contents: "will be ignored",
+									},
+									{
+										name:     "something.yaml",
+										contents: "will be ignored",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedFiles: map[string][]byte{
+				"something.yaml":      []byte("foo"),
+				"foo/something1.yaml": []byte("something1"),
+				"foo/something2.yaml": []byte("something2"),
+				"bar/something.yaml":  []byte("something"),
+				"bar/something2.yaml": []byte("something2"),
+			},
+		},
+		{
+			name: ".fleetignore contains folder/* entry does not apply to files",
+			directoryStructure: fsNode{
+				isDir: true,
+				name:  "fleetignore-all-files-in-dir-does-not-apply-to-files",
+				children: []fsNode{
+					{
+						name:     "something.yaml",
+						contents: "foo",
+					},
+					{
+						name:     ".fleetignore",
+						contents: "foo/*\n",
+					},
+					{
+						name:     "foo",
+						contents: "everybody was a kung-foo fighting",
+					},
+					{
+						name:  "bar",
+						isDir: true,
+						children: []fsNode{
+							{
+								name:     "something.yaml",
+								contents: "something",
+							},
+							{
+								name:     "something2.yaml",
+								contents: "something2",
+							},
+							{
+								name:     ".fleetignore",
+								contents: "foo/*\n",
+							},
+							{
+								name:  "foo",
+								isDir: true,
+								children: []fsNode{
+									{
+										name:     "ignore.yaml",
+										contents: "will be ignored",
+									},
+									{
+										name:     "ignore2.yaml",
+										contents: "will be ignored",
+									},
+									{
+										name:     "something.yaml",
+										contents: "will be ignored",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedFiles: map[string][]byte{
+				"something.yaml":      []byte("foo"),
+				"foo":                 []byte("everybody was a kung-foo fighting"),
+				"bar/something.yaml":  []byte("something"),
+				"bar/something2.yaml": []byte("something2"),
+			},
+		},
 	}
 
 	base, err := os.MkdirTemp("", "test-fleet")