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  }