github.com/argoproj/argo-cd/v3@v3.2.1/util/argo/argo.go (about) 1 package argo 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "regexp" 9 "slices" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/argoproj/gitops-engine/pkg/cache" 15 "github.com/argoproj/gitops-engine/pkg/sync/common" 16 "github.com/argoproj/gitops-engine/pkg/utils/kube" 17 "github.com/r3labs/diff/v3" 18 log "github.com/sirupsen/logrus" 19 "google.golang.org/grpc/codes" 20 "google.golang.org/grpc/status" 21 apierrors "k8s.io/apimachinery/pkg/api/errors" 22 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/apimachinery/pkg/types" 26 27 "github.com/argoproj/argo-cd/v3/util/gpg" 28 29 argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 30 "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/typed/application/v1alpha1" 31 applicationsv1 "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1" 32 "github.com/argoproj/argo-cd/v3/reposerver/apiclient" 33 "github.com/argoproj/argo-cd/v3/util/db" 34 "github.com/argoproj/argo-cd/v3/util/glob" 35 utilio "github.com/argoproj/argo-cd/v3/util/io" 36 "github.com/argoproj/argo-cd/v3/util/settings" 37 ) 38 39 const ( 40 ErrDestinationMissing = "Destination server missing from app spec" 41 ) 42 43 var ErrAnotherOperationInProgress = status.Errorf(codes.FailedPrecondition, "another operation is already in progress") 44 45 // AugmentSyncMsg enrich the K8s message with user-relevant information 46 func AugmentSyncMsg(res common.ResourceSyncResult, apiResourceInfoGetter func() ([]kube.APIResourceInfo, error)) (string, error) { 47 if strings.Contains(res.Message, "the server could not find the requested resource") { 48 resource, err := getAPIResourceInfo(res.ResourceKey.Group, res.ResourceKey.Kind, apiResourceInfoGetter) 49 if err != nil { 50 return "", fmt.Errorf("failed to get API resource info for group %q and kind %q: %w", res.ResourceKey.Group, res.ResourceKey.Kind, err) 51 } 52 if resource == nil { 53 return fmt.Sprintf("The Kubernetes API could not find %s/%s for requested resource %s/%s. Make sure the %q CRD is installed on the destination cluster.", res.ResourceKey.Group, res.ResourceKey.Kind, res.ResourceKey.Namespace, res.ResourceKey.Name, res.ResourceKey.Kind), nil 54 } 55 return fmt.Sprintf("The Kubernetes API could not find version %q of %s/%s for requested resource %s/%s. Version %q of %s/%s is installed on the destination cluster.", res.Version, res.ResourceKey.Group, res.ResourceKey.Kind, res.ResourceKey.Namespace, res.ResourceKey.Name, resource.GroupVersionResource.Version, resource.GroupKind.Group, resource.GroupKind.Kind), nil 56 } 57 // Check if the message contains "metadata.annotation: Too long" 58 if strings.Contains(res.Message, "metadata.annotations: Too long: must have at most 262144 bytes") { 59 return res.Message + " \n -Additional Info: This error usually means that you are trying to add a large resource on client side. Consider using Server-side apply or syncing with replace enabled. Note: Syncing with Replace enabled is potentially destructive as it may cause resource deletion and re-creation.", nil 60 } 61 62 return res.Message, nil 63 } 64 65 // getAPIResourceInfo gets Kubernetes API resource info for the given group and kind. If there's a matching resource 66 // group _and_ kind, it will return the resource info. If there's a matching kind but no matching group, it will 67 // return the first resource info that matches the kind. If there's no matching kind, it will return nil. 68 func getAPIResourceInfo(group, kind string, getAPIResourceInfo func() ([]kube.APIResourceInfo, error)) (*kube.APIResourceInfo, error) { 69 apiResources, err := getAPIResourceInfo() 70 if err != nil { 71 return nil, fmt.Errorf("failed to get API resource info: %w", err) 72 } 73 74 for _, r := range apiResources { 75 if r.GroupKind.Group == group && r.GroupKind.Kind == kind { 76 return &r, nil 77 } 78 } 79 80 for _, r := range apiResources { 81 if r.GroupKind.Kind == kind { 82 return &r, nil 83 } 84 } 85 86 return nil, nil 87 } 88 89 // FormatAppConditions returns string representation of give app condition list 90 func FormatAppConditions(conditions []argoappv1.ApplicationCondition) string { 91 formattedConditions := make([]string, 0) 92 for _, condition := range conditions { 93 formattedConditions = append(formattedConditions, fmt.Sprintf("%s: %s", condition.Type, condition.Message)) 94 } 95 return strings.Join(formattedConditions, ";") 96 } 97 98 // FilterByProjects returns applications which belongs to the specified project 99 func FilterByProjects(apps []argoappv1.Application, projects []string) []argoappv1.Application { 100 if len(projects) == 0 { 101 return apps 102 } 103 projectsMap := make(map[string]bool) 104 for i := range projects { 105 projectsMap[projects[i]] = true 106 } 107 items := make([]argoappv1.Application, 0) 108 for i := 0; i < len(apps); i++ { 109 a := apps[i] 110 if _, ok := projectsMap[a.Spec.GetProject()]; ok { 111 items = append(items, a) 112 } 113 } 114 return items 115 } 116 117 // FilterByProjectsP returns application pointers which belongs to the specified project 118 func FilterByProjectsP(apps []*argoappv1.Application, projects []string) []*argoappv1.Application { 119 if len(projects) == 0 { 120 return apps 121 } 122 projectsMap := make(map[string]bool) 123 for i := range projects { 124 projectsMap[projects[i]] = true 125 } 126 items := make([]*argoappv1.Application, 0) 127 for i := 0; i < len(apps); i++ { 128 a := apps[i] 129 if _, ok := projectsMap[a.Spec.GetProject()]; ok { 130 items = append(items, a) 131 } 132 } 133 return items 134 } 135 136 // FilterAppSetsByProjects returns applications which belongs to the specified project 137 func FilterAppSetsByProjects(appsets []argoappv1.ApplicationSet, projects []string) []argoappv1.ApplicationSet { 138 if len(projects) == 0 { 139 return appsets 140 } 141 projectsMap := make(map[string]bool) 142 for i := range projects { 143 projectsMap[projects[i]] = true 144 } 145 items := make([]argoappv1.ApplicationSet, 0) 146 for i := 0; i < len(appsets); i++ { 147 a := appsets[i] 148 if _, ok := projectsMap[a.Spec.Template.Spec.GetProject()]; ok { 149 items = append(items, a) 150 } 151 } 152 return items 153 } 154 155 // FilterByRepo returns an application 156 func FilterByRepo(apps []argoappv1.Application, repo string) []argoappv1.Application { 157 if repo == "" { 158 return apps 159 } 160 items := make([]argoappv1.Application, 0) 161 for i := 0; i < len(apps); i++ { 162 if apps[i].Spec.GetSource().RepoURL == repo { 163 items = append(items, apps[i]) 164 } 165 } 166 return items 167 } 168 169 // FilterByRepoP returns application pointers 170 func FilterByRepoP(apps []*argoappv1.Application, repo string) []*argoappv1.Application { 171 if repo == "" { 172 return apps 173 } 174 items := make([]*argoappv1.Application, 0) 175 for i := 0; i < len(apps); i++ { 176 if apps[i].Spec.GetSource().RepoURL == repo { 177 items = append(items, apps[i]) 178 } 179 } 180 return items 181 } 182 183 // FilterByCluster returns an application 184 func FilterByCluster(apps []argoappv1.Application, cluster string) []argoappv1.Application { 185 if cluster == "" { 186 return apps 187 } 188 items := make([]argoappv1.Application, 0) 189 for i := 0; i < len(apps); i++ { 190 if apps[i].Spec.Destination.Server == cluster || apps[i].Spec.Destination.Name == cluster { 191 items = append(items, apps[i]) 192 } 193 } 194 return items 195 } 196 197 // FilterByName returns an application 198 func FilterByName(apps []argoappv1.Application, name string) ([]argoappv1.Application, error) { 199 if name == "" { 200 return apps, nil 201 } 202 items := make([]argoappv1.Application, 0) 203 for i := 0; i < len(apps); i++ { 204 if apps[i].Name == name { 205 items = append(items, apps[i]) 206 return items, nil 207 } 208 } 209 return items, status.Errorf(codes.NotFound, "application '%s' not found", name) 210 } 211 212 // FilterByNameP returns pointer applications 213 // This function is for the changes in #12985. 214 func FilterByNameP(apps []*argoappv1.Application, name string) []*argoappv1.Application { 215 if name == "" { 216 return apps 217 } 218 items := make([]*argoappv1.Application, 0) 219 for i := 0; i < len(apps); i++ { 220 if apps[i].Name == name { 221 items = append(items, apps[i]) 222 return items 223 } 224 } 225 return items 226 } 227 228 // RefreshApp updates the refresh annotation of an application to coerce the controller to process it 229 func RefreshApp(appIf v1alpha1.ApplicationInterface, name string, refreshType argoappv1.RefreshType, hydrate bool) (*argoappv1.Application, error) { 230 metadata := map[string]any{ 231 "metadata": map[string]any{ 232 "annotations": map[string]string{ 233 argoappv1.AnnotationKeyRefresh: string(refreshType), 234 }, 235 }, 236 } 237 if hydrate { 238 metadata["metadata"].(map[string]any)["annotations"].(map[string]string)[argoappv1.AnnotationKeyHydrate] = string(argoappv1.HydrateTypeNormal) 239 } 240 241 var err error 242 patch, err := json.Marshal(metadata) 243 if err != nil { 244 return nil, fmt.Errorf("error marshaling metadata: %w", err) 245 } 246 for attempt := 0; attempt < 5; attempt++ { 247 app, err := appIf.Patch(context.Background(), name, types.MergePatchType, patch, metav1.PatchOptions{}) 248 if err == nil { 249 log.Infof("Requested app '%s' refresh", name) 250 return app.DeepCopy(), nil 251 } 252 if !apierrors.IsConflict(err) { 253 return nil, fmt.Errorf("error patching annotations in application %q: %w", name, err) 254 } 255 time.Sleep(100 * time.Millisecond) 256 } 257 return nil, err 258 } 259 260 func TestRepoWithKnownType(ctx context.Context, repoClient apiclient.RepoServerServiceClient, repo *argoappv1.Repository, isHelm bool, isHelmOci bool, isOCI bool) error { 261 repo = repo.DeepCopy() 262 switch { 263 case isHelm: 264 repo.Type = "helm" 265 case isOCI: 266 repo.Type = "oci" 267 case repo.Type != "oci": 268 repo.Type = "git" 269 } 270 repo.EnableOCI = repo.EnableOCI || isHelmOci 271 272 _, err := repoClient.TestRepository(ctx, &apiclient.TestRepositoryRequest{ 273 Repo: repo, 274 }) 275 if err != nil { 276 return fmt.Errorf("repo client error while testing repository: %w", err) 277 } 278 279 return nil 280 } 281 282 // ValidateRepo validates the repository specified in application spec. Following is checked: 283 // * the repository is accessible 284 // * the path contains valid manifests 285 // * there are parameters of only one app source type 286 // 287 // The plugins parameter is no longer used. It is kept for compatibility with the old signature until Argo CD v3.0. 288 func ValidateRepo( 289 ctx context.Context, 290 app *argoappv1.Application, 291 repoClientset apiclient.Clientset, 292 db db.ArgoDB, 293 kubectl kube.Kubectl, 294 proj *argoappv1.AppProject, 295 settingsMgr *settings.SettingsManager, 296 ) ([]argoappv1.ApplicationCondition, error) { 297 spec := &app.Spec 298 299 conditions := make([]argoappv1.ApplicationCondition, 0) 300 301 // Test the repo 302 conn, repoClient, err := repoClientset.NewRepoServerClient() 303 if err != nil { 304 return nil, fmt.Errorf("error instantiating new repo server client: %w", err) 305 } 306 defer utilio.Close(conn) 307 308 helmOptions, err := settingsMgr.GetHelmSettings() 309 if err != nil { 310 return nil, fmt.Errorf("error getting helm settings: %w", err) 311 } 312 313 helmRepos, err := db.ListHelmRepositories(ctx) 314 if err != nil { 315 return nil, fmt.Errorf("error listing helm repos: %w", err) 316 } 317 permittedHelmRepos, err := GetPermittedRepos(proj, helmRepos) 318 if err != nil { 319 return nil, fmt.Errorf("error getting permitted repos: %w", err) 320 } 321 helmRepositoryCredentials, err := db.GetAllHelmRepositoryCredentials(ctx) 322 if err != nil { 323 return nil, fmt.Errorf("error getting helm repo creds: %w", err) 324 } 325 ociRepos, err := db.ListOCIRepositories(ctx) 326 if err != nil { 327 return nil, fmt.Errorf("failed to list oci repositories: %w", err) 328 } 329 permittedOCIRepos, err := GetPermittedRepos(proj, ociRepos) 330 if err != nil { 331 return nil, fmt.Errorf("failed to get permitted oci repositories for project %q: %w", proj.Name, err) 332 } 333 permittedHelmCredentials, err := GetPermittedReposCredentials(proj, helmRepositoryCredentials) 334 if err != nil { 335 return nil, fmt.Errorf("error getting permitted repo creds: %w", err) 336 } 337 ociRepositoryCredentials, err := db.GetAllOCIRepositoryCredentials(ctx) 338 if err != nil { 339 return nil, fmt.Errorf("failed to get OCI credentials: %w", err) 340 } 341 permittedOCICredentials, err := GetPermittedReposCredentials(proj, ociRepositoryCredentials) 342 if err != nil { 343 return nil, fmt.Errorf("failed to get permitted OCI credentials for project %q: %w", proj.Name, err) 344 } 345 346 destCluster, err := GetDestinationCluster(ctx, spec.Destination, db) 347 if err != nil { 348 conditions = append(conditions, argoappv1.ApplicationCondition{ 349 Type: argoappv1.ApplicationConditionInvalidSpecError, 350 Message: fmt.Sprintf("Unable to get cluster: %v", err), 351 }) 352 return conditions, nil 353 } 354 config, err := destCluster.RESTConfig() 355 if err != nil { 356 return nil, fmt.Errorf("error getting cluster REST config: %w", err) 357 } 358 //nolint:staticcheck 359 destCluster.ServerVersion, err = kubectl.GetServerVersion(config) 360 if err != nil { 361 return nil, fmt.Errorf("error getting k8s server version: %w", err) 362 } 363 apiGroups, err := kubectl.GetAPIResources(config, false, cache.NewNoopSettings()) 364 if err != nil { 365 return nil, fmt.Errorf("error getting API resources: %w", err) 366 } 367 enabledSourceTypes, err := settingsMgr.GetEnabledSourceTypes() 368 if err != nil { 369 return nil, fmt.Errorf("error getting enabled source types: %w", err) 370 } 371 372 sourceCondition, err := validateRepo( 373 ctx, 374 app, 375 db, 376 app.Spec.GetSources(), 377 repoClient, 378 permittedHelmRepos, 379 permittedOCIRepos, 380 helmOptions, 381 destCluster, 382 apiGroups, 383 proj, 384 permittedHelmCredentials, 385 permittedOCICredentials, 386 enabledSourceTypes, 387 settingsMgr) 388 if err != nil { 389 return nil, err 390 } 391 conditions = append(conditions, sourceCondition...) 392 393 return conditions, nil 394 } 395 396 func validateRepo(ctx context.Context, 397 app *argoappv1.Application, 398 db db.ArgoDB, 399 sources []argoappv1.ApplicationSource, 400 repoClient apiclient.RepoServerServiceClient, 401 permittedHelmRepos []*argoappv1.Repository, 402 permittedOCIRepos []*argoappv1.Repository, 403 helmOptions *argoappv1.HelmOptions, 404 cluster *argoappv1.Cluster, 405 apiGroups []kube.APIResourceInfo, 406 proj *argoappv1.AppProject, 407 permittedHelmCredentials []*argoappv1.RepoCreds, 408 permittedOCICredentials []*argoappv1.RepoCreds, 409 enabledSourceTypes map[string]bool, 410 settingsMgr *settings.SettingsManager, 411 ) ([]argoappv1.ApplicationCondition, error) { 412 conditions := make([]argoappv1.ApplicationCondition, 0) 413 errMessage := "" 414 415 for _, source := range sources { 416 repo, err := db.GetRepository(ctx, source.RepoURL, proj.Name) 417 if err != nil { 418 return nil, err 419 } 420 if err := TestRepoWithKnownType(ctx, repoClient, repo, source.IsHelm(), source.IsHelmOci(), source.IsOCI()); err != nil { 421 errMessage = fmt.Sprintf("repositories not accessible: %v: %v", repo.StringForLogging(), err) 422 } 423 repoAccessible := false 424 425 if errMessage != "" { 426 conditions = append(conditions, argoappv1.ApplicationCondition{ 427 Type: argoappv1.ApplicationConditionInvalidSpecError, 428 Message: fmt.Sprintf("repository not accessible: %v", errMessage), 429 }) 430 } else { 431 repoAccessible = true 432 } 433 434 // Verify only one source type is defined 435 _, err = source.ExplicitType() 436 if err != nil { 437 return nil, fmt.Errorf("error verifying source type: %w", err) 438 } 439 440 // is the repo inaccessible - abort now 441 if !repoAccessible { 442 return conditions, nil 443 } 444 } 445 446 // If using the source hydrator, check the dry source instead of the sync source, since the sync source branch may 447 // not exist yet. 448 if app.Spec.SourceHydrator != nil { 449 sources = []argoappv1.ApplicationSource{app.Spec.SourceHydrator.GetDrySource()} 450 } 451 452 refSources, err := GetRefSources(ctx, sources, app.Spec.Project, db.GetRepository, []string{}) 453 if err != nil { 454 return nil, fmt.Errorf("error getting ref sources: %w", err) 455 } 456 conditions = append(conditions, verifyGenerateManifests( 457 ctx, 458 db, 459 permittedHelmRepos, 460 permittedOCIRepos, 461 helmOptions, 462 app, 463 proj, 464 sources, 465 repoClient, 466 //nolint:staticcheck 467 cluster.ServerVersion, 468 APIResourcesToStrings(apiGroups, true), 469 permittedHelmCredentials, 470 permittedOCICredentials, 471 enabledSourceTypes, 472 settingsMgr, 473 refSources)...) 474 475 return conditions, nil 476 } 477 478 // GetRefSources creates a map of ref keys (from the sources' 'ref' fields) to information about the referenced source. 479 // This function also validates the references use allowed characters and does not define the same ref key more than 480 // once (which would lead to ambiguous references). 481 func GetRefSources(ctx context.Context, sources argoappv1.ApplicationSources, project string, getRepository func(ctx context.Context, url string, project string) (*argoappv1.Repository, error), revisions []string) (argoappv1.RefTargetRevisionMapping, error) { 482 refSources := make(argoappv1.RefTargetRevisionMapping) 483 if len(sources) > 1 { 484 // Validate first to avoid unnecessary DB calls. 485 refKeys := make(map[string]bool) 486 for _, source := range sources { 487 if source.Ref == "" { 488 continue 489 } 490 isValidRefKey := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString 491 if !isValidRefKey(source.Ref) { 492 return nil, fmt.Errorf("sources.ref %s cannot contain any special characters except '_' and '-'", source.Ref) 493 } 494 refKey := "$" + source.Ref 495 if _, ok := refKeys[refKey]; ok { 496 return nil, errors.New("invalid sources: multiple sources had the same `ref` key") 497 } 498 refKeys[refKey] = true 499 } 500 // Get Repositories for all sources before generating Manifests 501 for i, source := range sources { 502 if source.Ref == "" { 503 continue 504 } 505 506 repo, err := getRepository(ctx, source.RepoURL, project) 507 if err != nil { 508 return nil, fmt.Errorf("failed to get repository %s: %w", source.RepoURL, err) 509 } 510 refKey := "$" + source.Ref 511 revision := source.TargetRevision 512 if len(revisions) > i && revisions[i] != "" { 513 revision = revisions[i] 514 } 515 refSources[refKey] = &argoappv1.RefTarget{ 516 Repo: *repo, 517 TargetRevision: revision, 518 Chart: source.Chart, 519 } 520 } 521 } 522 return refSources, nil 523 } 524 525 func validateSourcePermissions(source argoappv1.ApplicationSource, hasMultipleSources bool) []argoappv1.ApplicationCondition { 526 var conditions []argoappv1.ApplicationCondition 527 if hasMultipleSources { 528 if source.RepoURL == "" || (source.Path == "" && source.Chart == "" && source.Ref == "") { 529 conditions = append(conditions, argoappv1.ApplicationCondition{ 530 Type: argoappv1.ApplicationConditionInvalidSpecError, 531 Message: fmt.Sprintf("spec.source.repoURL and either source.path, source.chart, or source.ref are required for source %s", source), 532 }) 533 return conditions 534 } 535 } else { 536 if source.RepoURL == "" || (source.Path == "" && source.Chart == "") { 537 conditions = append(conditions, argoappv1.ApplicationCondition{ 538 Type: argoappv1.ApplicationConditionInvalidSpecError, 539 Message: "spec.source.repoURL and either spec.source.path or spec.source.chart are required", 540 }) 541 return conditions 542 } 543 } 544 if source.Chart != "" && source.TargetRevision == "" { 545 conditions = append(conditions, argoappv1.ApplicationCondition{ 546 Type: argoappv1.ApplicationConditionInvalidSpecError, 547 Message: "spec.source.targetRevision is required if the manifest source is a helm chart", 548 }) 549 return conditions 550 } 551 552 return conditions 553 } 554 555 func validateSourceHydrator(hydrator *argoappv1.SourceHydrator) []argoappv1.ApplicationCondition { 556 var conditions []argoappv1.ApplicationCondition 557 if hydrator.DrySource.RepoURL == "" { 558 conditions = append(conditions, argoappv1.ApplicationCondition{ 559 Type: argoappv1.ApplicationConditionInvalidSpecError, 560 Message: "spec.sourceHydrator.drySource.repoURL is required", 561 }) 562 } 563 if hydrator.SyncSource.TargetBranch == "" { 564 conditions = append(conditions, argoappv1.ApplicationCondition{ 565 Type: argoappv1.ApplicationConditionInvalidSpecError, 566 Message: "spec.sourceHydrator.syncSource.targetBranch is required", 567 }) 568 } 569 if hydrator.HydrateTo != nil && hydrator.HydrateTo.TargetBranch == "" { 570 conditions = append(conditions, argoappv1.ApplicationCondition{ 571 Type: argoappv1.ApplicationConditionInvalidSpecError, 572 Message: "when spec.sourceHydrator.hydrateTo is set, spec.sourceHydrator.hydrateTo.path is required", 573 }) 574 } 575 return conditions 576 } 577 578 // ValidatePermissions ensures that the referenced cluster has been added to Argo CD and the app source repo and destination namespace/cluster are permitted in app project 579 func ValidatePermissions(ctx context.Context, spec *argoappv1.ApplicationSpec, proj *argoappv1.AppProject, db db.ArgoDB) ([]argoappv1.ApplicationCondition, error) { 580 conditions := make([]argoappv1.ApplicationCondition, 0) 581 582 switch { 583 case spec.SourceHydrator != nil: 584 condition := validateSourceHydrator(spec.SourceHydrator) 585 if len(condition) > 0 { 586 conditions = append(conditions, condition...) 587 return conditions, nil 588 } 589 if !proj.IsSourcePermitted(spec.SourceHydrator.GetDrySource()) { 590 conditions = append(conditions, argoappv1.ApplicationCondition{ 591 Type: argoappv1.ApplicationConditionInvalidSpecError, 592 Message: fmt.Sprintf("application repo %s is not permitted in project '%s'", spec.SourceHydrator.GetDrySource().RepoURL, proj.Name), 593 }) 594 } 595 case spec.HasMultipleSources(): 596 for _, source := range spec.Sources { 597 condition := validateSourcePermissions(source, spec.HasMultipleSources()) 598 if len(condition) > 0 { 599 conditions = append(conditions, condition...) 600 return conditions, nil 601 } 602 603 if !proj.IsSourcePermitted(source) { 604 conditions = append(conditions, argoappv1.ApplicationCondition{ 605 Type: argoappv1.ApplicationConditionInvalidSpecError, 606 Message: fmt.Sprintf("application repo %s is not permitted in project '%s'", source.RepoURL, proj.Name), 607 }) 608 } 609 } 610 default: 611 conditions = validateSourcePermissions(spec.GetSource(), spec.HasMultipleSources()) 612 if len(conditions) > 0 { 613 return conditions, nil 614 } 615 616 if !proj.IsSourcePermitted(spec.GetSource()) { 617 conditions = append(conditions, argoappv1.ApplicationCondition{ 618 Type: argoappv1.ApplicationConditionInvalidSpecError, 619 Message: fmt.Sprintf("application repo %s is not permitted in project '%s'", spec.GetSource().RepoURL, proj.Name), 620 }) 621 } 622 } 623 624 destCluster, err := GetDestinationCluster(ctx, spec.Destination, db) 625 if err != nil { 626 conditions = append(conditions, argoappv1.ApplicationCondition{ 627 Type: argoappv1.ApplicationConditionInvalidSpecError, 628 Message: err.Error(), 629 }) 630 return conditions, nil 631 } 632 permitted, err := proj.IsDestinationPermitted(destCluster, spec.Destination.Namespace, func(project string) ([]*argoappv1.Cluster, error) { 633 return db.GetProjectClusters(ctx, project) 634 }) 635 if err != nil { 636 return nil, err 637 } 638 if !permitted { 639 server := destCluster.Server 640 if spec.Destination.Name != "" { 641 server = destCluster.Name 642 } 643 conditions = append(conditions, argoappv1.ApplicationCondition{ 644 Type: argoappv1.ApplicationConditionInvalidSpecError, 645 Message: fmt.Sprintf("application destination server '%s' and namespace '%s' do not match any of the allowed destinations in project '%s'", server, spec.Destination.Namespace, proj.Name), 646 }) 647 } 648 return conditions, nil 649 } 650 651 // APIResourcesToStrings converts list of API Resources list into string list 652 func APIResourcesToStrings(resources []kube.APIResourceInfo, includeKinds bool) []string { 653 resMap := map[string]bool{} 654 for _, r := range resources { 655 groupVersion := r.GroupVersionResource.GroupVersion().String() 656 resMap[groupVersion] = true 657 if includeKinds { 658 resMap[groupVersion+"/"+r.GroupKind.Kind] = true 659 } 660 } 661 var res []string 662 for k := range resMap { 663 res = append(res, k) 664 } 665 sort.Slice(res, func(i, j int) bool { 666 return res[i] < res[j] 667 }) 668 return res 669 } 670 671 // GetAppProjectWithScopedResources returns a project from an application with scoped resources 672 func GetAppProjectWithScopedResources(ctx context.Context, name string, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager, db db.ArgoDB) (*argoappv1.AppProject, argoappv1.Repositories, []*argoappv1.Cluster, error) { 673 projOrig, err := projLister.AppProjects(ns).Get(name) 674 if err != nil { 675 return nil, nil, nil, fmt.Errorf("error getting app project %q: %w", name, err) 676 } 677 678 project, err := GetAppVirtualProject(projOrig, projLister, settingsManager) 679 if err != nil { 680 return nil, nil, nil, fmt.Errorf("error getting app virtual project: %w", err) 681 } 682 683 clusters, err := db.GetProjectClusters(ctx, project.Name) 684 if err != nil { 685 return nil, nil, nil, fmt.Errorf("error getting project clusters: %w", err) 686 } 687 repos, err := db.GetProjectRepositories(name) 688 if err != nil { 689 return nil, nil, nil, fmt.Errorf("error getting project repos: %w", err) 690 } 691 return project, repos, clusters, nil 692 } 693 694 // GetAppProjectByName returns a project from an application based on name 695 func GetAppProjectByName(ctx context.Context, name string, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager, db db.ArgoDB) (*argoappv1.AppProject, error) { 696 projOrig, err := projLister.AppProjects(ns).Get(name) 697 if err != nil { 698 return nil, fmt.Errorf("error getting app project %q: %w", name, err) 699 } 700 project := projOrig.DeepCopy() 701 repos, err := db.GetProjectRepositories(name) 702 if err != nil { 703 return nil, fmt.Errorf("error getting project repositories: %w", err) 704 } 705 for _, repo := range repos { 706 project.Spec.SourceRepos = append(project.Spec.SourceRepos, repo.Repo) 707 } 708 clusters, err := db.GetProjectClusters(ctx, name) 709 if err != nil { 710 return nil, fmt.Errorf("error getting project clusters: %w", err) 711 } 712 for _, cluster := range clusters { 713 if len(cluster.Namespaces) == 0 { 714 project.Spec.Destinations = append(project.Spec.Destinations, argoappv1.ApplicationDestination{Server: cluster.Server, Namespace: "*"}) 715 } else { 716 for _, ns := range cluster.Namespaces { 717 project.Spec.Destinations = append(project.Spec.Destinations, argoappv1.ApplicationDestination{Server: cluster.Server, Namespace: ns}) 718 } 719 } 720 } 721 return GetAppVirtualProject(project, projLister, settingsManager) 722 } 723 724 // GetAppProject returns a project from an application. It will also ensure 725 // that the application is allowed to use the project. 726 func GetAppProject(ctx context.Context, app *argoappv1.Application, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager, db db.ArgoDB) (*argoappv1.AppProject, error) { 727 proj, err := GetAppProjectByName(ctx, app.Spec.GetProject(), projLister, ns, settingsManager, db) 728 if err != nil { 729 return nil, err 730 } 731 if !proj.IsAppNamespacePermitted(app, ns) { 732 return nil, NewErrApplicationNotAllowedToUseProject(app.Name, app.Namespace, proj.Name) 733 } 734 return proj, nil 735 } 736 737 // verifyGenerateManifests verifies a repo path can generate manifests 738 func verifyGenerateManifests( 739 ctx context.Context, 740 db db.ArgoDB, 741 helmRepos argoappv1.Repositories, 742 ociRepos argoappv1.Repositories, 743 helmOptions *argoappv1.HelmOptions, 744 app *argoappv1.Application, 745 proj *argoappv1.AppProject, 746 sources []argoappv1.ApplicationSource, 747 repoClient apiclient.RepoServerServiceClient, 748 kubeVersion string, 749 apiVersions []string, 750 repositoryCredentials []*argoappv1.RepoCreds, 751 ociRepositoryCredentials []*argoappv1.RepoCreds, 752 enableGenerateManifests map[string]bool, 753 settingsMgr *settings.SettingsManager, 754 refSources argoappv1.RefTargetRevisionMapping, 755 ) []argoappv1.ApplicationCondition { 756 var conditions []argoappv1.ApplicationCondition 757 // If source is Kustomize add build options 758 kustomizeSettings, err := settingsMgr.GetKustomizeSettings() 759 if err != nil { 760 conditions = append(conditions, argoappv1.ApplicationCondition{ 761 Type: argoappv1.ApplicationConditionInvalidSpecError, 762 Message: fmt.Sprintf("Error getting Kustomize settings: %v", err), 763 }) 764 return conditions // Can't perform the next check without settings. 765 } 766 767 for _, source := range sources { 768 repoRes, err := db.GetRepository(ctx, source.RepoURL, proj.Name) 769 if err != nil { 770 conditions = append(conditions, argoappv1.ApplicationCondition{ 771 Type: argoappv1.ApplicationConditionInvalidSpecError, 772 Message: fmt.Sprintf("Unable to get repository: %v", err), 773 }) 774 continue 775 } 776 installationID, err := settingsMgr.GetInstallationID() 777 if err != nil { 778 conditions = append(conditions, argoappv1.ApplicationCondition{ 779 Type: argoappv1.ApplicationConditionInvalidSpecError, 780 Message: fmt.Sprintf("Error getting installation ID: %v", err), 781 }) 782 continue 783 } 784 785 appLabelKey, err := settingsMgr.GetAppInstanceLabelKey() 786 if err != nil { 787 conditions = append(conditions, argoappv1.ApplicationCondition{ 788 Type: argoappv1.ApplicationConditionInvalidSpecError, 789 Message: fmt.Sprintf("Error getting app label key ID: %v", err), 790 }) 791 continue 792 } 793 794 trackingMethod, err := settingsMgr.GetTrackingMethod() 795 if err != nil { 796 conditions = append(conditions, argoappv1.ApplicationCondition{ 797 Type: argoappv1.ApplicationConditionInvalidSpecError, 798 Message: fmt.Sprintf("Error getting trackingMethod: %v", err), 799 }) 800 continue 801 } 802 803 verifySignature := false 804 if len(proj.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled() { 805 verifySignature = true 806 } 807 808 repos := helmRepos 809 helmRepoCreds := repositoryCredentials 810 // If the source is OCI, there is a potential for an OCI image to be a Helm chart and that said chart in 811 // turn would have OCI dependencies. To ensure that those dependencies can be resolved, add them to the repos 812 // list. 813 if source.IsOCI() { 814 repos = slices.Clone(helmRepos) 815 helmRepoCreds = slices.Clone(repositoryCredentials) 816 repos = append(repos, ociRepos...) 817 helmRepoCreds = append(helmRepoCreds, ociRepositoryCredentials...) 818 } 819 820 req := apiclient.ManifestRequest{ 821 Repo: &argoappv1.Repository{ 822 Repo: source.RepoURL, 823 Type: repoRes.Type, 824 Name: repoRes.Name, 825 Proxy: repoRes.Proxy, 826 NoProxy: repoRes.NoProxy, 827 }, 828 VerifySignature: verifySignature, 829 Repos: repos, 830 Revision: source.TargetRevision, 831 AppName: app.Name, 832 Namespace: app.Spec.Destination.Namespace, 833 ApplicationSource: &source, 834 AppLabelKey: appLabelKey, 835 KustomizeOptions: kustomizeSettings, 836 KubeVersion: kubeVersion, 837 ApiVersions: apiVersions, 838 HelmOptions: helmOptions, 839 HelmRepoCreds: helmRepoCreds, 840 TrackingMethod: trackingMethod, 841 EnabledSourceTypes: enableGenerateManifests, 842 NoRevisionCache: true, 843 HasMultipleSources: app.Spec.HasMultipleSources(), 844 RefSources: refSources, 845 ProjectName: proj.Name, 846 ProjectSourceRepos: proj.Spec.SourceRepos, 847 AnnotationManifestGeneratePaths: app.GetAnnotation(argoappv1.AnnotationKeyManifestGeneratePaths), 848 InstallationID: installationID, 849 } 850 req.Repo.CopyCredentialsFromRepo(repoRes) 851 req.Repo.CopySettingsFrom(repoRes) 852 853 // Only check whether we can access the application's path, 854 // and not whether it actually contains any manifests. 855 _, err = repoClient.GenerateManifest(ctx, &req) 856 if err != nil { 857 errMessage := fmt.Sprintf("Unable to generate manifests in %s: %s", source.Path, err) 858 conditions = append(conditions, argoappv1.ApplicationCondition{ 859 Type: argoappv1.ApplicationConditionInvalidSpecError, 860 Message: errMessage, 861 }) 862 } 863 } 864 865 return conditions 866 } 867 868 // SetAppOperation updates an application with the specified operation, retrying conflict errors 869 func SetAppOperation(appIf v1alpha1.ApplicationInterface, appName string, op *argoappv1.Operation) (*argoappv1.Application, error) { 870 for { 871 a, err := appIf.Get(context.Background(), appName, metav1.GetOptions{}) 872 if err != nil { 873 return nil, fmt.Errorf("error getting application %q: %w", appName, err) 874 } 875 a = a.DeepCopy() 876 if a.Operation != nil { 877 return nil, ErrAnotherOperationInProgress 878 } 879 a.Operation = op 880 a.Status.OperationState = nil 881 a, err = appIf.Update(context.Background(), a, metav1.UpdateOptions{}) 882 if op.Sync == nil { 883 return nil, status.Errorf(codes.InvalidArgument, "Operation unspecified") 884 } 885 if err == nil { 886 return a, nil 887 } 888 if !apierrors.IsConflict(err) { 889 return nil, fmt.Errorf("error updating application %q: %w", appName, err) 890 } 891 log.Warnf("Failed to set operation for app '%s' due to update conflict. Retrying again...", appName) 892 } 893 } 894 895 // ContainsSyncResource determines if the given resource exists in the provided slice of sync operation resources. 896 func ContainsSyncResource(name string, namespace string, gvk schema.GroupVersionKind, rr []argoappv1.SyncOperationResource) bool { 897 for _, r := range rr { 898 if r.HasIdentity(name, namespace, gvk) { 899 return true 900 } 901 } 902 return false 903 } 904 905 // IncludeResource The app resource is checked against the include or exclude filters. 906 // If exclude filters are present, they are evaluated only after all include filters have been assessed. 907 func IncludeResource(resourceName string, resourceNamespace string, gvk schema.GroupVersionKind, 908 syncOperationResources []*argoappv1.SyncOperationResource, 909 ) bool { 910 includeResource := false 911 foundIncludeRule := false 912 // Evaluate include filters only in this loop. 913 for _, syncOperationResource := range syncOperationResources { 914 if syncOperationResource.Exclude { 915 continue 916 } 917 foundIncludeRule = true 918 includeResource = syncOperationResource.Compare(resourceName, resourceNamespace, gvk) 919 if includeResource { 920 break 921 } 922 } 923 924 // if a resource is present both in include and in exclude, the exclude wins. 925 // that including it here is a temporary decision for the use case when no include rules exist, 926 // but it still might be excluded later if it matches an exclude rule: 927 if !foundIncludeRule { 928 includeResource = true 929 } 930 // No needs to evaluate exclude filters when the resource is not included. 931 if !includeResource { 932 return false 933 } 934 // Evaluate exclude filters only in this loop. 935 for _, syncOperationResource := range syncOperationResources { 936 if syncOperationResource.Exclude && syncOperationResource.Compare(resourceName, resourceNamespace, gvk) { 937 return false 938 } 939 } 940 return true 941 } 942 943 // NormalizeApplicationSpec will normalize an application spec to a preferred state. This is used 944 // for migrating application objects which are using deprecated legacy fields into the new fields, 945 // and defaulting fields in the spec (e.g. spec.project) 946 func NormalizeApplicationSpec(spec *argoappv1.ApplicationSpec) *argoappv1.ApplicationSpec { 947 spec = spec.DeepCopy() 948 if spec.Project == "" { 949 spec.Project = argoappv1.DefaultAppProjectName 950 } 951 if spec.SyncPolicy.IsZero() { 952 spec.SyncPolicy = nil 953 } 954 if len(spec.Sources) > 0 { 955 for _, source := range spec.Sources { 956 NormalizeSource(&source) 957 } 958 } else if spec.Source != nil { 959 // In practice, spec.Source should never be nil. 960 NormalizeSource(spec.Source) 961 } 962 return spec 963 } 964 965 func NormalizeSource(source *argoappv1.ApplicationSource) *argoappv1.ApplicationSource { 966 // 3. If any app sources are their zero values, then nil out the pointers to the source spec. 967 // This makes it easier for users to switch between app source types if they are not using 968 // any of the source-specific parameters. 969 if source.Kustomize != nil && source.Kustomize.IsZero() { 970 source.Kustomize = nil 971 } 972 if source.Helm != nil && source.Helm.IsZero() { 973 source.Helm = nil 974 } 975 if source.Directory != nil && source.Directory.IsZero() { 976 switch { 977 case source.Directory.Exclude != "" && source.Directory.Include != "": 978 source.Directory = &argoappv1.ApplicationSourceDirectory{Exclude: source.Directory.Exclude, Include: source.Directory.Include} 979 case source.Directory.Exclude != "": 980 source.Directory = &argoappv1.ApplicationSourceDirectory{Exclude: source.Directory.Exclude} 981 case source.Directory.Include != "": 982 source.Directory = &argoappv1.ApplicationSourceDirectory{Include: source.Directory.Include} 983 default: 984 source.Directory = nil 985 } 986 } 987 return source 988 } 989 990 func GetPermittedReposCredentials(proj *argoappv1.AppProject, repoCreds []*argoappv1.RepoCreds) ([]*argoappv1.RepoCreds, error) { 991 var permittedRepoCreds []*argoappv1.RepoCreds 992 for _, v := range repoCreds { 993 if proj.IsSourcePermitted(argoappv1.ApplicationSource{RepoURL: v.URL}) { 994 permittedRepoCreds = append(permittedRepoCreds, v) 995 } 996 } 997 return permittedRepoCreds, nil 998 } 999 1000 func GetPermittedRepos(proj *argoappv1.AppProject, repos []*argoappv1.Repository) ([]*argoappv1.Repository, error) { 1001 var permittedRepos []*argoappv1.Repository 1002 for _, v := range repos { 1003 if proj.IsSourcePermitted(argoappv1.ApplicationSource{RepoURL: v.Repo}) { 1004 permittedRepos = append(permittedRepos, v) 1005 } 1006 } 1007 return permittedRepos, nil 1008 } 1009 1010 type ClusterGetter interface { 1011 GetCluster(ctx context.Context, name string) (*argoappv1.Cluster, error) 1012 GetClusterServersByName(ctx context.Context, server string) ([]string, error) 1013 } 1014 1015 // GetDestinationCluster returns the cluster object based on the destination server or name. If both are provided or 1016 // both are empty, an error is returned. If the destination server is provided, the cluster is fetched by the server 1017 // URL. If the destination name is provided, the cluster is fetched by the name. If multiple clusters have the specified 1018 // name, an error is returned. 1019 func GetDestinationCluster(ctx context.Context, destination argoappv1.ApplicationDestination, db ClusterGetter) (*argoappv1.Cluster, error) { 1020 if destination.Name != "" && destination.Server != "" { 1021 return nil, fmt.Errorf("application destination can't have both name and server defined: %s %s", destination.Name, destination.Server) 1022 } 1023 if destination.Server != "" { 1024 cluster, err := db.GetCluster(ctx, destination.Server) 1025 if err != nil { 1026 return nil, fmt.Errorf("error getting cluster by server %q: %w", destination.Server, err) 1027 } 1028 return cluster, nil 1029 } else if destination.Name != "" { 1030 clusterURLs, err := db.GetClusterServersByName(ctx, destination.Name) 1031 if err != nil { 1032 return nil, fmt.Errorf("error getting cluster by name %q: %w", destination.Name, err) 1033 } 1034 if len(clusterURLs) == 0 { 1035 return nil, fmt.Errorf("there are no clusters with this name: %s", destination.Name) 1036 } 1037 if len(clusterURLs) > 1 { 1038 return nil, fmt.Errorf("there are %d clusters with the same name: [%s]", len(clusterURLs), strings.Join(clusterURLs, " ")) 1039 } 1040 cluster, err := db.GetCluster(ctx, clusterURLs[0]) 1041 if err != nil { 1042 return nil, fmt.Errorf("error getting cluster by URL: %w", err) 1043 } 1044 return cluster, nil 1045 } 1046 // nolint:staticcheck // Error constant is very old, shouldn't lowercase the first letter. 1047 return nil, errors.New(ErrDestinationMissing) 1048 } 1049 1050 func GetGlobalProjects(proj *argoappv1.AppProject, projLister applicationsv1.AppProjectLister, settingsManager *settings.SettingsManager) []*argoappv1.AppProject { 1051 gps, err := settingsManager.GetGlobalProjectsSettings() 1052 globalProjects := make([]*argoappv1.AppProject, 0) 1053 1054 if err != nil { 1055 log.Warnf("Failed to get global project settings: %v", err) 1056 return globalProjects 1057 } 1058 1059 for _, gp := range gps { 1060 // The project itself is not its own the global project 1061 if proj.Name == gp.ProjectName { 1062 continue 1063 } 1064 1065 selector, err := metav1.LabelSelectorAsSelector(&gp.LabelSelector) 1066 if err != nil { 1067 break 1068 } 1069 // Get projects which match the label selector, then see if proj is a match 1070 projList, err := projLister.AppProjects(proj.Namespace).List(selector) 1071 if err != nil { 1072 break 1073 } 1074 var matchMe bool 1075 for _, item := range projList { 1076 if item.Name == proj.Name { 1077 matchMe = true 1078 break 1079 } 1080 } 1081 if !matchMe { 1082 continue 1083 } 1084 // If proj is a match for this global project setting, then it is its global project 1085 globalProj, err := projLister.AppProjects(proj.Namespace).Get(gp.ProjectName) 1086 if err != nil { 1087 break 1088 } 1089 globalProjects = append(globalProjects, globalProj) 1090 } 1091 return globalProjects 1092 } 1093 1094 func GetAppVirtualProject(proj *argoappv1.AppProject, projLister applicationsv1.AppProjectLister, settingsManager *settings.SettingsManager) (*argoappv1.AppProject, error) { 1095 virtualProj := proj.DeepCopy() 1096 globalProjects := GetGlobalProjects(proj, projLister, settingsManager) 1097 1098 for _, gp := range globalProjects { 1099 virtualProj = mergeVirtualProject(virtualProj, gp) 1100 } 1101 return virtualProj, nil 1102 } 1103 1104 func mergeVirtualProject(proj *argoappv1.AppProject, globalProj *argoappv1.AppProject) *argoappv1.AppProject { 1105 if globalProj == nil { 1106 return proj 1107 } 1108 proj.Spec.ClusterResourceWhitelist = append(proj.Spec.ClusterResourceWhitelist, globalProj.Spec.ClusterResourceWhitelist...) 1109 proj.Spec.ClusterResourceBlacklist = append(proj.Spec.ClusterResourceBlacklist, globalProj.Spec.ClusterResourceBlacklist...) 1110 1111 proj.Spec.NamespaceResourceWhitelist = append(proj.Spec.NamespaceResourceWhitelist, globalProj.Spec.NamespaceResourceWhitelist...) 1112 proj.Spec.NamespaceResourceBlacklist = append(proj.Spec.NamespaceResourceBlacklist, globalProj.Spec.NamespaceResourceBlacklist...) 1113 1114 proj.Spec.SyncWindows = append(proj.Spec.SyncWindows, globalProj.Spec.SyncWindows...) 1115 1116 proj.Spec.SourceRepos = append(proj.Spec.SourceRepos, globalProj.Spec.SourceRepos...) 1117 1118 proj.Spec.Destinations = append(proj.Spec.Destinations, globalProj.Spec.Destinations...) 1119 1120 return proj 1121 } 1122 1123 func GenerateSpecIsDifferentErrorMessage(entity string, a, b any) string { 1124 basicMsg := fmt.Sprintf("existing %s spec is different; use upsert flag to force update", entity) 1125 difference, _ := GetDifferentPathsBetweenStructs(a, b) 1126 if len(difference) == 0 { 1127 return basicMsg 1128 } 1129 return fmt.Sprintf("%s; difference in keys %q", basicMsg, strings.Join(difference, ",")) 1130 } 1131 1132 func GetDifferentPathsBetweenStructs(a, b any) ([]string, error) { 1133 var difference []string 1134 changelog, err := diff.Diff(a, b) 1135 if err != nil { 1136 return nil, fmt.Errorf("error during diff: %w", err) 1137 } 1138 for _, changeItem := range changelog { 1139 difference = append(difference, changeItem.Path...) 1140 } 1141 return difference, nil 1142 } 1143 1144 // parseName will split the qualified name into its components, which are separated by the delimiter. 1145 // If delimiter is not contained in the string qualifiedName then returned namespace is defaultNs. 1146 func parseName(qualifiedName string, defaultNs string, delim string) (name string, namespace string) { 1147 t := strings.SplitN(qualifiedName, delim, 2) 1148 if len(t) == 2 { 1149 namespace = t[0] 1150 name = t[1] 1151 } else { 1152 namespace = defaultNs 1153 name = t[0] 1154 } 1155 return 1156 } 1157 1158 // ParseAppNamespacedName parses a namespaced name in the format namespace/name 1159 // and returns the components. If name wasn't namespaced, defaultNs will be 1160 // returned as namespace component. 1161 func ParseFromQualifiedName(qualifiedAppName string, defaultNs string) (appName string, appNamespace string) { 1162 return parseName(qualifiedAppName, defaultNs, "/") 1163 } 1164 1165 // ParseInstanceName parses a namespaced name in the format namespace_name 1166 // and returns the components. If name wasn't namespaced, defaultNs will be 1167 // returned as namespace component. 1168 func ParseInstanceName(appName string, defaultNs string) (string, string) { 1169 return parseName(appName, defaultNs, "_") 1170 } 1171 1172 // AppInstanceName returns the value to be used for app instance labels from 1173 // the combination of appName, appNs and defaultNs. 1174 func AppInstanceName(appName, appNs, defaultNs string) string { 1175 if appNs == "" || appNs == defaultNs { 1176 return appName 1177 } 1178 return appNs + "_" + appName 1179 } 1180 1181 // InstanceNameFromQualified returns the value to be used for app 1182 func InstanceNameFromQualified(name string, defaultNs string) string { 1183 appName, appNs := ParseFromQualifiedName(name, defaultNs) 1184 return AppInstanceName(appName, appNs, defaultNs) 1185 } 1186 1187 // ErrProjectNotPermitted returns an error to indicate that an application 1188 // identified by appName and appNamespace is not allowed to use the project 1189 // identified by projName. 1190 func ErrProjectNotPermitted(appName, appNamespace, projName string) error { 1191 return fmt.Errorf("application '%s' in namespace '%s' is not permitted to use project '%s'", appName, appNamespace, projName) 1192 } 1193 1194 // IsValidPodName checks that a podName is valid 1195 func IsValidPodName(name string) bool { 1196 // https://github.com/kubernetes/kubernetes/blob/976a940f4a4e84fe814583848f97b9aafcdb083f/pkg/apis/core/validation/validation.go#L241 1197 validationErrors := apimachineryvalidation.NameIsDNSSubdomain(name, false) 1198 return len(validationErrors) == 0 1199 } 1200 1201 // IsValidAppName checks if the name can be used as application name 1202 func IsValidAppName(name string) bool { 1203 // app names have the same rules as pods. 1204 return IsValidPodName(name) 1205 } 1206 1207 // IsValidProjectName checks if the name can be used as project name 1208 func IsValidProjectName(name string) bool { 1209 // project names have the same rules as pods. 1210 return IsValidPodName(name) 1211 } 1212 1213 // IsValidNamespaceName checks that a namespace name is valid 1214 func IsValidNamespaceName(name string) bool { 1215 // https://github.com/kubernetes/kubernetes/blob/976a940f4a4e84fe814583848f97b9aafcdb083f/pkg/apis/core/validation/validation.go#L262 1216 validationErrors := apimachineryvalidation.ValidateNamespaceName(name, false) 1217 return len(validationErrors) == 0 1218 } 1219 1220 // IsValidContainerName checks that a containerName is valid 1221 func IsValidContainerName(name string) bool { 1222 // https://github.com/kubernetes/kubernetes/blob/53a9d106c4aabcd550cc32ae4e8004f32fb0ae7b/pkg/api/validation/validation.go#L280 1223 validationErrors := apimachineryvalidation.NameIsDNSLabel(name, false) 1224 return len(validationErrors) == 0 1225 } 1226 1227 // GetAppEventLabels returns a map of labels to add to a K8s event. 1228 // The Application and its AppProject labels are compared against the `resource.includeEventLabelKeys` key in argocd-cm. 1229 // If matched, the corresponding labels are returned to be added to the generated event. In case of a conflict 1230 // between labels on the Application and AppProject, the Application label values are prioritized and added to the event. 1231 // Furthermore, labels specified in `resource.excludeEventLabelKeys` in argocd-cm are removed from the event labels, if they were included. 1232 func GetAppEventLabels(ctx context.Context, app *argoappv1.Application, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager, db db.ArgoDB) map[string]string { 1233 eventLabels := make(map[string]string) 1234 1235 // Get all app & app-project labels 1236 labels := app.Labels 1237 if labels == nil { 1238 labels = make(map[string]string) 1239 } 1240 proj, err := GetAppProject(ctx, app, projLister, ns, settingsManager, db) 1241 if err == nil { 1242 for k, v := range proj.Labels { 1243 _, found := labels[k] 1244 if !found { 1245 labels[k] = v 1246 } 1247 } 1248 } else { 1249 log.Warn(err) 1250 } 1251 1252 // Filter out event labels to include 1253 inKeys := settingsManager.GetIncludeEventLabelKeys() 1254 for k, v := range labels { 1255 found := glob.MatchStringInList(inKeys, k, glob.GLOB) 1256 if found { 1257 eventLabels[k] = v 1258 } 1259 } 1260 1261 // Remove excluded event labels 1262 exKeys := settingsManager.GetExcludeEventLabelKeys() 1263 for k := range eventLabels { 1264 found := glob.MatchStringInList(exKeys, k, glob.GLOB) 1265 if found { 1266 delete(eventLabels, k) 1267 } 1268 } 1269 1270 return eventLabels 1271 }