github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/generators/git.go (about) 1 package generators 2 3 import ( 4 "context" 5 "fmt" 6 "path" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/jeremywohl/flatten" 13 log "github.com/sirupsen/logrus" 14 "sigs.k8s.io/yaml" 15 16 "github.com/argoproj/argo-cd/v2/applicationset/services" 17 "github.com/argoproj/argo-cd/v2/applicationset/utils" 18 argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 19 ) 20 21 var _ Generator = (*GitGenerator)(nil) 22 23 type GitGenerator struct { 24 repos services.Repos 25 } 26 27 func NewGitGenerator(repos services.Repos) Generator { 28 g := &GitGenerator{ 29 repos: repos, 30 } 31 return g 32 } 33 34 func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { 35 return &appSetGenerator.Git.Template 36 } 37 38 func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { 39 40 // Return a requeue default of 3 minutes, if no default is specified. 41 42 if appSetGenerator.Git.RequeueAfterSeconds != nil { 43 return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second 44 } 45 46 return DefaultRequeueAfterSeconds 47 } 48 49 func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) { 50 51 if appSetGenerator == nil { 52 return nil, EmptyAppSetGeneratorError 53 } 54 55 if appSetGenerator.Git == nil { 56 return nil, EmptyAppSetGeneratorError 57 } 58 59 noRevisionCache := appSet.RefreshRequired() 60 61 var err error 62 var res []map[string]interface{} 63 if len(appSetGenerator.Git.Directories) != 0 { 64 res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) 65 } else if len(appSetGenerator.Git.Files) != 0 { 66 res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) 67 } else { 68 return nil, EmptyAppSetGeneratorError 69 } 70 if err != nil { 71 return nil, fmt.Errorf("error generating params from git: %w", err) 72 } 73 74 return res, nil 75 } 76 77 func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { 78 79 // Directories, not files 80 allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache) 81 if err != nil { 82 return nil, fmt.Errorf("error getting directories from repo: %w", err) 83 } 84 85 log.WithFields(log.Fields{ 86 "allPaths": allPaths, 87 "total": len(allPaths), 88 "repoURL": appSetGenerator.Git.RepoURL, 89 "revision": appSetGenerator.Git.Revision, 90 "pathParamPrefix": appSetGenerator.Git.PathParamPrefix, 91 }).Info("applications result from the repo service") 92 93 requestedApps := g.filterApps(appSetGenerator.Git.Directories, allPaths) 94 95 res, err := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate, goTemplateOptions) 96 if err != nil { 97 return nil, fmt.Errorf("error generating params from apps: %w", err) 98 } 99 100 return res, nil 101 } 102 103 func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { 104 105 // Get all files that match the requested path string, removing duplicates 106 allFiles := make(map[string][]byte) 107 for _, requestedPath := range appSetGenerator.Git.Files { 108 files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache) 109 if err != nil { 110 return nil, err 111 } 112 for filePath, content := range files { 113 allFiles[filePath] = content 114 } 115 } 116 117 // Extract the unduplicated map into a list, and sort by path to ensure a deterministic 118 // processing order in the subsequent step 119 allPaths := []string{} 120 for path := range allFiles { 121 allPaths = append(allPaths, path) 122 } 123 sort.Strings(allPaths) 124 125 // Generate params from each path, and return 126 res := []map[string]interface{}{} 127 for _, path := range allPaths { 128 129 // A JSON / YAML file path can contain multiple sets of parameters (ie it is an array) 130 paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix) 131 if err != nil { 132 return nil, fmt.Errorf("unable to process file '%s': %v", path, err) 133 } 134 135 res = append(res, paramsArray...) 136 } 137 return res, nil 138 } 139 140 func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]interface{}, error) { 141 objectsFound := []map[string]interface{}{} 142 143 // First, we attempt to parse as an array 144 err := yaml.Unmarshal(fileContent, &objectsFound) 145 if err != nil { 146 // If unable to parse as an array, attempt to parse as a single object 147 singleObj := make(map[string]interface{}) 148 err = yaml.Unmarshal(fileContent, &singleObj) 149 if err != nil { 150 return nil, fmt.Errorf("unable to parse file: %v", err) 151 } 152 objectsFound = append(objectsFound, singleObj) 153 } else if len(objectsFound) == 0 { 154 // If file is valid but empty, add a default empty item 155 objectsFound = append(objectsFound, map[string]interface{}{}) 156 } 157 158 res := []map[string]interface{}{} 159 160 for _, objectFound := range objectsFound { 161 162 params := map[string]interface{}{} 163 164 if useGoTemplate { 165 for k, v := range objectFound { 166 params[k] = v 167 } 168 169 paramPath := map[string]interface{}{} 170 171 paramPath["path"] = path.Dir(filePath) 172 paramPath["basename"] = path.Base(paramPath["path"].(string)) 173 paramPath["filename"] = path.Base(filePath) 174 paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(paramPath["path"].(string))) 175 paramPath["filenameNormalized"] = utils.SanitizeName(path.Base(paramPath["filename"].(string))) 176 paramPath["segments"] = strings.Split(paramPath["path"].(string), "/") 177 if pathParamPrefix != "" { 178 params[pathParamPrefix] = map[string]interface{}{"path": paramPath} 179 } else { 180 params["path"] = paramPath 181 } 182 } else { 183 flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle) 184 if err != nil { 185 return nil, fmt.Errorf("error flattening object: %w", err) 186 } 187 for k, v := range flat { 188 params[k] = fmt.Sprintf("%v", v) 189 } 190 pathParamName := "path" 191 if pathParamPrefix != "" { 192 pathParamName = pathParamPrefix + "." + pathParamName 193 } 194 params[pathParamName] = path.Dir(filePath) 195 params[pathParamName+".basename"] = path.Base(params[pathParamName].(string)) 196 params[pathParamName+".filename"] = path.Base(filePath) 197 params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName].(string))) 198 params[pathParamName+".filenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName+".filename"].(string))) 199 for k, v := range strings.Split(params[pathParamName].(string), "/") { 200 if len(v) > 0 { 201 params[pathParamName+"["+strconv.Itoa(k)+"]"] = v 202 } 203 } 204 } 205 206 err := appendTemplatedValues(values, params, useGoTemplate, goTemplateOptions) 207 if err != nil { 208 return nil, fmt.Errorf("failed to append templated values: %w", err) 209 } 210 211 res = append(res, params) 212 } 213 214 return res, nil 215 } 216 217 func (g *GitGenerator) filterApps(Directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string { 218 res := []string{} 219 for _, appPath := range allPaths { 220 appInclude := false 221 appExclude := false 222 // Iterating over each appPath and check whether directories object has requestedPath that matches the appPath 223 for _, requestedPath := range Directories { 224 match, err := path.Match(requestedPath.Path, appPath) 225 if err != nil { 226 log.WithError(err).WithField("requestedPath", requestedPath). 227 WithField("appPath", appPath).Error("error while matching appPath to requestedPath") 228 continue 229 } 230 if match && !requestedPath.Exclude { 231 appInclude = true 232 } 233 if match && requestedPath.Exclude { 234 appExclude = true 235 } 236 } 237 // Whenever there is a path with exclude: true it wont be included, even if it is included in a different path pattern 238 if appInclude && !appExclude { 239 res = append(res, appPath) 240 } 241 } 242 return res 243 } 244 245 func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { 246 res := make([]map[string]interface{}, len(requestedApps)) 247 for i, a := range requestedApps { 248 249 params := make(map[string]interface{}, 5) 250 251 if useGoTemplate { 252 paramPath := map[string]interface{}{} 253 paramPath["path"] = a 254 paramPath["basename"] = path.Base(a) 255 paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(a)) 256 paramPath["segments"] = strings.Split(paramPath["path"].(string), "/") 257 if appSetGenerator.Git.PathParamPrefix != "" { 258 params[appSetGenerator.Git.PathParamPrefix] = map[string]interface{}{"path": paramPath} 259 } else { 260 params["path"] = paramPath 261 } 262 } else { 263 pathParamName := "path" 264 if appSetGenerator.Git.PathParamPrefix != "" { 265 pathParamName = appSetGenerator.Git.PathParamPrefix + "." + pathParamName 266 } 267 params[pathParamName] = a 268 params[pathParamName+".basename"] = path.Base(a) 269 params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a)) 270 for k, v := range strings.Split(params[pathParamName].(string), "/") { 271 if len(v) > 0 { 272 params[pathParamName+"["+strconv.Itoa(k)+"]"] = v 273 } 274 } 275 } 276 277 err := appendTemplatedValues(appSetGenerator.Git.Values, params, useGoTemplate, goTemplateOptions) 278 if err != nil { 279 return nil, fmt.Errorf("failed to append templated values: %w", err) 280 } 281 282 res[i] = params 283 } 284 285 return res, nil 286 }