github.com/argoproj/argo-cd/v3@v3.2.1/server/project/project.go (about) 1 package project 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 9 "github.com/argoproj/gitops-engine/pkg/utils/kube" 10 "github.com/argoproj/pkg/v2/sync" 11 "github.com/golang-jwt/jwt/v5" 12 "github.com/google/uuid" 13 log "github.com/sirupsen/logrus" 14 "google.golang.org/grpc/codes" 15 "google.golang.org/grpc/status" 16 corev1 "k8s.io/api/core/v1" 17 apierrors "k8s.io/apimachinery/pkg/api/errors" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 "k8s.io/apimachinery/pkg/fields" 20 "k8s.io/client-go/kubernetes" 21 "k8s.io/client-go/tools/cache" 22 "k8s.io/client-go/util/retry" 23 24 "github.com/argoproj/argo-cd/v3/pkg/apiclient/application" 25 "github.com/argoproj/argo-cd/v3/pkg/apiclient/project" 26 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 27 appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned" 28 listersv1alpha1 "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1" 29 "github.com/argoproj/argo-cd/v3/server/deeplinks" 30 "github.com/argoproj/argo-cd/v3/server/rbacpolicy" 31 "github.com/argoproj/argo-cd/v3/util/argo" 32 "github.com/argoproj/argo-cd/v3/util/db" 33 jwtutil "github.com/argoproj/argo-cd/v3/util/jwt" 34 "github.com/argoproj/argo-cd/v3/util/rbac" 35 "github.com/argoproj/argo-cd/v3/util/session" 36 "github.com/argoproj/argo-cd/v3/util/settings" 37 ) 38 39 const ( 40 // JWTTokenSubFormat format of the JWT token subject that Argo CD vends out. 41 JWTTokenSubFormat = "proj:%s:%s" 42 ) 43 44 // Server provides a Project service 45 type Server struct { 46 ns string 47 enf *rbac.Enforcer 48 policyEnf *rbacpolicy.RBACPolicyEnforcer 49 appclientset appclientset.Interface 50 kubeclientset kubernetes.Interface 51 auditLogger *argo.AuditLogger 52 projectLock sync.KeyLock 53 sessionMgr *session.SessionManager 54 projInformer cache.SharedIndexInformer 55 settingsMgr *settings.SettingsManager 56 db db.ArgoDB 57 } 58 59 // NewServer returns a new instance of the Project service 60 func NewServer(ns string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, enf *rbac.Enforcer, projectLock sync.KeyLock, sessionMgr *session.SessionManager, policyEnf *rbacpolicy.RBACPolicyEnforcer, 61 projInformer cache.SharedIndexInformer, settingsMgr *settings.SettingsManager, db db.ArgoDB, enableK8sEvent []string, 62 ) *Server { 63 auditLogger := argo.NewAuditLogger(kubeclientset, "argocd-server", enableK8sEvent) 64 return &Server{ 65 enf: enf, policyEnf: policyEnf, appclientset: appclientset, kubeclientset: kubeclientset, ns: ns, projectLock: projectLock, auditLogger: auditLogger, sessionMgr: sessionMgr, 66 projInformer: projInformer, settingsMgr: settingsMgr, db: db, 67 } 68 } 69 70 func validateProject(proj *v1alpha1.AppProject) error { 71 err := proj.ValidateProject() 72 if err != nil { 73 return err 74 } 75 err = rbac.ValidatePolicy(proj.ProjectPoliciesString()) 76 if err != nil { 77 return status.Errorf(codes.InvalidArgument, "policy syntax error: %s", err.Error()) 78 } 79 return nil 80 } 81 82 // CreateToken creates a new token to access a project 83 func (s *Server) CreateToken(ctx context.Context, q *project.ProjectTokenCreateRequest) (*project.ProjectTokenResponse, error) { 84 var resp *project.ProjectTokenResponse 85 err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 86 var createErr error 87 resp, createErr = s.createToken(ctx, q) 88 return createErr 89 }) 90 return resp, err 91 } 92 93 func (s *Server) createToken(ctx context.Context, q *project.ProjectTokenCreateRequest) (*project.ProjectTokenResponse, error) { 94 prj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project, metav1.GetOptions{}) 95 if err != nil { 96 return nil, err 97 } 98 err = validateProject(prj) 99 if err != nil { 100 return nil, fmt.Errorf("error validating project: %w", err) 101 } 102 103 s.projectLock.Lock(q.Project) 104 defer s.projectLock.Unlock(q.Project) 105 106 role, _, err := prj.GetRoleByName(q.Role) 107 if err != nil { 108 return nil, status.Errorf(codes.NotFound, "project '%s' does not have role '%s'", q.Project, q.Role) 109 } 110 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionUpdate, q.Project); err != nil { 111 if !jwtutil.IsMember(jwtutil.Claims(ctx.Value("claims")), role.Groups, s.policyEnf.GetScopes()) { 112 return nil, err 113 } 114 } 115 id := q.Id 116 if err := prj.ValidateJWTTokenID(q.Role, q.Id); err != nil { 117 return nil, status.Error(codes.InvalidArgument, err.Error()) 118 } 119 if id == "" { 120 uniqueId, _ := uuid.NewRandom() 121 id = uniqueId.String() 122 } 123 subject := fmt.Sprintf(JWTTokenSubFormat, q.Project, q.Role) 124 jwtToken, err := s.sessionMgr.Create(subject, q.ExpiresIn, id) 125 if err != nil { 126 return nil, status.Error(codes.InvalidArgument, err.Error()) 127 } 128 parser := jwt.NewParser(jwt.WithoutClaimsValidation()) 129 claims := jwt.RegisteredClaims{} 130 _, _, err = parser.ParseUnverified(jwtToken, &claims) 131 if err != nil { 132 return nil, status.Error(codes.InvalidArgument, err.Error()) 133 } 134 var issuedAt, expiresAt int64 135 if claims.IssuedAt != nil { 136 issuedAt = claims.IssuedAt.Unix() 137 } 138 if claims.ExpiresAt != nil { 139 expiresAt = claims.ExpiresAt.Unix() 140 } 141 id = claims.ID 142 143 prj.NormalizeJWTTokens() 144 145 items := append(prj.Status.JWTTokensByRole[q.Role].Items, v1alpha1.JWTToken{IssuedAt: issuedAt, ExpiresAt: expiresAt, ID: id}) 146 if _, found := prj.Status.JWTTokensByRole[q.Role]; found { 147 prj.Status.JWTTokensByRole[q.Role] = v1alpha1.JWTTokens{Items: items} 148 } else { 149 tokensMap := make(map[string]v1alpha1.JWTTokens) 150 tokensMap[q.Role] = v1alpha1.JWTTokens{Items: items} 151 prj.Status.JWTTokensByRole = tokensMap 152 } 153 154 prj.NormalizeJWTTokens() 155 156 _, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, prj, metav1.UpdateOptions{}) 157 if err != nil { 158 return nil, err 159 } 160 s.logEvent(ctx, prj, argo.EventReasonResourceCreated, "created token") 161 return &project.ProjectTokenResponse{Token: jwtToken}, nil 162 } 163 164 func (s *Server) ListLinks(ctx context.Context, q *project.ListProjectLinksRequest) (*application.LinksResponse, error) { 165 projName := q.GetName() 166 167 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionGet, projName); err != nil { 168 log.WithFields(map[string]any{ 169 "project": projName, 170 }).Warnf("unauthorized access to project, error=%v", err.Error()) 171 return nil, fmt.Errorf("unauthorized access to project %v", projName) 172 } 173 174 proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, projName, metav1.GetOptions{}) 175 if err != nil { 176 return nil, err 177 } 178 179 // sanitize project jwt tokens 180 proj.Status = v1alpha1.AppProjectStatus{} 181 182 obj, err := kube.ToUnstructured(proj) 183 if err != nil { 184 return nil, fmt.Errorf("error getting application: %w", err) 185 } 186 187 deepLinks, err := s.settingsMgr.GetDeepLinks(settings.ProjectDeepLinks) 188 if err != nil { 189 return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err) 190 } 191 192 deeplinksObj := deeplinks.CreateDeepLinksObject(nil, nil, nil, obj) 193 finalList, errorList := deeplinks.EvaluateDeepLinksResponse(deeplinksObj, obj.GetName(), deepLinks) 194 if len(errorList) > 0 { 195 log.Errorf("errorList while evaluating project deep links, %v", strings.Join(errorList, ", ")) 196 } 197 198 return finalList, nil 199 } 200 201 // DeleteToken deletes a token in a project 202 func (s *Server) DeleteToken(ctx context.Context, q *project.ProjectTokenDeleteRequest) (*project.EmptyResponse, error) { 203 var resp *project.EmptyResponse 204 err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 205 var deleteErr error 206 resp, deleteErr = s.deleteToken(ctx, q) 207 return deleteErr 208 }) 209 return resp, err 210 } 211 212 func (s *Server) deleteToken(ctx context.Context, q *project.ProjectTokenDeleteRequest) (*project.EmptyResponse, error) { 213 prj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project, metav1.GetOptions{}) 214 if err != nil { 215 return nil, err 216 } 217 err = validateProject(prj) 218 if err != nil { 219 return nil, fmt.Errorf("error validating project: %w", err) 220 } 221 222 s.projectLock.Lock(q.Project) 223 defer s.projectLock.Unlock(q.Project) 224 225 role, roleIndex, err := prj.GetRoleByName(q.Role) 226 if err != nil { 227 return &project.EmptyResponse{}, nil 228 } 229 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionUpdate, q.Project); err != nil { 230 if !jwtutil.IsMember(jwtutil.Claims(ctx.Value("claims")), role.Groups, s.policyEnf.GetScopes()) { 231 return nil, err 232 } 233 } 234 235 err = prj.RemoveJWTToken(roleIndex, q.Iat, q.Id) 236 if err != nil { 237 return &project.EmptyResponse{}, nil 238 } 239 240 _, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, prj, metav1.UpdateOptions{}) 241 if err != nil { 242 return nil, err 243 } 244 s.logEvent(ctx, prj, argo.EventReasonResourceDeleted, "deleted token") 245 246 return &project.EmptyResponse{}, nil 247 } 248 249 // Create a new project 250 func (s *Server) Create(ctx context.Context, q *project.ProjectCreateRequest) (*v1alpha1.AppProject, error) { 251 if q.Project == nil { 252 return nil, status.Errorf(codes.InvalidArgument, "missing payload 'project' in request") 253 } 254 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionCreate, q.Project.Name); err != nil { 255 return nil, err 256 } 257 q.Project.NormalizePolicies() 258 err := validateProject(q.Project) 259 if err != nil { 260 return nil, fmt.Errorf("error validating project: %w", err) 261 } 262 res, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Create(ctx, q.Project, metav1.CreateOptions{}) 263 if apierrors.IsAlreadyExists(err) { 264 existing, getErr := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project.Name, metav1.GetOptions{}) 265 if getErr != nil { 266 return nil, status.Errorf(codes.Internal, "unable to check existing project details: %v", getErr) 267 } 268 if !q.GetUpsert() { 269 if !reflect.DeepEqual(existing.Spec, q.GetProject().Spec) { 270 return nil, status.Error(codes.InvalidArgument, argo.GenerateSpecIsDifferentErrorMessage("project", existing.Spec, q.GetProject().Spec)) 271 } 272 return existing, nil 273 } 274 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionUpdate, q.GetProject().Name); err != nil { 275 return nil, err 276 } 277 existing.Spec = q.GetProject().Spec 278 res, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, existing, metav1.UpdateOptions{}) 279 } 280 if err == nil { 281 s.logEvent(ctx, res, argo.EventReasonResourceCreated, "created project") 282 } 283 return res, err 284 } 285 286 // List returns list of projects 287 func (s *Server) List(ctx context.Context, _ *project.ProjectQuery) (*v1alpha1.AppProjectList, error) { 288 list, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).List(ctx, metav1.ListOptions{}) 289 if list != nil { 290 newItems := make([]v1alpha1.AppProject, 0) 291 for i := range list.Items { 292 project := list.Items[i] 293 if s.enf.Enforce(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionGet, project.Name) { 294 newItems = append(newItems, project) 295 } 296 } 297 list.Items = newItems 298 } 299 return list, err 300 } 301 302 // GetDetailedProject returns a project with scoped resources 303 func (s *Server) GetDetailedProject(ctx context.Context, q *project.ProjectQuery) (*project.DetailedProjectsResponse, error) { 304 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionGet, q.Name); err != nil { 305 return nil, err 306 } 307 proj, repositories, clusters, err := argo.GetAppProjectWithScopedResources(ctx, q.Name, listersv1alpha1.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db) 308 if err != nil { 309 return nil, err 310 } 311 proj.NormalizeJWTTokens() 312 globalProjects := argo.GetGlobalProjects(proj, listersv1alpha1.NewAppProjectLister(s.projInformer.GetIndexer()), s.settingsMgr) 313 var apiRepos []*v1alpha1.Repository 314 for _, repo := range repositories { 315 apiRepos = append(apiRepos, repo.Normalize().Sanitized()) 316 } 317 var apiClusters []*v1alpha1.Cluster 318 for _, cluster := range clusters { 319 apiClusters = append(apiClusters, cluster.Sanitized()) 320 } 321 322 return &project.DetailedProjectsResponse{ 323 GlobalProjects: globalProjects, 324 Project: proj, 325 Repositories: apiRepos, 326 Clusters: apiClusters, 327 }, err 328 } 329 330 // Get returns a project by name 331 func (s *Server) Get(ctx context.Context, q *project.ProjectQuery) (*v1alpha1.AppProject, error) { 332 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionGet, q.Name); err != nil { 333 return nil, err 334 } 335 proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{}) 336 if err != nil { 337 return nil, err 338 } 339 proj.NormalizeJWTTokens() 340 return proj, err 341 } 342 343 // GetGlobalProjects returns global projects 344 func (s *Server) GetGlobalProjects(ctx context.Context, q *project.ProjectQuery) (*project.GlobalProjectsResponse, error) { 345 projOrig, err := s.Get(ctx, q) 346 if err != nil { 347 return nil, err 348 } 349 350 globalProjects := argo.GetGlobalProjects(projOrig, listersv1alpha1.NewAppProjectLister(s.projInformer.GetIndexer()), s.settingsMgr) 351 352 res := &project.GlobalProjectsResponse{} 353 res.Items = globalProjects 354 return res, nil 355 } 356 357 // Update updates a project 358 func (s *Server) Update(ctx context.Context, q *project.ProjectUpdateRequest) (*v1alpha1.AppProject, error) { 359 if q.Project == nil { 360 return nil, status.Errorf(codes.InvalidArgument, "missing payload 'project' in request") 361 } 362 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionUpdate, q.Project.Name); err != nil { 363 return nil, err 364 } 365 q.Project.NormalizePolicies() 366 q.Project.NormalizeJWTTokens() 367 err := validateProject(q.Project) 368 if err != nil { 369 return nil, err 370 } 371 s.projectLock.Lock(q.Project.Name) 372 defer s.projectLock.Unlock(q.Project.Name) 373 374 oldProj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Project.Name, metav1.GetOptions{}) 375 if err != nil { 376 return nil, err 377 } 378 379 for _, cluster := range difference(q.Project.Spec.DestinationClusters(), oldProj.Spec.DestinationClusters()) { 380 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceClusters, rbac.ActionUpdate, cluster); err != nil { 381 return nil, err 382 } 383 } 384 385 for _, repoURL := range difference(q.Project.Spec.SourceRepos, oldProj.Spec.SourceRepos) { 386 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceRepositories, rbac.ActionUpdate, repoURL); err != nil { 387 return nil, err 388 } 389 } 390 391 clusterResourceWhitelistsEqual := reflect.DeepEqual(q.Project.Spec.ClusterResourceWhitelist, oldProj.Spec.ClusterResourceWhitelist) 392 clusterResourceBlacklistsEqual := reflect.DeepEqual(q.Project.Spec.ClusterResourceBlacklist, oldProj.Spec.ClusterResourceBlacklist) 393 namespacesResourceBlacklistsEqual := reflect.DeepEqual(q.Project.Spec.NamespaceResourceBlacklist, oldProj.Spec.NamespaceResourceBlacklist) 394 namespacesResourceWhitelistsEqual := reflect.DeepEqual(q.Project.Spec.NamespaceResourceWhitelist, oldProj.Spec.NamespaceResourceWhitelist) 395 if !clusterResourceWhitelistsEqual || !clusterResourceBlacklistsEqual || !namespacesResourceBlacklistsEqual || !namespacesResourceWhitelistsEqual { 396 for _, cluster := range q.Project.Spec.DestinationClusters() { 397 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceClusters, rbac.ActionUpdate, cluster); err != nil { 398 return nil, err 399 } 400 } 401 } 402 403 appsList, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(ctx, metav1.ListOptions{}) 404 if err != nil { 405 return nil, err 406 } 407 408 getProjectClusters := func(project string) ([]*v1alpha1.Cluster, error) { 409 return s.db.GetProjectClusters(ctx, project) 410 } 411 412 invalidSrcCount := 0 413 invalidDstCount := 0 414 415 for _, a := range argo.FilterByProjects(appsList.Items, []string{q.Project.Name}) { 416 if oldProj.IsSourcePermitted(a.Spec.GetSource()) && !q.Project.IsSourcePermitted(a.Spec.GetSource()) { 417 invalidSrcCount++ 418 } 419 420 destCluster, err := argo.GetDestinationCluster(ctx, a.Spec.Destination, s.db) 421 if err != nil { 422 if err.Error() != argo.ErrDestinationMissing { 423 // If cluster is not found, we should discard this app, as it's most likely already in error 424 continue 425 } 426 invalidDstCount++ 427 } 428 dstPermitted, err := oldProj.IsDestinationPermitted(destCluster, a.Spec.Destination.Namespace, getProjectClusters) 429 if err != nil { 430 return nil, err 431 } 432 433 if dstPermitted { 434 dstPermitted, err = q.Project.IsDestinationPermitted(destCluster, a.Spec.Destination.Namespace, getProjectClusters) 435 if err != nil { 436 return nil, err 437 } 438 if !dstPermitted { 439 invalidDstCount++ 440 } 441 } 442 } 443 444 var parts []string 445 if invalidSrcCount > 0 { 446 parts = append(parts, fmt.Sprintf("%d applications source became invalid", invalidSrcCount)) 447 } 448 if invalidDstCount > 0 { 449 parts = append(parts, fmt.Sprintf("%d applications destination became invalid", invalidDstCount)) 450 } 451 if len(parts) > 0 { 452 return nil, status.Errorf(codes.InvalidArgument, "as a result of project update %s", strings.Join(parts, " and ")) 453 } 454 455 res, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(ctx, q.Project, metav1.UpdateOptions{}) 456 if err == nil { 457 s.logEvent(ctx, res, argo.EventReasonResourceUpdated, "updated project") 458 } 459 return res, err 460 } 461 462 // Delete deletes a project 463 func (s *Server) Delete(ctx context.Context, q *project.ProjectQuery) (*project.EmptyResponse, error) { 464 if q.Name == v1alpha1.DefaultAppProjectName { 465 return nil, status.Errorf(codes.InvalidArgument, "name '%s' is reserved and cannot be deleted", q.Name) 466 } 467 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionDelete, q.Name); err != nil { 468 return nil, err 469 } 470 471 s.projectLock.Lock(q.Name) 472 defer s.projectLock.Unlock(q.Name) 473 474 p, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{}) 475 if err != nil { 476 return nil, err 477 } 478 479 appsList, err := s.appclientset.ArgoprojV1alpha1().Applications(s.ns).List(ctx, metav1.ListOptions{}) 480 if err != nil { 481 return nil, err 482 } 483 apps := argo.FilterByProjects(appsList.Items, []string{q.Name}) 484 if len(apps) > 0 { 485 return nil, status.Errorf(codes.InvalidArgument, "project is referenced by %d applications", len(apps)) 486 } 487 err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Delete(ctx, q.Name, metav1.DeleteOptions{}) 488 if err == nil { 489 s.logEvent(ctx, p, argo.EventReasonResourceDeleted, "deleted project") 490 } 491 return &project.EmptyResponse{}, err 492 } 493 494 func (s *Server) ListEvents(ctx context.Context, q *project.ProjectQuery) (*corev1.EventList, error) { 495 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionGet, q.Name); err != nil { 496 return nil, err 497 } 498 proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{}) 499 if err != nil { 500 return nil, err 501 } 502 fieldSelector := fields.SelectorFromSet(map[string]string{ 503 "involvedObject.name": proj.Name, 504 "involvedObject.uid": string(proj.UID), 505 "involvedObject.namespace": proj.Namespace, 506 }).String() 507 return s.kubeclientset.CoreV1().Events(s.ns).List(ctx, metav1.ListOptions{FieldSelector: fieldSelector}) 508 } 509 510 func (s *Server) logEvent(ctx context.Context, a *v1alpha1.AppProject, reason string, action string) { 511 eventInfo := argo.EventInfo{Type: corev1.EventTypeNormal, Reason: reason} 512 user := session.Username(ctx) 513 if user == "" { 514 user = "Unknown user" 515 } 516 message := fmt.Sprintf("%s %s", user, action) 517 s.auditLogger.LogAppProjEvent(a, eventInfo, message, user) 518 } 519 520 func (s *Server) GetSyncWindowsState(ctx context.Context, q *project.SyncWindowsQuery) (*project.SyncWindowsResponse, error) { 521 if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceProjects, rbac.ActionGet, q.Name); err != nil { 522 return nil, err 523 } 524 proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, q.Name, metav1.GetOptions{}) 525 if err != nil { 526 return nil, err 527 } 528 529 res := &project.SyncWindowsResponse{} 530 531 windows, err := proj.Spec.SyncWindows.Active() 532 if err != nil { 533 return nil, err 534 } 535 if windows.HasWindows() { 536 res.Windows = *windows 537 } else { 538 res.Windows = []*v1alpha1.SyncWindow{} 539 } 540 541 return res, nil 542 } 543 544 func (s *Server) NormalizeProjs() error { 545 projList, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).List(context.Background(), metav1.ListOptions{}) 546 if err != nil { 547 return status.Errorf(codes.Internal, "Error retrieving project list: %s", err.Error()) 548 } 549 for _, proj := range projList.Items { 550 for i := 0; i < 3; i++ { 551 if !proj.NormalizeJWTTokens() { 552 break 553 } 554 _, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(context.Background(), &proj, metav1.UpdateOptions{}) 555 if err == nil { 556 log.Infof("Successfully normalized project %s.", proj.Name) 557 break 558 } 559 if !apierrors.IsConflict(err) { 560 log.Warnf("Failed normalize project %s", proj.Name) 561 break 562 } 563 projGet, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(context.Background(), proj.Name, metav1.GetOptions{}) 564 if err != nil { 565 return status.Errorf(codes.Internal, "Error retrieving project: %s", err.Error()) 566 } 567 proj = *projGet 568 if i == 2 { 569 return status.Errorf(codes.Internal, "Failed normalize project %s", proj.Name) 570 } 571 } 572 } 573 return nil 574 }