Skip to content

Commit dd5a878

Browse files
authored
feat: add ability to exclude files when using git file generator (#22734)
Signed-off-by: nitishfy <[email protected]>
1 parent 9675487 commit dd5a878

File tree

18 files changed

+2147
-815
lines changed

18 files changed

+2147
-815
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
apiVersion: argoproj.io/v1alpha1
2+
kind: ApplicationSet
3+
metadata:
4+
name: guestbook
5+
spec:
6+
generators:
7+
- git:
8+
repoURL: https://github.com/argoproj/argo-cd.git
9+
revision: HEAD
10+
files:
11+
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
12+
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
13+
exclude: true
14+
template:
15+
metadata:
16+
name: '{{cluster.name}}-guestbook'
17+
spec:
18+
project: default
19+
source:
20+
repoURL: https://github.com/argoproj/argo-cd.git
21+
targetRevision: HEAD
22+
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
23+
destination:
24+
server: https://kubernetes.default.svc
25+
#server: '{{cluster.address}}'
26+
namespace: guestbook
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: argoproj.io/v1alpha1
2+
kind: ApplicationSet
3+
metadata:
4+
name: guestbook
5+
spec:
6+
goTemplate: true
7+
goTemplateOptions: ["missingkey=error"]
8+
generators:
9+
- git:
10+
repoURL: https://github.com/argoproj/argo-cd.git
11+
revision: HEAD
12+
files:
13+
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
14+
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
15+
exclude: true
16+
template:
17+
metadata:
18+
name: '{{.cluster.name}}-guestbook'
19+
spec:
20+
project: default
21+
source:
22+
repoURL: https://github.com/argoproj/argo-cd.git
23+
targetRevision: HEAD
24+
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
25+
destination:
26+
server: https://kubernetes.default.svc
27+
namespace: guestbook

applicationset/generators/git.go

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type GitGenerator struct {
2828
namespace string
2929
}
3030

31+
// NewGitGenerator creates a new instance of Git Generator
3132
func NewGitGenerator(repos services.Repos, namespace string) Generator {
3233
g := &GitGenerator{
3334
repos: repos,
@@ -37,20 +38,26 @@ func NewGitGenerator(repos services.Repos, namespace string) Generator {
3738
return g
3839
}
3940

41+
// GetTemplate returns the ApplicationSetTemplate associated with the Git generator
42+
// from the provided ApplicationSetGenerator. This template defines how each
43+
// generated Argo CD Application should be rendered.
4044
func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
4145
return &appSetGenerator.Git.Template
4246
}
4347

48+
// GetRequeueAfter returns the duration after which the Git generator should be
49+
// requeued for reconciliation. If RequeueAfterSeconds is set in the generator spec,
50+
// it uses that value. Otherwise, it falls back to a default requeue interval (3 minutes).
4451
func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
45-
// Return a requeue default of 3 minutes, if no default is specified.
46-
4752
if appSetGenerator.Git.RequeueAfterSeconds != nil {
4853
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second
4954
}
5055

5156
return getDefaultRequeueAfter()
5257
}
5358

59+
// GenerateParams generates a list of parameter maps for the ApplicationSet by evaluating the Git generator's configuration.
60+
// It supports both directory-based and file-based Git generators.
5461
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
5562
if appSetGenerator == nil {
5663
return nil, ErrEmptyAppSetGenerator
@@ -67,6 +74,7 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
6774
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
6875
// but git generator cannot be called without verifying the commit signature.
6976
// In this case, we skip the signature verification.
77+
// If the project is templated, we skip the commit verification
7078
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
7179
project := appSet.Spec.Template.Spec.Project
7280
appProject := &argoprojiov1alpha1.AppProject{}
@@ -102,8 +110,10 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
102110
return res, nil
103111
}
104112

113+
// generateParamsForGitDirectories generates parameters for an ApplicationSet using a directory-based Git generator.
114+
// It fetches all directories from the given Git repository and revision, optionally using a revision cache and verifying commits.
115+
// It then filters the directories based on the generator's configuration and renders parameters for the resulting applications
105116
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
106-
// Directories, not files
107117
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, noRevisionCache, verifyCommit)
108118
if err != nil {
109119
return nil, fmt.Errorf("error getting directories from repo: %w", err)
@@ -127,41 +137,88 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
127137
return res, nil
128138
}
129139

140+
// generateParamsForGitFiles generates parameters for an ApplicationSet using a file-based Git generator.
141+
// It retrieves and processes specified files from the Git repository, supporting both YAML and JSON formats,
142+
// and returns a list of parameter maps extracted from the content.
130143
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
131-
// Get all files that match the requested path string, removing duplicates
132-
allFiles := make(map[string][]byte)
133-
for _, requestedPath := range appSetGenerator.Git.Files {
134-
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, requestedPath.Path, noRevisionCache, verifyCommit)
144+
// fileContentMap maps absolute file paths to their byte content
145+
fileContentMap := make(map[string][]byte)
146+
var includePatterns []string
147+
var excludePatterns []string
148+
149+
for _, req := range appSetGenerator.Git.Files {
150+
if req.Exclude {
151+
excludePatterns = append(excludePatterns, req.Path)
152+
} else {
153+
includePatterns = append(includePatterns, req.Path)
154+
}
155+
}
156+
157+
// Fetch all files from include patterns
158+
for _, includePattern := range includePatterns {
159+
retrievedFiles, err := g.repos.GetFiles(
160+
context.TODO(),
161+
appSetGenerator.Git.RepoURL,
162+
appSetGenerator.Git.Revision,
163+
project,
164+
includePattern,
165+
noRevisionCache,
166+
verifyCommit,
167+
)
135168
if err != nil {
136169
return nil, err
137170
}
138-
for filePath, content := range files {
139-
allFiles[filePath] = content
171+
for absPath, content := range retrievedFiles {
172+
fileContentMap[absPath] = content
140173
}
141174
}
142175

143-
// Extract the unduplicated map into a list, and sort by path to ensure a deterministic
144-
// processing order in the subsequent step
145-
allPaths := []string{}
146-
for path := range allFiles {
147-
allPaths = append(allPaths, path)
176+
// Now remove files matching any exclude pattern
177+
for _, excludePattern := range excludePatterns {
178+
matchingFiles, err := g.repos.GetFiles(
179+
context.TODO(),
180+
appSetGenerator.Git.RepoURL,
181+
appSetGenerator.Git.Revision,
182+
project,
183+
excludePattern,
184+
noRevisionCache,
185+
verifyCommit,
186+
)
187+
if err != nil {
188+
return nil, err
189+
}
190+
for absPath := range matchingFiles {
191+
// if the file doesn't exist already and you try to delete it from the map
192+
// the operation is a no-op. It’s safe and doesn't return an error or panic.
193+
// Hence, we can simply try to delete the file from the path without checking
194+
// if that file already exists in the map.
195+
delete(fileContentMap, absPath)
196+
}
148197
}
149-
sort.Strings(allPaths)
150198

151-
// Generate params from each path, and return
152-
res := []map[string]any{}
153-
for _, path := range allPaths {
199+
// Get a sorted list of file paths to ensure deterministic processing order
200+
var filePaths []string
201+
for path := range fileContentMap {
202+
filePaths = append(filePaths, path)
203+
}
204+
sort.Strings(filePaths)
205+
206+
var allParams []map[string]any
207+
for _, filePath := range filePaths {
154208
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
155-
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
209+
paramsFromFileArray, err := g.generateParamsFromGitFile(filePath, fileContentMap[filePath], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
156210
if err != nil {
157-
return nil, fmt.Errorf("unable to process file '%s': %w", path, err)
211+
return nil, fmt.Errorf("unable to process file '%s': %w", filePath, err)
158212
}
159-
160-
res = append(res, paramsArray...)
213+
allParams = append(allParams, paramsFromFileArray...)
161214
}
162-
return res, nil
215+
216+
return allParams, nil
163217
}
164218

219+
// generateParamsFromGitFile parses the content of a Git-tracked file and generates a slice of parameter maps.
220+
// The file can contain a single YAML/JSON object or an array of such objects. Depending on the useGoTemplate flag,
221+
// it either preserves structure for Go templating or flattens the objects for use as plain key-value parameters.
165222
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]any, error) {
166223
objectsFound := []map[string]any{}
167224

@@ -238,8 +295,10 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
238295
return res, nil
239296
}
240297

298+
// filterApps filters the list of all application paths based on inclusion and exclusion rules
299+
// defined in GitDirectoryGeneratorItems. Each item can either include or exclude matching paths.
241300
func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
242-
res := []string{}
301+
var res []string
243302
for _, appPath := range allPaths {
244303
appInclude := false
245304
appExclude := false
@@ -266,6 +325,9 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
266325
return res
267326
}
268327

328+
// generateParamsFromApps generates a list of parameter maps based on the given app paths.
329+
// Each app path is converted into a parameter object with path metadata (basename, segments, etc.).
330+
// It supports both Go templates and flat key-value parameters.
269331
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]any, error) {
270332
res := make([]map[string]any, len(requestedApps))
271333
for i, a := range requestedApps {
@@ -308,6 +370,7 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
308370
return res, nil
309371
}
310372

373+
// resolveProjectName resolves a project name whether templated or not
311374
func resolveProjectName(project string) string {
312375
if strings.Contains(project, "{{") {
313376
return ""

0 commit comments

Comments
 (0)