github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/project.go (about) 1 package commands 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "slices" 10 "strings" 11 "text/tabwriter" 12 "time" 13 14 humanize "github.com/dustin/go-humanize" 15 log "github.com/sirupsen/logrus" 16 "github.com/spf13/cobra" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "sigs.k8s.io/yaml" 19 20 "github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless" 21 "github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils" 22 cmdutil "github.com/argoproj/argo-cd/v3/cmd/util" 23 argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient" 24 projectpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/project" 25 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 26 "github.com/argoproj/argo-cd/v3/util/cli" 27 "github.com/argoproj/argo-cd/v3/util/errors" 28 "github.com/argoproj/argo-cd/v3/util/git" 29 "github.com/argoproj/argo-cd/v3/util/gpg" 30 utilio "github.com/argoproj/argo-cd/v3/util/io" 31 "github.com/argoproj/argo-cd/v3/util/templates" 32 ) 33 34 type policyOpts struct { 35 action string 36 permission string 37 object string 38 resource string 39 } 40 41 // NewProjectCommand returns a new instance of an `argocd proj` command 42 func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 43 command := &cobra.Command{ 44 Use: "proj", 45 Short: "Manage projects", 46 Example: templates.Examples(` 47 # List all available projects 48 argocd proj list 49 50 # Create a new project with name PROJECT 51 argocd proj create PROJECT 52 53 # Delete the project with name PROJECT 54 argocd proj delete PROJECT 55 56 # Edit the information on project with name PROJECT 57 argocd proj edit PROJECT 58 `), 59 Run: func(c *cobra.Command, args []string) { 60 c.HelpFunc()(c, args) 61 os.Exit(1) 62 }, 63 } 64 command.AddCommand(NewProjectRoleCommand(clientOpts)) 65 command.AddCommand(NewProjectCreateCommand(clientOpts)) 66 command.AddCommand(NewProjectGetCommand(clientOpts)) 67 command.AddCommand(NewProjectDeleteCommand(clientOpts)) 68 command.AddCommand(NewProjectListCommand(clientOpts)) 69 command.AddCommand(NewProjectSetCommand(clientOpts)) 70 command.AddCommand(NewProjectEditCommand(clientOpts)) 71 command.AddCommand(NewProjectAddSignatureKeyCommand(clientOpts)) 72 command.AddCommand(NewProjectRemoveSignatureKeyCommand(clientOpts)) 73 command.AddCommand(NewProjectAddDestinationCommand(clientOpts)) 74 command.AddCommand(NewProjectRemoveDestinationCommand(clientOpts)) 75 command.AddCommand(NewProjectAddSourceCommand(clientOpts)) 76 command.AddCommand(NewProjectRemoveSourceCommand(clientOpts)) 77 command.AddCommand(NewProjectAllowClusterResourceCommand(clientOpts)) 78 command.AddCommand(NewProjectDenyClusterResourceCommand(clientOpts)) 79 command.AddCommand(NewProjectAllowNamespaceResourceCommand(clientOpts)) 80 command.AddCommand(NewProjectDenyNamespaceResourceCommand(clientOpts)) 81 command.AddCommand(NewProjectWindowsCommand(clientOpts)) 82 command.AddCommand(NewProjectAddOrphanedIgnoreCommand(clientOpts)) 83 command.AddCommand(NewProjectRemoveOrphanedIgnoreCommand(clientOpts)) 84 command.AddCommand(NewProjectAddSourceNamespace(clientOpts)) 85 command.AddCommand(NewProjectRemoveSourceNamespace(clientOpts)) 86 command.AddCommand(NewProjectAddDestinationServiceAccountCommand(clientOpts)) 87 command.AddCommand(NewProjectRemoveDestinationServiceAccountCommand(clientOpts)) 88 return command 89 } 90 91 func addPolicyFlags(command *cobra.Command, opts *policyOpts) { 92 command.Flags().StringVarP(&opts.action, "action", "a", "", "Action to grant/deny permission on (e.g. get, create, list, update, delete)") 93 command.Flags().StringVarP(&opts.permission, "permission", "p", "allow", "Whether to allow or deny access to object with the action. This can only be 'allow' or 'deny'") 94 command.Flags().StringVarP(&opts.object, "object", "o", "", "Object within the project to grant/deny access. Use '*' for a wildcard. Will want access to '<project>/<object>'") 95 command.Flags().StringVarP(&opts.resource, "resource", "r", "applications", "Resource e.g. 'applications', 'applicationsets', 'logs', 'exec', etc.") 96 } 97 98 func humanizeTimestamp(epoch int64) string { 99 ts := time.Unix(epoch, 0) 100 return fmt.Sprintf("%s (%s)", ts.Format(time.RFC3339), humanize.Time(ts)) 101 } 102 103 // NewProjectCreateCommand returns a new instance of an `argocd proj create` command 104 func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 105 var ( 106 opts cmdutil.ProjectOpts 107 fileURL string 108 upsert bool 109 ) 110 command := &cobra.Command{ 111 Use: "create PROJECT", 112 Short: "Create a project", 113 Example: templates.Examples(` 114 # Create a new project with name PROJECT 115 argocd proj create PROJECT 116 117 # Create a new project with name PROJECT from a file or URL to a Kubernetes manifest 118 argocd proj create PROJECT -f FILE|URL 119 `), 120 Run: func(c *cobra.Command, args []string) { 121 ctx := c.Context() 122 123 proj, err := cmdutil.ConstructAppProj(fileURL, args, opts, c) 124 errors.CheckError(err) 125 126 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 127 defer utilio.Close(conn) 128 _, err = projIf.Create(ctx, &projectpkg.ProjectCreateRequest{Project: proj, Upsert: upsert}) 129 errors.CheckError(err) 130 }, 131 } 132 command.Flags().BoolVar(&upsert, "upsert", false, "Allows to override a project with the same name even if supplied project spec is different from existing spec") 133 command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the project") 134 err := command.Flags().SetAnnotation("file", cobra.BashCompFilenameExt, []string{"json", "yaml", "yml"}) 135 if err != nil { 136 log.Fatal(err) 137 } 138 cmdutil.AddProjFlags(command, &opts) 139 return command 140 } 141 142 // NewProjectSetCommand returns a new instance of an `argocd proj set` command 143 func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 144 var opts cmdutil.ProjectOpts 145 command := &cobra.Command{ 146 Use: "set PROJECT", 147 Short: "Set project parameters", 148 Example: templates.Examples(` 149 # Set project parameters with some allowed cluster resources [RES1,RES2,...] for project with name PROJECT 150 argocd proj set PROJECT --allow-cluster-resource [RES1,RES2,...] 151 152 # Set project parameters with some denied namespaced resources [RES1,RES2,...] for project with name PROJECT 153 argocd proj set PROJECT ---deny-namespaced-resource [RES1,RES2,...] 154 `), 155 Run: func(c *cobra.Command, args []string) { 156 ctx := c.Context() 157 158 if len(args) == 0 { 159 c.HelpFunc()(c, args) 160 os.Exit(1) 161 } 162 projName := args[0] 163 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 164 defer utilio.Close(conn) 165 166 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 167 errors.CheckError(err) 168 169 if visited := cmdutil.SetProjSpecOptions(c.Flags(), &proj.Spec, &opts); visited == 0 { 170 log.Error("Please set at least one option to update") 171 c.HelpFunc()(c, args) 172 os.Exit(1) 173 } 174 175 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 176 errors.CheckError(err) 177 }, 178 } 179 cmdutil.AddProjFlags(command, &opts) 180 return command 181 } 182 183 // NewProjectAddSignatureKeyCommand returns a new instance of an `argocd proj add-signature-key` command 184 func NewProjectAddSignatureKeyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 185 command := &cobra.Command{ 186 Use: "add-signature-key PROJECT KEY-ID", 187 Short: "Add GnuPG signature key to project", 188 Example: templates.Examples(` 189 # Add GnuPG signature key KEY-ID to project PROJECT 190 argocd proj add-signature-key PROJECT KEY-ID 191 `), 192 Run: func(c *cobra.Command, args []string) { 193 ctx := c.Context() 194 195 if len(args) != 2 { 196 c.HelpFunc()(c, args) 197 os.Exit(1) 198 } 199 projName := args[0] 200 signatureKey := args[1] 201 202 if !gpg.IsShortKeyID(signatureKey) && !gpg.IsLongKeyID(signatureKey) { 203 log.Fatalf("%s is not a valid GnuPG key ID", signatureKey) 204 } 205 206 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 207 defer utilio.Close(conn) 208 209 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 210 errors.CheckError(err) 211 212 for _, key := range proj.Spec.SignatureKeys { 213 if key.KeyID == signatureKey { 214 log.Fatal("Specified signature key is already defined in project") 215 } 216 } 217 proj.Spec.SignatureKeys = append(proj.Spec.SignatureKeys, v1alpha1.SignatureKey{KeyID: signatureKey}) 218 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 219 errors.CheckError(err) 220 }, 221 } 222 return command 223 } 224 225 // NewProjectRemoveSignatureKeyCommand returns a new instance of an `argocd proj remove-signature-key` command 226 func NewProjectRemoveSignatureKeyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 227 command := &cobra.Command{ 228 Use: "remove-signature-key PROJECT KEY-ID", 229 Short: "Remove GnuPG signature key from project", 230 Example: templates.Examples(` 231 # Remove GnuPG signature key KEY-ID from project PROJECT 232 argocd proj remove-signature-key PROJECT KEY-ID 233 `), 234 Run: func(c *cobra.Command, args []string) { 235 ctx := c.Context() 236 237 if len(args) != 2 { 238 c.HelpFunc()(c, args) 239 os.Exit(1) 240 } 241 projName := args[0] 242 signatureKey := args[1] 243 244 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 245 defer utilio.Close(conn) 246 247 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 248 errors.CheckError(err) 249 250 index := -1 251 for i, key := range proj.Spec.SignatureKeys { 252 if key.KeyID == signatureKey { 253 index = i 254 break 255 } 256 } 257 if index == -1 { 258 log.Fatal("Specified signature key is not configured for project") 259 } 260 proj.Spec.SignatureKeys = append(proj.Spec.SignatureKeys[:index], proj.Spec.SignatureKeys[index+1:]...) 261 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 262 errors.CheckError(err) 263 }, 264 } 265 266 return command 267 } 268 269 // NewProjectAddDestinationCommand returns a new instance of an `argocd proj add-destination` command 270 func NewProjectAddDestinationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 271 var nameInsteadServer bool 272 273 buildApplicationDestination := func(destination string, namespace string, nameInsteadServer bool) v1alpha1.ApplicationDestination { 274 if nameInsteadServer { 275 return v1alpha1.ApplicationDestination{Name: destination, Namespace: namespace} 276 } 277 return v1alpha1.ApplicationDestination{Server: destination, Namespace: namespace} 278 } 279 280 command := &cobra.Command{ 281 Use: "add-destination PROJECT SERVER/NAME NAMESPACE", 282 Short: "Add project destination", 283 Example: templates.Examples(` 284 # Add project destination using a server URL (SERVER) in the specified namespace (NAMESPACE) on the project with name PROJECT 285 argocd proj add-destination PROJECT SERVER NAMESPACE 286 287 # Add project destination using a server name (NAME) in the specified namespace (NAMESPACE) on the project with name PROJECT 288 argocd proj add-destination PROJECT NAME NAMESPACE --name 289 `), 290 Run: func(c *cobra.Command, args []string) { 291 ctx := c.Context() 292 293 if len(args) != 3 { 294 c.HelpFunc()(c, args) 295 os.Exit(1) 296 } 297 projName := args[0] 298 namespace := args[2] 299 destination := buildApplicationDestination(args[1], namespace, nameInsteadServer) 300 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 301 defer utilio.Close(conn) 302 303 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 304 errors.CheckError(err) 305 306 for _, dest := range proj.Spec.Destinations { 307 dstServerExist := destination.Server != "" && dest.Server == destination.Server 308 dstNameExist := destination.Name != "" && dest.Name == destination.Name 309 if dest.Namespace == namespace && (dstServerExist || dstNameExist) { 310 log.Fatal("Specified destination is already defined in project") 311 } 312 } 313 proj.Spec.Destinations = append(proj.Spec.Destinations, destination) 314 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 315 errors.CheckError(err) 316 }, 317 } 318 command.Flags().BoolVar(&nameInsteadServer, "name", false, "Use name as destination instead server") 319 return command 320 } 321 322 // NewProjectRemoveDestinationCommand returns a new instance of an `argocd proj remove-destination` command 323 func NewProjectRemoveDestinationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 324 command := &cobra.Command{ 325 Use: "remove-destination PROJECT SERVER NAMESPACE", 326 Short: "Remove project destination", 327 Example: templates.Examples(` 328 # Remove the destination (SERVER) from the specified namespace (NAMESPACE) on the project with name PROJECT 329 argocd proj remove-destination PROJECT SERVER NAMESPACE 330 `), 331 Run: func(c *cobra.Command, args []string) { 332 ctx := c.Context() 333 334 if len(args) != 3 { 335 c.HelpFunc()(c, args) 336 os.Exit(1) 337 } 338 projName := args[0] 339 server := args[1] 340 namespace := args[2] 341 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 342 defer utilio.Close(conn) 343 344 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 345 errors.CheckError(err) 346 347 index := -1 348 for i, dest := range proj.Spec.Destinations { 349 if dest.Namespace == namespace && dest.Server == server { 350 index = i 351 break 352 } 353 } 354 if index == -1 { 355 log.Fatal("Specified destination does not exist in project") 356 } 357 proj.Spec.Destinations = append(proj.Spec.Destinations[:index], proj.Spec.Destinations[index+1:]...) 358 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 359 errors.CheckError(err) 360 }, 361 } 362 363 return command 364 } 365 366 // NewProjectAddOrphanedIgnoreCommand returns a new instance of an `argocd proj add-orphaned-ignore` command 367 func NewProjectAddOrphanedIgnoreCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 368 var name string 369 command := &cobra.Command{ 370 Use: "add-orphaned-ignore PROJECT GROUP KIND", 371 Short: "Add a resource to orphaned ignore list", 372 Example: templates.Examples(` 373 # Add a resource of the specified GROUP and KIND to orphaned ignore list on the project with name PROJECT 374 argocd proj add-orphaned-ignore PROJECT GROUP KIND 375 376 # Add resources of the specified GROUP and KIND using a NAME pattern to orphaned ignore list on the project with name PROJECT 377 argocd proj add-orphaned-ignore PROJECT GROUP KIND --name NAME 378 `), 379 Run: func(c *cobra.Command, args []string) { 380 ctx := c.Context() 381 382 if len(args) != 3 { 383 c.HelpFunc()(c, args) 384 os.Exit(1) 385 } 386 projName := args[0] 387 group := args[1] 388 kind := args[2] 389 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 390 defer utilio.Close(conn) 391 392 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 393 errors.CheckError(err) 394 395 if proj.Spec.OrphanedResources == nil { 396 settings := v1alpha1.OrphanedResourcesMonitorSettings{} 397 settings.Ignore = []v1alpha1.OrphanedResourceKey{{Group: group, Kind: kind, Name: name}} 398 proj.Spec.OrphanedResources = &settings 399 } else { 400 for _, ignore := range proj.Spec.OrphanedResources.Ignore { 401 if ignore.Group == group && ignore.Kind == kind && ignore.Name == name { 402 log.Fatal("Specified resource is already defined in the orphaned ignore list of project") 403 return 404 } 405 } 406 proj.Spec.OrphanedResources.Ignore = append(proj.Spec.OrphanedResources.Ignore, v1alpha1.OrphanedResourceKey{Group: group, Kind: kind, Name: name}) 407 } 408 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 409 errors.CheckError(err) 410 }, 411 } 412 command.Flags().StringVar(&name, "name", "", "Resource name pattern") 413 return command 414 } 415 416 // NewProjectRemoveOrphanedIgnoreCommand returns a new instance of an `argocd proj remove-orphaned-ignore` command 417 func NewProjectRemoveOrphanedIgnoreCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 418 var name string 419 command := &cobra.Command{ 420 Use: "remove-orphaned-ignore PROJECT GROUP KIND", 421 Short: "Remove a resource from orphaned ignore list", 422 Example: templates.Examples(` 423 # Remove a resource of the specified GROUP and KIND from orphaned ignore list on the project with name PROJECT 424 argocd proj remove-orphaned-ignore PROJECT GROUP KIND 425 426 # Remove resources of the specified GROUP and KIND using a NAME pattern from orphaned ignore list on the project with name PROJECT 427 argocd proj remove-orphaned-ignore PROJECT GROUP KIND --name NAME 428 `), 429 Run: func(c *cobra.Command, args []string) { 430 ctx := c.Context() 431 432 if len(args) != 3 { 433 c.HelpFunc()(c, args) 434 os.Exit(1) 435 } 436 projName := args[0] 437 group := args[1] 438 kind := args[2] 439 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 440 defer utilio.Close(conn) 441 442 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 443 errors.CheckError(err) 444 445 if proj.Spec.OrphanedResources == nil { 446 log.Fatal("Specified resource does not exist in the orphaned ignore list of project") 447 return 448 } 449 450 index := -1 451 for i, ignore := range proj.Spec.OrphanedResources.Ignore { 452 if ignore.Group == group && ignore.Kind == kind && ignore.Name == name { 453 index = i 454 break 455 } 456 } 457 if index == -1 { 458 log.Fatal("Specified resource does not exist in the orphaned ignore of project") 459 } 460 proj.Spec.OrphanedResources.Ignore = append(proj.Spec.OrphanedResources.Ignore[:index], proj.Spec.OrphanedResources.Ignore[index+1:]...) 461 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 462 errors.CheckError(err) 463 }, 464 } 465 command.Flags().StringVar(&name, "name", "", "Resource name pattern") 466 return command 467 } 468 469 // NewProjectAddSourceCommand returns a new instance of an `argocd proj add-src` command 470 func NewProjectAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 471 command := &cobra.Command{ 472 Use: "add-source PROJECT URL", 473 Short: "Add project source repository", 474 Example: templates.Examples(` 475 # Add a source repository (URL) to the project with name PROJECT 476 argocd proj add-source PROJECT URL 477 `), 478 Run: func(c *cobra.Command, args []string) { 479 ctx := c.Context() 480 481 if len(args) != 2 { 482 c.HelpFunc()(c, args) 483 os.Exit(1) 484 } 485 projName := args[0] 486 url := args[1] 487 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 488 defer utilio.Close(conn) 489 490 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 491 errors.CheckError(err) 492 493 for _, item := range proj.Spec.SourceRepos { 494 if item == "*" { 495 fmt.Printf("Source repository '*' already allowed in project\n") 496 return 497 } 498 if git.SameURL(item, url) { 499 fmt.Printf("Source repository '%s' already allowed in project\n", item) 500 return 501 } 502 } 503 proj.Spec.SourceRepos = append(proj.Spec.SourceRepos, url) 504 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 505 errors.CheckError(err) 506 }, 507 } 508 return command 509 } 510 511 // NewProjectAddSourceNamespace returns a new instance of an `argocd proj add-source-namespace` command 512 func NewProjectAddSourceNamespace(clientOpts *argocdclient.ClientOptions) *cobra.Command { 513 command := &cobra.Command{ 514 Use: "add-source-namespace PROJECT NAMESPACE", 515 Short: "Add source namespace to the AppProject", 516 Example: templates.Examples(` 517 # Add Kubernetes namespace as source namespace to the AppProject where application resources are allowed to be created in. 518 argocd proj add-source-namespace PROJECT NAMESPACE 519 `), 520 Run: func(c *cobra.Command, args []string) { 521 ctx := c.Context() 522 523 if len(args) != 2 { 524 c.HelpFunc()(c, args) 525 os.Exit(1) 526 } 527 projName := args[0] 528 srcNamespace := args[1] 529 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 530 defer utilio.Close(conn) 531 532 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 533 errors.CheckError(err) 534 535 for _, item := range proj.Spec.SourceNamespaces { 536 if item == "*" || item == srcNamespace { 537 fmt.Printf("Source namespace '*' already allowed in project\n") 538 return 539 } 540 } 541 proj.Spec.SourceNamespaces = append(proj.Spec.SourceNamespaces, srcNamespace) 542 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 543 errors.CheckError(err) 544 }, 545 } 546 return command 547 } 548 549 // NewProjectRemoveSourceNamespace returns a new instance of an `argocd proj remove-source-namespace` command 550 func NewProjectRemoveSourceNamespace(clientOpts *argocdclient.ClientOptions) *cobra.Command { 551 command := &cobra.Command{ 552 Use: "remove-source-namespace PROJECT NAMESPACE", 553 Short: "Removes the source namespace from the AppProject", 554 Example: templates.Examples(` 555 # Remove source NAMESPACE in PROJECT 556 argocd proj remove-source-namespace PROJECT NAMESPACE 557 `), 558 Run: func(c *cobra.Command, args []string) { 559 ctx := c.Context() 560 561 if len(args) != 2 { 562 c.HelpFunc()(c, args) 563 os.Exit(1) 564 } 565 projName := args[0] 566 srcNamespace := args[1] 567 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 568 defer utilio.Close(conn) 569 570 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 571 errors.CheckError(err) 572 573 index := -1 574 for i, item := range proj.Spec.SourceNamespaces { 575 if item == srcNamespace && item != "*" { 576 index = i 577 break 578 } 579 } 580 if index == -1 { 581 fmt.Printf("Source namespace '%s' does not exist in project or cannot be removed\n", srcNamespace) 582 } else { 583 proj.Spec.SourceNamespaces = append(proj.Spec.SourceNamespaces[:index], proj.Spec.SourceNamespaces[index+1:]...) 584 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 585 errors.CheckError(err) 586 } 587 }, 588 } 589 590 return command 591 } 592 593 func modifyResourcesList(list *[]metav1.GroupKind, add bool, listDesc string, group string, kind string) bool { 594 if add { 595 for _, item := range *list { 596 if item.Group == group && item.Kind == kind { 597 fmt.Printf("Group '%s' and kind '%s' already present in %s resources\n", group, kind, listDesc) 598 return false 599 } 600 } 601 fmt.Printf("Group '%s' and kind '%s' is added to %s resources\n", group, kind, listDesc) 602 *list = append(*list, metav1.GroupKind{Group: group, Kind: kind}) 603 return true 604 } 605 index := -1 606 for i, item := range *list { 607 if item.Group == group && item.Kind == kind { 608 index = i 609 break 610 } 611 } 612 if index == -1 { 613 fmt.Printf("Group '%s' and kind '%s' not in %s resources\n", group, kind, listDesc) 614 return false 615 } 616 *list = append((*list)[:index], (*list)[index+1:]...) 617 fmt.Printf("Group '%s' and kind '%s' is removed from %s resources\n", group, kind, listDesc) 618 return true 619 } 620 621 func modifyResourceListCmd(cmdUse, cmdDesc, examples string, clientOpts *argocdclient.ClientOptions, allow bool, namespacedList bool) *cobra.Command { 622 var ( 623 listType string 624 defaultList string 625 ) 626 if namespacedList { 627 defaultList = "deny" 628 } else { 629 defaultList = "allow" 630 } 631 command := &cobra.Command{ 632 Use: cmdUse, 633 Short: cmdDesc, 634 Example: templates.Examples(examples), 635 Run: func(c *cobra.Command, args []string) { 636 ctx := c.Context() 637 638 if len(args) != 3 { 639 c.HelpFunc()(c, args) 640 os.Exit(1) 641 } 642 projName, group, kind := args[0], args[1], args[2] 643 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 644 defer utilio.Close(conn) 645 646 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 647 errors.CheckError(err) 648 var list, allowList, denyList *[]metav1.GroupKind 649 var listAction, listDesc string 650 var add bool 651 if namespacedList { 652 allowList, denyList = &proj.Spec.NamespaceResourceWhitelist, &proj.Spec.NamespaceResourceBlacklist 653 listDesc = "namespaced" 654 } else { 655 allowList, denyList = &proj.Spec.ClusterResourceWhitelist, &proj.Spec.ClusterResourceBlacklist 656 listDesc = "cluster" 657 } 658 659 if (listType == "allow") || (listType == "white") { 660 list = allowList 661 listAction = "allowed" 662 add = allow 663 } else { 664 list = denyList 665 listAction = "denied" 666 add = !allow 667 } 668 669 if modifyResourcesList(list, add, listAction+" "+listDesc, group, kind) { 670 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 671 errors.CheckError(err) 672 } 673 }, 674 } 675 command.Flags().StringVarP(&listType, "list", "l", defaultList, "Use deny list or allow list. This can only be 'allow' or 'deny'") 676 return command 677 } 678 679 // NewProjectAllowNamespaceResourceCommand returns a new instance of an `deny-cluster-resources` command 680 func NewProjectAllowNamespaceResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 681 use := "allow-namespace-resource PROJECT GROUP KIND" 682 desc := "Removes a namespaced API resource from the deny list or add a namespaced API resource to the allow list" 683 examples := ` 684 # Removes a namespaced API resource with specified GROUP and KIND from the deny list or add a namespaced API resource to the allow list for project PROJECT 685 argocd proj allow-namespace-resource PROJECT GROUP KIND 686 ` 687 return modifyResourceListCmd(use, desc, examples, clientOpts, true, true) 688 } 689 690 // NewProjectDenyNamespaceResourceCommand returns a new instance of an `argocd proj deny-namespace-resource` command 691 func NewProjectDenyNamespaceResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 692 use := "deny-namespace-resource PROJECT GROUP KIND" 693 desc := "Adds a namespaced API resource to the deny list or removes a namespaced API resource from the allow list" 694 examples := ` 695 # Adds a namespaced API resource with specified GROUP and KIND from the deny list or removes a namespaced API resource from the allow list for project PROJECT 696 argocd proj deny-namespace-resource PROJECT GROUP KIND 697 ` 698 return modifyResourceListCmd(use, desc, examples, clientOpts, false, true) 699 } 700 701 // NewProjectDenyClusterResourceCommand returns a new instance of an `deny-cluster-resource` command 702 func NewProjectDenyClusterResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 703 use := "deny-cluster-resource PROJECT GROUP KIND" 704 desc := "Removes a cluster-scoped API resource from the allow list and adds it to deny list" 705 examples := ` 706 # Removes a cluster-scoped API resource with specified GROUP and KIND from the allow list and adds it to deny list for project PROJECT 707 argocd proj deny-cluster-resource PROJECT GROUP KIND 708 ` 709 return modifyResourceListCmd(use, desc, examples, clientOpts, false, false) 710 } 711 712 // NewProjectAllowClusterResourceCommand returns a new instance of an `argocd proj allow-cluster-resource` command 713 func NewProjectAllowClusterResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 714 use := "allow-cluster-resource PROJECT GROUP KIND" 715 desc := "Adds a cluster-scoped API resource to the allow list and removes it from deny list" 716 examples := ` 717 # Adds a cluster-scoped API resource with specified GROUP and KIND to the allow list and removes it from deny list for project PROJECT 718 argocd proj allow-cluster-resource PROJECT GROUP KIND 719 ` 720 return modifyResourceListCmd(use, desc, examples, clientOpts, true, false) 721 } 722 723 // NewProjectRemoveSourceCommand returns a new instance of an `argocd proj remove-src` command 724 func NewProjectRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 725 command := &cobra.Command{ 726 Use: "remove-source PROJECT URL", 727 Short: "Remove project source repository", 728 Example: templates.Examples(` 729 # Remove URL source repository to project PROJECT 730 argocd proj remove-source PROJECT URL 731 `), 732 Run: func(c *cobra.Command, args []string) { 733 ctx := c.Context() 734 735 if len(args) != 2 { 736 c.HelpFunc()(c, args) 737 os.Exit(1) 738 } 739 projName := args[0] 740 url := args[1] 741 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 742 defer utilio.Close(conn) 743 744 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 745 errors.CheckError(err) 746 747 index := -1 748 for i, item := range proj.Spec.SourceRepos { 749 if item == url { 750 index = i 751 break 752 } 753 } 754 if index == -1 { 755 fmt.Printf("Source repository '%s' does not exist in project\n", url) 756 } else { 757 proj.Spec.SourceRepos = append(proj.Spec.SourceRepos[:index], proj.Spec.SourceRepos[index+1:]...) 758 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 759 errors.CheckError(err) 760 } 761 }, 762 } 763 764 return command 765 } 766 767 // NewProjectDeleteCommand returns a new instance of an `argocd proj delete` command 768 func NewProjectDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 769 command := &cobra.Command{ 770 Use: "delete PROJECT", 771 Short: "Delete project", 772 Example: templates.Examples(` 773 # Delete the project with name PROJECT 774 argocd proj delete PROJECT 775 `), 776 Run: func(c *cobra.Command, args []string) { 777 ctx := c.Context() 778 779 if len(args) == 0 { 780 c.HelpFunc()(c, args) 781 os.Exit(1) 782 } 783 784 promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled) 785 786 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 787 defer utilio.Close(conn) 788 for _, name := range args { 789 canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to delete %s? [y/n]", name)) 790 if canDelete { 791 _, err := projIf.Delete(ctx, &projectpkg.ProjectQuery{Name: name}) 792 errors.CheckError(err) 793 } else { 794 fmt.Printf("The command to delete %s was cancelled.\n", name) 795 } 796 } 797 }, 798 } 799 return command 800 } 801 802 // Print list of project names 803 func printProjectNames(projects []v1alpha1.AppProject) { 804 for _, p := range projects { 805 fmt.Println(p.Name) 806 } 807 } 808 809 // Print table of project info 810 func printProjectTable(projects []v1alpha1.AppProject) { 811 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 812 fmt.Fprintf(w, "NAME\tDESCRIPTION\tDESTINATIONS\tSOURCES\tCLUSTER-RESOURCE-WHITELIST\tNAMESPACE-RESOURCE-BLACKLIST\tSIGNATURE-KEYS\tORPHANED-RESOURCES\tDESTINATION-SERVICE-ACCOUNTS\n") 813 for _, p := range projects { 814 printProjectLine(w, &p) 815 } 816 _ = w.Flush() 817 } 818 819 // NewProjectListCommand returns a new instance of an `argocd proj list` command 820 func NewProjectListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 821 var output string 822 command := &cobra.Command{ 823 Use: "list", 824 Short: "List projects", 825 Example: templates.Examples(` 826 # List all available projects 827 argocd proj list 828 829 # List all available projects in yaml format 830 argocd proj list -o yaml 831 `), 832 Run: func(c *cobra.Command, _ []string) { 833 ctx := c.Context() 834 835 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 836 defer utilio.Close(conn) 837 projects, err := projIf.List(ctx, &projectpkg.ProjectQuery{}) 838 errors.CheckError(err) 839 switch output { 840 case "yaml", "json": 841 err := PrintResourceList(projects.Items, output, false) 842 errors.CheckError(err) 843 case "name": 844 printProjectNames(projects.Items) 845 case "wide", "": 846 printProjectTable(projects.Items) 847 default: 848 errors.CheckError(fmt.Errorf("unknown output format: %s", output)) 849 } 850 }, 851 } 852 command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|name") 853 return command 854 } 855 856 func formatOrphanedResources(p *v1alpha1.AppProject) string { 857 if p.Spec.OrphanedResources == nil { 858 return "disabled" 859 } 860 details := fmt.Sprintf("warn=%v", p.Spec.OrphanedResources.IsWarn()) 861 if len(p.Spec.OrphanedResources.Ignore) > 0 { 862 details = fmt.Sprintf("%s, ignored %d", details, len(p.Spec.OrphanedResources.Ignore)) 863 } 864 return fmt.Sprintf("enabled (%s)", details) 865 } 866 867 func printProjectLine(w io.Writer, p *v1alpha1.AppProject) { 868 var destinations, destinationServiceAccounts, sourceRepos, clusterWhitelist, namespaceBlacklist, signatureKeys string 869 switch len(p.Spec.Destinations) { 870 case 0: 871 destinations = "<none>" 872 case 1: 873 destinations = fmt.Sprintf("%s,%s", p.Spec.Destinations[0].Server, p.Spec.Destinations[0].Namespace) 874 default: 875 destinations = fmt.Sprintf("%d destinations", len(p.Spec.Destinations)) 876 } 877 switch len(p.Spec.DestinationServiceAccounts) { 878 case 0: 879 destinationServiceAccounts = "<none>" 880 case 1: 881 destinationServiceAccounts = fmt.Sprintf("%s,%s,%s", p.Spec.DestinationServiceAccounts[0].Server, p.Spec.DestinationServiceAccounts[0].Namespace, p.Spec.DestinationServiceAccounts[0].DefaultServiceAccount) 882 default: 883 destinationServiceAccounts = fmt.Sprintf("%d destinationServiceAccounts", len(p.Spec.DestinationServiceAccounts)) 884 } 885 switch len(p.Spec.SourceRepos) { 886 case 0: 887 sourceRepos = "<none>" 888 case 1: 889 sourceRepos = p.Spec.SourceRepos[0] 890 default: 891 sourceRepos = fmt.Sprintf("%d repos", len(p.Spec.SourceRepos)) 892 } 893 switch len(p.Spec.ClusterResourceWhitelist) { 894 case 0: 895 clusterWhitelist = "<none>" 896 case 1: 897 clusterWhitelist = fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[0].Group, p.Spec.ClusterResourceWhitelist[0].Kind) 898 default: 899 clusterWhitelist = fmt.Sprintf("%d resources", len(p.Spec.ClusterResourceWhitelist)) 900 } 901 switch len(p.Spec.NamespaceResourceBlacklist) { 902 case 0: 903 namespaceBlacklist = "<none>" 904 default: 905 namespaceBlacklist = fmt.Sprintf("%d resources", len(p.Spec.NamespaceResourceBlacklist)) 906 } 907 switch len(p.Spec.SignatureKeys) { 908 case 0: 909 signatureKeys = "<none>" 910 default: 911 signatureKeys = fmt.Sprintf("%d key(s)", len(p.Spec.SignatureKeys)) 912 } 913 fmt.Fprintf(w, "%s\t%s\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", p.Name, p.Spec.Description, destinations, sourceRepos, clusterWhitelist, namespaceBlacklist, signatureKeys, formatOrphanedResources(p), destinationServiceAccounts) 914 } 915 916 func printProject(p *v1alpha1.AppProject, scopedRepositories []*v1alpha1.Repository, scopedClusters []*v1alpha1.Cluster) { 917 const printProjFmtStr = "%-29s%s\n" 918 919 fmt.Printf(printProjFmtStr, "Name:", p.Name) 920 fmt.Printf(printProjFmtStr, "Description:", p.Spec.Description) 921 922 // Print destinations 923 dest0 := "<none>" 924 if len(p.Spec.Destinations) > 0 { 925 dest0 = fmt.Sprintf("%s,%s", p.Spec.Destinations[0].Server, p.Spec.Destinations[0].Namespace) 926 } 927 fmt.Printf(printProjFmtStr, "Destinations:", dest0) 928 for i := 1; i < len(p.Spec.Destinations); i++ { 929 fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s,%s", p.Spec.Destinations[i].Server, p.Spec.Destinations[i].Namespace)) 930 } 931 932 // Print sources 933 src0 := "<none>" 934 if len(p.Spec.SourceRepos) > 0 { 935 src0 = p.Spec.SourceRepos[0] 936 } 937 fmt.Printf(printProjFmtStr, "Repositories:", src0) 938 for i := 1; i < len(p.Spec.SourceRepos); i++ { 939 fmt.Printf(printProjFmtStr, "", p.Spec.SourceRepos[i]) 940 } 941 942 // Print source namespaces 943 ns0 := "<none>" 944 if len(p.Spec.SourceNamespaces) > 0 { 945 ns0 = p.Spec.SourceNamespaces[0] 946 } 947 fmt.Printf(printProjFmtStr, "Source Namespaces:", ns0) 948 for i := 1; i < len(p.Spec.SourceNamespaces); i++ { 949 fmt.Printf(printProjFmtStr, "", p.Spec.SourceNamespaces[i]) 950 } 951 952 // Print scoped repositories 953 scr0 := "<none>" 954 if len(scopedRepositories) > 0 { 955 scr0 = scopedRepositories[0].Repo 956 } 957 fmt.Printf(printProjFmtStr, "Scoped Repositories:", scr0) 958 for i := 1; i < len(scopedRepositories); i++ { 959 fmt.Printf(printProjFmtStr, "", scopedRepositories[i].Repo) 960 } 961 962 // Print allowed cluster resources 963 cwl0 := "<none>" 964 if len(p.Spec.ClusterResourceWhitelist) > 0 { 965 cwl0 = fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[0].Group, p.Spec.ClusterResourceWhitelist[0].Kind) 966 } 967 fmt.Printf(printProjFmtStr, "Allowed Cluster Resources:", cwl0) 968 for i := 1; i < len(p.Spec.ClusterResourceWhitelist); i++ { 969 fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[i].Group, p.Spec.ClusterResourceWhitelist[i].Kind)) 970 } 971 972 // Print scoped clusters 973 scc0 := "<none>" 974 if len(scopedClusters) > 0 { 975 scc0 = scopedClusters[0].Server 976 } 977 fmt.Printf(printProjFmtStr, "Scoped Clusters:", scc0) 978 for i := 1; i < len(scopedClusters); i++ { 979 fmt.Printf(printProjFmtStr, "", scopedClusters[i].Server) 980 } 981 982 // Print denied namespaced resources 983 rbl0 := "<none>" 984 if len(p.Spec.NamespaceResourceBlacklist) > 0 { 985 rbl0 = fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[0].Group, p.Spec.NamespaceResourceBlacklist[0].Kind) 986 } 987 fmt.Printf(printProjFmtStr, "Denied Namespaced Resources:", rbl0) 988 for i := 1; i < len(p.Spec.NamespaceResourceBlacklist); i++ { 989 fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[i].Group, p.Spec.NamespaceResourceBlacklist[i].Kind)) 990 } 991 992 // Print required signature keys 993 signatureKeysStr := "<none>" 994 if len(p.Spec.SignatureKeys) > 0 { 995 kids := make([]string, 0) 996 for _, key := range p.Spec.SignatureKeys { 997 kids = append(kids, key.KeyID) 998 } 999 signatureKeysStr = strings.Join(kids, ", ") 1000 } 1001 fmt.Printf(printProjFmtStr, "Signature keys:", signatureKeysStr) 1002 1003 fmt.Printf(printProjFmtStr, "Orphaned Resources:", formatOrphanedResources(p)) 1004 } 1005 1006 // NewProjectGetCommand returns a new instance of an `argocd proj get` command 1007 func NewProjectGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 1008 var output string 1009 command := &cobra.Command{ 1010 Use: "get PROJECT", 1011 Short: "Get project details", 1012 Example: templates.Examples(` 1013 # Get details from project PROJECT 1014 argocd proj get PROJECT 1015 1016 # Get details from project PROJECT in yaml format 1017 argocd proj get PROJECT -o yaml 1018 1019 `), 1020 Run: func(c *cobra.Command, args []string) { 1021 ctx := c.Context() 1022 1023 if len(args) != 1 { 1024 c.HelpFunc()(c, args) 1025 os.Exit(1) 1026 } 1027 projName := args[0] 1028 detailedProject := getProject(ctx, c, clientOpts, projName) 1029 1030 switch output { 1031 case "yaml", "json": 1032 err := PrintResource(detailedProject.Project, output) 1033 errors.CheckError(err) 1034 case "wide", "": 1035 printProject(detailedProject.Project, detailedProject.Repositories, detailedProject.Clusters) 1036 default: 1037 errors.CheckError(fmt.Errorf("unknown output format: %s", output)) 1038 } 1039 }, 1040 } 1041 command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide") 1042 return command 1043 } 1044 1045 func getProject(ctx context.Context, c *cobra.Command, clientOpts *argocdclient.ClientOptions, projName string) *projectpkg.DetailedProjectsResponse { 1046 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 1047 defer utilio.Close(conn) 1048 detailedProject, err := projIf.GetDetailedProject(ctx, &projectpkg.ProjectQuery{Name: projName}) 1049 errors.CheckError(err) 1050 return detailedProject 1051 } 1052 1053 func NewProjectEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 1054 command := &cobra.Command{ 1055 Use: "edit PROJECT", 1056 Short: "Edit project", 1057 Example: templates.Examples(` 1058 # Edit the information on project with name PROJECT 1059 argocd proj edit PROJECT 1060 `), 1061 Run: func(c *cobra.Command, args []string) { 1062 ctx := c.Context() 1063 1064 if len(args) != 1 { 1065 c.HelpFunc()(c, args) 1066 os.Exit(1) 1067 } 1068 projName := args[0] 1069 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 1070 defer utilio.Close(conn) 1071 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 1072 errors.CheckError(err) 1073 projData, err := json.Marshal(proj.Spec) 1074 errors.CheckError(err) 1075 projData, err = yaml.JSONToYAML(projData) 1076 errors.CheckError(err) 1077 1078 cli.InteractiveEdit(projName+"-*-edit.yaml", projData, func(input []byte) error { 1079 input, err = yaml.YAMLToJSON(input) 1080 if err != nil { 1081 return fmt.Errorf("error converting YAML to JSON: %w", err) 1082 } 1083 updatedSpec := v1alpha1.AppProjectSpec{} 1084 err = json.Unmarshal(input, &updatedSpec) 1085 if err != nil { 1086 return fmt.Errorf("error unmarshaling input into application spec: %w", err) 1087 } 1088 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 1089 if err != nil { 1090 return fmt.Errorf("could not get project by project name: %w", err) 1091 } 1092 proj.Spec = updatedSpec 1093 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 1094 if err != nil { 1095 return fmt.Errorf("failed to update project:\n%w", err) 1096 } 1097 return nil 1098 }) 1099 }, 1100 } 1101 return command 1102 } 1103 1104 // NewProjectAddDestinationServiceAccountCommand returns a new instance of an `argocd proj add-destination-service-account` command 1105 func NewProjectAddDestinationServiceAccountCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 1106 var serviceAccountNamespace string 1107 1108 buildApplicationDestinationServiceAccount := func(destination string, namespace string, serviceAccount string, serviceAccountNamespace string) v1alpha1.ApplicationDestinationServiceAccount { 1109 if serviceAccountNamespace != "" { 1110 return v1alpha1.ApplicationDestinationServiceAccount{ 1111 Server: destination, 1112 Namespace: namespace, 1113 DefaultServiceAccount: fmt.Sprintf("%s:%s", serviceAccountNamespace, serviceAccount), 1114 } 1115 } 1116 return v1alpha1.ApplicationDestinationServiceAccount{ 1117 Server: destination, 1118 Namespace: namespace, 1119 DefaultServiceAccount: serviceAccount, 1120 } 1121 } 1122 1123 command := &cobra.Command{ 1124 Use: "add-destination-service-account PROJECT SERVER NAMESPACE SERVICE_ACCOUNT", 1125 Short: "Add project destination's default service account", 1126 Example: templates.Examples(` 1127 # Add project destination service account (SERVICE_ACCOUNT) for a server URL (SERVER) in the specified namespace (NAMESPACE) on the project with name PROJECT 1128 argocd proj add-destination-service-account PROJECT SERVER NAMESPACE SERVICE_ACCOUNT 1129 1130 # Add project destination service account (SERVICE_ACCOUNT) from a different namespace 1131 argocd proj add-destination PROJECT SERVER NAMESPACE SERVICE_ACCOUNT --service-account-namespace <service_account_namespace> 1132 `), 1133 Run: func(c *cobra.Command, args []string) { 1134 ctx := c.Context() 1135 1136 if len(args) != 4 { 1137 c.HelpFunc()(c, args) 1138 os.Exit(1) 1139 } 1140 projName := args[0] 1141 server := args[1] 1142 namespace := args[2] 1143 serviceAccount := args[3] 1144 1145 if strings.Contains(serviceAccountNamespace, "*") { 1146 log.Fatal("service-account-namespace for DestinationServiceAccount must not contain wildcards") 1147 } 1148 1149 if strings.Contains(serviceAccount, "*") { 1150 log.Fatal("ServiceAccount for DestinationServiceAccount must not contain wildcards") 1151 } 1152 1153 destinationServiceAccount := buildApplicationDestinationServiceAccount(server, namespace, serviceAccount, serviceAccountNamespace) 1154 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 1155 defer utilio.Close(conn) 1156 1157 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 1158 errors.CheckError(err) 1159 1160 for _, dest := range proj.Spec.DestinationServiceAccounts { 1161 dstServerExist := destinationServiceAccount.Server != "" && dest.Server == destinationServiceAccount.Server 1162 dstServiceAccountExist := destinationServiceAccount.DefaultServiceAccount != "" && dest.DefaultServiceAccount == destinationServiceAccount.DefaultServiceAccount 1163 if dest.Namespace == destinationServiceAccount.Namespace && dstServerExist && dstServiceAccountExist { 1164 log.Fatal("Specified destination service account is already defined in project") 1165 } 1166 } 1167 proj.Spec.DestinationServiceAccounts = append(proj.Spec.DestinationServiceAccounts, destinationServiceAccount) 1168 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 1169 errors.CheckError(err) 1170 }, 1171 } 1172 command.Flags().StringVar(&serviceAccountNamespace, "service-account-namespace", "", "Use service-account-namespace as namespace where the service account is present") 1173 return command 1174 } 1175 1176 // NewProjectRemoveDestinationCommand returns a new instance of an `argocd proj remove-destination-service-account` command 1177 func NewProjectRemoveDestinationServiceAccountCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 1178 command := &cobra.Command{ 1179 Use: "remove-destination-service-account PROJECT SERVER NAMESPACE SERVICE_ACCOUNT", 1180 Short: "Remove default destination service account from the project", 1181 Example: templates.Examples(` 1182 # Remove the destination service account (SERVICE_ACCOUNT) from the specified destination (SERVER and NAMESPACE combination) on the project with name PROJECT 1183 argocd proj remove-destination-service-account PROJECT SERVER NAMESPACE SERVICE_ACCOUNT 1184 `), 1185 Run: func(c *cobra.Command, args []string) { 1186 ctx := c.Context() 1187 1188 if len(args) != 4 { 1189 c.HelpFunc()(c, args) 1190 os.Exit(1) 1191 } 1192 projName := args[0] 1193 server := args[1] 1194 namespace := args[2] 1195 serviceAccount := args[3] 1196 conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie() 1197 defer utilio.Close(conn) 1198 1199 proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName}) 1200 errors.CheckError(err) 1201 1202 originalLength := len(proj.Spec.DestinationServiceAccounts) 1203 proj.Spec.DestinationServiceAccounts = slices.DeleteFunc(proj.Spec.DestinationServiceAccounts, 1204 func(destServiceAccount v1alpha1.ApplicationDestinationServiceAccount) bool { 1205 return destServiceAccount.Namespace == namespace && 1206 destServiceAccount.Server == server && 1207 destServiceAccount.DefaultServiceAccount == serviceAccount 1208 }, 1209 ) 1210 if originalLength == len(proj.Spec.DestinationServiceAccounts) { 1211 log.Fatal("Specified destination service account does not exist in project") 1212 } 1213 _, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj}) 1214 errors.CheckError(err) 1215 }, 1216 } 1217 1218 return command 1219 }