github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/project_role.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"text/tabwriter"
     8  	"time"
     9  
    10  	timeutil "github.com/argoproj/pkg/v2/time"
    11  	jwtgo "github.com/golang-jwt/jwt/v5"
    12  	"github.com/spf13/cobra"
    13  
    14  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
    15  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
    16  	argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
    17  	projectpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/project"
    18  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    19  	"github.com/argoproj/argo-cd/v3/util/errors"
    20  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    21  	"github.com/argoproj/argo-cd/v3/util/jwt"
    22  	"github.com/argoproj/argo-cd/v3/util/rbac"
    23  	"github.com/argoproj/argo-cd/v3/util/templates"
    24  )
    25  
    26  const (
    27  	policyTemplate = "p, proj:%s:%s, %s, %s, %s/%s, %s"
    28  )
    29  
    30  // NewProjectRoleCommand returns a new instance of the `argocd proj role` command
    31  func NewProjectRoleCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    32  	roleCommand := &cobra.Command{
    33  		Use:   "role",
    34  		Short: "Manage a project's roles",
    35  		Run: func(c *cobra.Command, args []string) {
    36  			c.HelpFunc()(c, args)
    37  			os.Exit(1)
    38  		},
    39  	}
    40  	roleCommand.AddCommand(NewProjectRoleListCommand(clientOpts))
    41  	roleCommand.AddCommand(NewProjectRoleGetCommand(clientOpts))
    42  	roleCommand.AddCommand(NewProjectRoleCreateCommand(clientOpts))
    43  	roleCommand.AddCommand(NewProjectRoleDeleteCommand(clientOpts))
    44  	roleCommand.AddCommand(NewProjectRoleCreateTokenCommand(clientOpts))
    45  	roleCommand.AddCommand(NewProjectRoleListTokensCommand(clientOpts))
    46  	roleCommand.AddCommand(NewProjectRoleDeleteTokenCommand(clientOpts))
    47  	roleCommand.AddCommand(NewProjectRoleAddPolicyCommand(clientOpts))
    48  	roleCommand.AddCommand(NewProjectRoleRemovePolicyCommand(clientOpts))
    49  	roleCommand.AddCommand(NewProjectRoleAddGroupCommand(clientOpts))
    50  	roleCommand.AddCommand(NewProjectRoleRemoveGroupCommand(clientOpts))
    51  	return roleCommand
    52  }
    53  
    54  // NewProjectRoleAddPolicyCommand returns a new instance of an `argocd proj role add-policy` command
    55  func NewProjectRoleAddPolicyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    56  	var opts policyOpts
    57  	command := &cobra.Command{
    58  		Use:   "add-policy PROJECT ROLE-NAME",
    59  		Short: "Add a policy to a project role",
    60  		Example: `# Before adding new policy
    61  $ argocd proj role get test-project test-role
    62  Role Name:     test-role
    63  Description:
    64  Policies:
    65  p, proj:test-project:test-role, projects, get, test-project, allow
    66  JWT Tokens:
    67  ID          ISSUED-AT                                EXPIRES-AT
    68  1696759698  2023-10-08T11:08:18+01:00 (3 hours ago)  <none>
    69  
    70  # Add a new policy to allow update to the project
    71  $ argocd proj role add-policy test-project test-role -a update -p allow -o project
    72  
    73  # Policy should be updated
    74  $  argocd proj role get test-project test-role
    75  Role Name:     test-role
    76  Description:
    77  Policies:
    78  p, proj:test-project:test-role, projects, get, test-project, allow
    79  p, proj:test-project:test-role, applications, update, test-project/project, allow
    80  JWT Tokens:
    81  ID          ISSUED-AT                                EXPIRES-AT
    82  1696759698  2023-10-08T11:08:18+01:00 (3 hours ago)  <none>
    83  
    84  # Add a new policy to allow get logs to the project
    85  $ argocd proj role add-policy test-project test-role -a get -p allow -o project -r logs
    86  
    87  # Policy should be updated
    88  $  argocd proj role get test-project test-role
    89  Role Name:     test-role
    90  Description:
    91  Policies:
    92  p, proj:test-project:test-role, projects, get, test-project, allow
    93  p, proj:test-project:test-role, applications, update, test-project/project, allow
    94  p, proj:test-project:test-role, logs, get, test-project/project, allow
    95  JWT Tokens:
    96  ID          ISSUED-AT                                EXPIRES-AT
    97  1696759698  2023-10-08T11:08:18+01:00 (3 hours ago)  <none>
    98  `,
    99  		Run: func(c *cobra.Command, args []string) {
   100  			ctx := c.Context()
   101  
   102  			if len(args) != 2 || !rbac.ProjectScoped[opts.resource] {
   103  				c.HelpFunc()(c, args)
   104  				os.Exit(1)
   105  			}
   106  			projName := args[0]
   107  			roleName := args[1]
   108  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   109  			defer utilio.Close(conn)
   110  
   111  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   112  			errors.CheckError(err)
   113  
   114  			role, roleIndex, err := proj.GetRoleByName(roleName)
   115  			errors.CheckError(err)
   116  
   117  			policy := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.resource, opts.action, proj.Name, opts.object, opts.permission)
   118  			proj.Spec.Roles[roleIndex].Policies = append(role.Policies, policy)
   119  
   120  			_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   121  			errors.CheckError(err)
   122  		},
   123  	}
   124  	addPolicyFlags(command, &opts)
   125  	return command
   126  }
   127  
   128  // NewProjectRoleRemovePolicyCommand returns a new instance of an `argocd proj role remove-policy` command
   129  func NewProjectRoleRemovePolicyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   130  	var opts policyOpts
   131  	command := &cobra.Command{
   132  		Use:   "remove-policy PROJECT ROLE-NAME",
   133  		Short: "Remove a policy from a role within a project",
   134  		Example: `List the policy of the test-role before removing a policy
   135  $ argocd proj role get test-project test-role
   136  Role Name:     test-role
   137  Description:
   138  Policies:
   139  p, proj:test-project:test-role, projects, get, test-project, allow
   140  p, proj:test-project:test-role, applications, update, test-project/project, allow
   141  p, proj:test-project:test-role, logs, get, test-project/project, allow
   142  JWT Tokens:
   143  ID          ISSUED-AT                                EXPIRES-AT
   144  1696759698  2023-10-08T11:08:18+01:00 (3 hours ago)  <none>
   145  
   146  # Remove the policy to allow update to objects
   147  $ argocd proj role remove-policy test-project test-role -a update -p allow -o project
   148  
   149  # The role should be removed now.
   150  $ argocd proj role get test-project test-role
   151  Role Name:     test-role
   152  Description:
   153  Policies:
   154  p, proj:test-project:test-role, projects, get, test-project, allow
   155  p, proj:test-project:test-role, logs, get, test-project/project, allow
   156  JWT Tokens:
   157  ID          ISSUED-AT                                EXPIRES-AT
   158  1696759698  2023-10-08T11:08:18+01:00 (4 hours ago)  <none>
   159  
   160  
   161  # Remove the logs read policy
   162  $ argocd proj role remove-policy test-project test-role -a get -p allow -o project -r logs
   163  
   164  # The role should be removed now.
   165  $ argocd proj role get test-project test-role
   166  Role Name:     test-role
   167  Description:
   168  Policies:
   169  p, proj:test-project:test-role, projects, get, test-project, allow
   170  JWT Tokens:
   171  ID          ISSUED-AT                                EXPIRES-AT
   172  1696759698  2023-10-08T11:08:18+01:00 (4 hours ago)  <none>
   173  `,
   174  		Run: func(c *cobra.Command, args []string) {
   175  			ctx := c.Context()
   176  
   177  			if len(args) != 2 || !rbac.ProjectScoped[opts.resource] {
   178  				c.HelpFunc()(c, args)
   179  				os.Exit(1)
   180  			}
   181  			projName := args[0]
   182  			roleName := args[1]
   183  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   184  			defer utilio.Close(conn)
   185  
   186  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   187  			errors.CheckError(err)
   188  
   189  			role, roleIndex, err := proj.GetRoleByName(roleName)
   190  			errors.CheckError(err)
   191  
   192  			policyToRemove := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.resource, opts.action, proj.Name, opts.object, opts.permission)
   193  			duplicateIndex := -1
   194  			for i, policy := range role.Policies {
   195  				if policy == policyToRemove {
   196  					duplicateIndex = i
   197  					break
   198  				}
   199  			}
   200  			if duplicateIndex < 0 {
   201  				return
   202  			}
   203  			role.Policies[duplicateIndex] = role.Policies[len(role.Policies)-1]
   204  			proj.Spec.Roles[roleIndex].Policies = role.Policies[:len(role.Policies)-1]
   205  
   206  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
   207  			canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to delete '%s' policy? [y/n]", policyToRemove))
   208  			if canDelete {
   209  				_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   210  				errors.CheckError(err)
   211  			} else {
   212  				fmt.Printf("The command to delete policy '%s' was cancelled.\n", policyToRemove)
   213  			}
   214  		},
   215  	}
   216  	addPolicyFlags(command, &opts)
   217  	return command
   218  }
   219  
   220  // NewProjectRoleCreateCommand returns a new instance of an `argocd proj role create` command
   221  func NewProjectRoleCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   222  	var description string
   223  	command := &cobra.Command{
   224  		Use:   "create PROJECT ROLE-NAME",
   225  		Short: "Create a project role",
   226  		Example: templates.Examples(`
   227    # Create a project role in the "my-project" project with the name "my-role".
   228    argocd proj role create my-project my-role --description "My project role description"
   229    		`),
   230  
   231  		Run: func(c *cobra.Command, args []string) {
   232  			ctx := c.Context()
   233  
   234  			if len(args) != 2 {
   235  				c.HelpFunc()(c, args)
   236  				os.Exit(1)
   237  			}
   238  			projName := args[0]
   239  			roleName := args[1]
   240  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   241  			defer utilio.Close(conn)
   242  
   243  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   244  			errors.CheckError(err)
   245  
   246  			_, _, err = proj.GetRoleByName(roleName)
   247  			if err == nil {
   248  				fmt.Printf("Role '%s' already exists\n", roleName)
   249  				return
   250  			}
   251  			proj.Spec.Roles = append(proj.Spec.Roles, v1alpha1.ProjectRole{Name: roleName, Description: description})
   252  
   253  			_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   254  			errors.CheckError(err)
   255  			fmt.Printf("Role '%s' created\n", roleName)
   256  		},
   257  	}
   258  	command.Flags().StringVarP(&description, "description", "", "", "Project description")
   259  	return command
   260  }
   261  
   262  // NewProjectRoleDeleteCommand returns a new instance of an `argocd proj role delete` command
   263  func NewProjectRoleDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   264  	command := &cobra.Command{
   265  		Use:     "delete PROJECT ROLE-NAME",
   266  		Short:   "Delete a project role",
   267  		Example: `$ argocd proj role delete test-project test-role`,
   268  		Run: func(c *cobra.Command, args []string) {
   269  			ctx := c.Context()
   270  
   271  			if len(args) != 2 {
   272  				c.HelpFunc()(c, args)
   273  				os.Exit(1)
   274  			}
   275  			projName := args[0]
   276  			roleName := args[1]
   277  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   278  			defer utilio.Close(conn)
   279  
   280  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
   281  
   282  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   283  			errors.CheckError(err)
   284  
   285  			_, index, err := proj.GetRoleByName(roleName)
   286  			if err != nil {
   287  				fmt.Printf("Role '%s' does not exist in project\n", roleName)
   288  				return
   289  			}
   290  			proj.Spec.Roles[index] = proj.Spec.Roles[len(proj.Spec.Roles)-1]
   291  			proj.Spec.Roles = proj.Spec.Roles[:len(proj.Spec.Roles)-1]
   292  
   293  			canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to delete '%s' role? [y/n]", roleName))
   294  			if canDelete {
   295  				_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   296  				errors.CheckError(err)
   297  				fmt.Printf("Role '%s' deleted\n", roleName)
   298  			} else {
   299  				fmt.Printf("The command to delete role '%s' was cancelled.\n", roleName)
   300  			}
   301  		},
   302  	}
   303  	return command
   304  }
   305  
   306  func tokenTimeToString(t int64) string {
   307  	tokenTimeToString := "Never"
   308  	if t > 0 {
   309  		tokenTimeToString = time.Unix(t, 0).Format(time.RFC3339)
   310  	}
   311  	return tokenTimeToString
   312  }
   313  
   314  // NewProjectRoleCreateTokenCommand returns a new instance of an `argocd proj role create-token` command
   315  func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   316  	var (
   317  		expiresIn       string
   318  		outputTokenOnly bool
   319  		tokenID         string
   320  	)
   321  	command := &cobra.Command{
   322  		Use:   "create-token PROJECT ROLE-NAME",
   323  		Short: "Create a project token",
   324  		Example: `$ argocd proj role create-token test-project test-role
   325  Create token succeeded for proj:test-project:test-role.
   326    ID: f316c466-40bd-4cfd-8a8c-1392e92255d4
   327    Issued At: 2023-10-08T15:21:40+01:00
   328    Expires At: Never
   329    Token: xxx
   330  `,
   331  		Aliases: []string{"token-create"},
   332  		Run: func(c *cobra.Command, args []string) {
   333  			ctx := c.Context()
   334  
   335  			if len(args) != 2 {
   336  				c.HelpFunc()(c, args)
   337  				os.Exit(1)
   338  			}
   339  			projName := args[0]
   340  			roleName := args[1]
   341  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   342  			defer utilio.Close(conn)
   343  			if expiresIn == "" {
   344  				expiresIn = "0s"
   345  			}
   346  			duration, err := timeutil.ParseDuration(expiresIn)
   347  			errors.CheckError(err)
   348  			tokenResponse, err := projIf.CreateToken(ctx, &projectpkg.ProjectTokenCreateRequest{
   349  				Project:   projName,
   350  				Role:      roleName,
   351  				ExpiresIn: int64(duration.Seconds()),
   352  				Id:        tokenID,
   353  			})
   354  			errors.CheckError(err)
   355  
   356  			token, err := jwtgo.Parse(tokenResponse.Token, nil)
   357  			if token == nil {
   358  				err = fmt.Errorf("received malformed token %w", err)
   359  				errors.CheckError(err)
   360  				return
   361  			}
   362  
   363  			claims := token.Claims.(jwtgo.MapClaims)
   364  
   365  			issuedAt, _ := jwt.IssuedAt(claims)
   366  			expiresAt := int64(jwt.Float64Field(claims, "exp"))
   367  			id := jwt.StringField(claims, "jti")
   368  			subject := jwt.GetUserIdentifier(claims)
   369  			if !outputTokenOnly {
   370  				fmt.Printf("Create token succeeded for %s.\n", subject)
   371  				fmt.Printf("  ID: %s\n  Issued At: %s\n  Expires At: %s\n",
   372  					id, tokenTimeToString(issuedAt), tokenTimeToString(expiresAt),
   373  				)
   374  				fmt.Println("  Token: " + tokenResponse.Token)
   375  			} else {
   376  				fmt.Println(tokenResponse.Token)
   377  			}
   378  		},
   379  	}
   380  	command.Flags().StringVarP(&expiresIn, "expires-in", "e", "",
   381  		"Duration before the token will expire, e.g. \"12h\", \"7d\". (Default: No expiration)",
   382  	)
   383  	command.Flags().StringVarP(&tokenID, "id", "i", "", "Token unique identifier. (Default: Random UUID)")
   384  	command.Flags().BoolVarP(&outputTokenOnly, "token-only", "t", false, "Output token only - for use in scripts.")
   385  
   386  	return command
   387  }
   388  
   389  func NewProjectRoleListTokensCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   390  	var useUnixTime bool
   391  	command := &cobra.Command{
   392  		Use:   "list-tokens PROJECT ROLE-NAME",
   393  		Short: "List tokens for a given role.",
   394  		Example: `$ argocd proj role list-tokens test-project test-role
   395  ID                                      ISSUED AT                    EXPIRES AT
   396  f316c466-40bd-4cfd-8a8c-1392e92255d4    2023-10-08T15:21:40+01:00    Never
   397  fa9d3517-c52d-434c-9bff-215b38508842    2023-10-08T11:08:18+01:00    Never
   398  `,
   399  		Aliases: []string{"list-token", "token-list"},
   400  		Run: func(c *cobra.Command, args []string) {
   401  			ctx := c.Context()
   402  
   403  			if len(args) != 2 {
   404  				c.HelpFunc()(c, args)
   405  				os.Exit(1)
   406  			}
   407  			projName := args[0]
   408  			roleName := args[1]
   409  
   410  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   411  			defer utilio.Close(conn)
   412  
   413  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   414  			errors.CheckError(err)
   415  			role, _, err := proj.GetRoleByName(roleName)
   416  			errors.CheckError(err)
   417  
   418  			if len(role.JWTTokens) == 0 {
   419  				fmt.Printf("No tokens for %s.%s\n", projName, roleName)
   420  				return
   421  			}
   422  
   423  			writer := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
   424  			_, err = fmt.Fprintf(writer, "ID\tISSUED AT\tEXPIRES AT\n")
   425  			errors.CheckError(err)
   426  
   427  			tokenRowFormat := "%s\t%v\t%v\n"
   428  			for _, token := range role.JWTTokens {
   429  				if useUnixTime {
   430  					_, _ = fmt.Fprintf(writer, tokenRowFormat, token.ID, token.IssuedAt, token.ExpiresAt)
   431  				} else {
   432  					_, _ = fmt.Fprintf(writer, tokenRowFormat, token.ID, tokenTimeToString(token.IssuedAt), tokenTimeToString(token.ExpiresAt))
   433  				}
   434  			}
   435  			err = writer.Flush()
   436  			errors.CheckError(err)
   437  		},
   438  	}
   439  	command.Flags().BoolVarP(&useUnixTime, "unixtime", "u", false,
   440  		"Print timestamps as Unix time instead of converting. Useful for piping into delete-token.",
   441  	)
   442  	return command
   443  }
   444  
   445  // NewProjectRoleDeleteTokenCommand returns a new instance of an `argocd proj role delete-token` command
   446  func NewProjectRoleDeleteTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   447  	command := &cobra.Command{
   448  		Use:   "delete-token PROJECT ROLE-NAME ISSUED-AT",
   449  		Short: "Delete a project token",
   450  		Example: `#Create project test-project
   451  $ argocd proj create test-project
   452  
   453  # Create a role associated with test-project
   454  $ argocd proj role create test-project test-role
   455  Role 'test-role' created
   456  
   457  # Create test-role associated with test-project
   458  $ argocd proj role create-token test-project test-role
   459  Create token succeeded for proj:test-project:test-role.
   460    ID: c312450e-12e1-4e0d-9f65-fac9cb027b32
   461    Issued At: 2023-10-08T13:58:57+01:00
   462    Expires At: Never
   463    Token: xxx
   464  
   465  # Get test-role id to input into the delete-token command below
   466  $ argocd proj role get test-project test-role
   467  Role Name:     test-role
   468  Description:
   469  Policies:
   470  p, proj:test-project:test-role, projects, get, test-project, allow
   471  JWT Tokens:
   472  ID          ISSUED-AT                                  EXPIRES-AT
   473  1696769937  2023-10-08T13:58:57+01:00 (6 minutes ago)  <none>
   474  
   475  $ argocd proj role delete-token test-project test-role 1696769937
   476  `,
   477  		Aliases: []string{"token-delete", "remove-token"},
   478  		Run: func(c *cobra.Command, args []string) {
   479  			ctx := c.Context()
   480  
   481  			if len(args) != 3 {
   482  				c.HelpFunc()(c, args)
   483  				os.Exit(1)
   484  			}
   485  			projName := args[0]
   486  			roleName := args[1]
   487  			tokenId := args[2]
   488  			issuedAt, err := strconv.ParseInt(tokenId, 10, 64)
   489  			errors.CheckError(err)
   490  
   491  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
   492  
   493  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   494  			defer utilio.Close(conn)
   495  
   496  			canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to delete '%s' project token? [y/n]", tokenId))
   497  			if canDelete {
   498  				_, err = projIf.DeleteToken(ctx, &projectpkg.ProjectTokenDeleteRequest{Project: projName, Role: roleName, Iat: issuedAt})
   499  				errors.CheckError(err)
   500  			} else {
   501  				fmt.Printf("The command to delete project token '%s' was cancelled.\n", tokenId)
   502  			}
   503  		},
   504  	}
   505  	return command
   506  }
   507  
   508  // Print list of project role names
   509  func printProjectRoleListName(roles []v1alpha1.ProjectRole) {
   510  	for _, role := range roles {
   511  		fmt.Println(role.Name)
   512  	}
   513  }
   514  
   515  // Print table of project roles
   516  func printProjectRoleListTable(roles []v1alpha1.ProjectRole) {
   517  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   518  	fmt.Fprintf(w, "ROLE-NAME\tDESCRIPTION\n")
   519  	for _, role := range roles {
   520  		fmt.Fprintf(w, "%s\t%s\n", role.Name, role.Description)
   521  	}
   522  	_ = w.Flush()
   523  }
   524  
   525  // NewProjectRoleListCommand returns a new instance of an `argocd proj roles list` command
   526  func NewProjectRoleListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   527  	var output string
   528  	command := &cobra.Command{
   529  		Use:   "list PROJECT",
   530  		Short: "List all the roles in a project",
   531  		Example: templates.Examples(`
   532    # This command will list all the roles in argocd-project in a default table format.
   533    argocd proj role list PROJECT
   534  
   535    # List the roles in the project in formats like json, yaml, wide, or name.
   536    argocd proj role list PROJECT --output json
   537  
   538    		`),
   539  
   540  		Run: func(c *cobra.Command, args []string) {
   541  			ctx := c.Context()
   542  
   543  			if len(args) != 1 {
   544  				c.HelpFunc()(c, args)
   545  				os.Exit(1)
   546  			}
   547  			projName := args[0]
   548  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   549  			defer utilio.Close(conn)
   550  
   551  			project, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   552  			errors.CheckError(err)
   553  			switch output {
   554  			case "json", "yaml":
   555  				err := PrintResourceList(project.Spec.Roles, output, false)
   556  				errors.CheckError(err)
   557  			case "name":
   558  				printProjectRoleListName(project.Spec.Roles)
   559  			case "wide", "":
   560  				printProjectRoleListTable(project.Spec.Roles)
   561  			default:
   562  				errors.CheckError(fmt.Errorf("unknown output format: %s", output))
   563  			}
   564  		},
   565  	}
   566  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|name")
   567  	return command
   568  }
   569  
   570  // NewProjectRoleGetCommand returns a new instance of an `argocd proj roles get` command
   571  func NewProjectRoleGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   572  	command := &cobra.Command{
   573  		Use:   "get PROJECT ROLE-NAME",
   574  		Short: "Get the details of a specific role",
   575  		Example: `$ argocd proj role get test-project test-role
   576  Role Name:     test-role
   577  Description:
   578  Policies:
   579  p, proj:test-project:test-role, projects, get, test-project, allow
   580  JWT Tokens:
   581  ID          ISSUED-AT                                  EXPIRES-AT
   582  1696774900  2023-10-08T15:21:40+01:00 (4 minutes ago)  <none>
   583  1696759698  2023-10-08T11:08:18+01:00 (4 hours ago)    <none>
   584  `,
   585  		Run: func(c *cobra.Command, args []string) {
   586  			ctx := c.Context()
   587  
   588  			if len(args) != 2 {
   589  				c.HelpFunc()(c, args)
   590  				os.Exit(1)
   591  			}
   592  			projName := args[0]
   593  			roleName := args[1]
   594  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   595  			defer utilio.Close(conn)
   596  
   597  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   598  			errors.CheckError(err)
   599  
   600  			role, _, err := proj.GetRoleByName(roleName)
   601  			errors.CheckError(err)
   602  
   603  			printRoleFmtStr := "%-15s%s\n"
   604  			fmt.Printf(printRoleFmtStr, "Role Name:", roleName)
   605  			fmt.Printf(printRoleFmtStr, "Description:", role.Description)
   606  			fmt.Printf("Policies:\n")
   607  			fmt.Printf("%s\n", proj.ProjectPoliciesString())
   608  			fmt.Printf("Groups:\n")
   609  			// if the group exists in the role
   610  			// range over each group and print it
   611  			if v1alpha1.RoleGroupExists(role) {
   612  				for _, group := range role.Groups {
   613  					fmt.Printf("  - %s\n", group)
   614  				}
   615  			} else {
   616  				fmt.Println("<none>")
   617  			}
   618  			fmt.Printf("JWT Tokens:\n")
   619  			w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   620  			fmt.Fprintf(w, "ID\tISSUED-AT\tEXPIRES-AT\n")
   621  			for _, token := range proj.Status.JWTTokensByRole[roleName].Items {
   622  				expiresAt := "<none>"
   623  				if token.ExpiresAt > 0 {
   624  					expiresAt = humanizeTimestamp(token.ExpiresAt)
   625  				}
   626  				fmt.Fprintf(w, "%d\t%s\t%s\n", token.IssuedAt, humanizeTimestamp(token.IssuedAt), expiresAt)
   627  			}
   628  			_ = w.Flush()
   629  		},
   630  	}
   631  	return command
   632  }
   633  
   634  // NewProjectRoleAddGroupCommand returns a new instance of an `argocd proj role add-group` command
   635  func NewProjectRoleAddGroupCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   636  	command := &cobra.Command{
   637  		Use:   "add-group PROJECT ROLE-NAME GROUP-CLAIM",
   638  		Short: "Add a group claim to a project role",
   639  		Run: func(c *cobra.Command, args []string) {
   640  			ctx := c.Context()
   641  
   642  			if len(args) != 3 {
   643  				c.HelpFunc()(c, args)
   644  				os.Exit(1)
   645  			}
   646  			projName, roleName, groupName := args[0], args[1], args[2]
   647  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   648  			defer utilio.Close(conn)
   649  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   650  			errors.CheckError(err)
   651  			updated, err := proj.AddGroupToRole(roleName, groupName)
   652  			errors.CheckError(err)
   653  			if !updated {
   654  				fmt.Printf("Group '%s' already present in role '%s'\n", groupName, roleName)
   655  				return
   656  			}
   657  			_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   658  			errors.CheckError(err)
   659  			fmt.Printf("Group '%s' added to role '%s'\n", groupName, roleName)
   660  		},
   661  	}
   662  	return command
   663  }
   664  
   665  // NewProjectRoleRemoveGroupCommand returns a new instance of an `argocd proj role remove-group` command
   666  func NewProjectRoleRemoveGroupCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   667  	command := &cobra.Command{
   668  		Use:   "remove-group PROJECT ROLE-NAME GROUP-CLAIM",
   669  		Short: "Remove a group claim from a role within a project",
   670  		Run: func(c *cobra.Command, args []string) {
   671  			ctx := c.Context()
   672  
   673  			if len(args) != 3 {
   674  				c.HelpFunc()(c, args)
   675  				os.Exit(1)
   676  			}
   677  			projName, roleName, groupName := args[0], args[1], args[2]
   678  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   679  			defer utilio.Close(conn)
   680  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   681  			errors.CheckError(err)
   682  			updated, err := proj.RemoveGroupFromRole(roleName, groupName)
   683  			errors.CheckError(err)
   684  			if !updated {
   685  				fmt.Printf("Group '%s' not present in role '%s'\n", groupName, roleName)
   686  				return
   687  			}
   688  
   689  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
   690  
   691  			canDelete := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to remove '%s' group from role '%s'? [y/n]", groupName, roleName))
   692  
   693  			if canDelete {
   694  				_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   695  				errors.CheckError(err)
   696  				fmt.Printf("Group '%s' removed from role '%s'\n", groupName, roleName)
   697  			} else {
   698  				fmt.Printf("The command to remove group '%s' from role '%s' was cancelled.\n", groupName, roleName)
   699  			}
   700  		},
   701  	}
   702  	return command
   703  }