commands/token/create/create.go (228 lines of code) (raw):
package create
import (
"encoding/json"
"errors"
"fmt"
"time"
"gitlab.com/gitlab-org/cli/commands/token/expirationdate"
"gitlab.com/gitlab-org/cli/commands/token/filter"
"gitlab.com/gitlab-org/cli/commands/flag"
"gitlab.com/gitlab-org/cli/commands/token/accesslevel"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"github.com/MakeNowJust/heredoc/v2"
"github.com/spf13/cobra"
gitlab "gitlab.com/gitlab-org/api/client-go"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/internal/glrepo"
)
type CreateOptions struct {
HTTPClient func() (*gitlab.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (glrepo.Interface, error)
Name string
Description string
User string
Group string
AccessLevel accesslevel.AccessLevel
Scopes []string
Duration time.Duration
ExpireAt expirationdate.ExpirationDate
OutputFormat string
}
func NewCmdCreate(f *cmdutils.Factory, runE func(opts *CreateOptions) error) *cobra.Command {
opts := &CreateOptions{
IO: f.IO,
}
cmd := &cobra.Command{
Use: "create <name>",
Aliases: []string{"create", "new"},
Args: cobra.RangeArgs(1, 1),
Short: "Creates user, group, or project access tokens.",
Long: heredoc.Doc(`
Creates a new access token for a user, group, or project. Defaults to a
project access token, unless user or group name is specified.
The expiration date of the token is calculated by adding the duration
(default: 30 days) to the current date. You can specify a different duration,
or an explicit end date.
The name of the token must be unique. The token is printed to stdout.
Administrators can create full-featured personal access tokens for themselves and for other users.
Non-administrators can create personal access tokens only for themselves (@me) with the scope 'k8s_proxy'.
`),
Example: heredoc.Doc(`
Create project access token for current project
- glab token create --access-level developer --scope read_repository --scope read_registry my-project-token
Create project access token for a specific project
- glab token create --repo user/my-repo --access-level owner --scope api my-project-token --description "example description"
Create a group access token
- glab token create --group group/sub-group --access-level owner --scope api my-group-token
Create a personal access token for current user
- glab token create --user @me --scope k8s_proxy my-personal-token
(administrator only) Create a personal access token for another user
- glab token create --user johndoe --scope api johns-personal-token
`),
RunE: func(cmd *cobra.Command, args []string) (err error) {
// Supports repo override
opts.HTTPClient = f.HttpClient
opts.BaseRepo = f.BaseRepo
opts.Name = args[0]
if opts.Group, err = flag.GroupOverride(cmd); err != nil {
return
}
if opts.Group != "" && opts.User != "" {
return cmdutils.FlagError{Err: errors.New("'--group' and '--user' are mutually exclusive.")}
}
if cmd.Flags().Changed("expires-at") && cmd.Flags().Changed("duration") {
return cmdutils.FlagError{Err: errors.New("'--expires-at' and '--duration' are mutually exclusive.")}
}
if time.Time(opts.ExpireAt).IsZero() {
opts.ExpireAt = expirationdate.ExpirationDate(time.Now().Add(opts.Duration).Truncate(time.Hour * 24))
}
if opts.Duration.Truncate(24*time.Hour) != opts.Duration {
return cmdutils.FlagError{Err: errors.New("duration must be in days.")}
}
if opts.Duration < 24*time.Hour || opts.Duration > 365*24*time.Hour {
return cmdutils.FlagError{Err: errors.New("duration in days must be between 1 and 365.")}
}
if opts.User != "" && opts.Group != "" {
return cmdutils.FlagError{Err: errors.New("'--user' and '--group' are mutually exclusive.")}
}
if opts.User == "" && !cmd.Flag("access-level").Changed {
return cmdutils.FlagError{Err: errors.New("the required flag '--access-level' is not set.")}
}
if runE != nil {
err = runE(opts)
return
}
err = createTokenRun(opts)
return
},
}
cmdutils.EnableRepoOverride(cmd, f)
cmd.Flags().StringVarP(&opts.Group, "group", "g", "", "Create a group access token. Ignored if a user or repository argument is set.")
cmd.Flags().StringVarP(&opts.User, "user", "U", "", "Create a personal access token. For the current user, use @me.")
cmd.Flags().StringVarP(&opts.Description, "description", "", "description", "Sets the token's description.")
cmd.Flags().DurationVarP(&opts.Duration, "duration", "D", time.Duration(30*24*time.Hour), "Sets the token duration, in hours. Maximum of 8760. Examples: 24h, 168h, 504h.")
cmd.Flags().VarP(&opts.ExpireAt, "expires-at", "E", "Sets the token's expiration date and time, in YYYY-MM-DD format. If not specified, --duration is used.")
cmd.Flags().StringSliceVarP(&opts.Scopes, "scope", "S", []string{"read_repository"}, "Scopes for the token. For a list, see https://docs.gitlab.com/user/profile/personal_access_tokens/#personal-access-token-scopes.")
cmd.Flags().VarP(&opts.AccessLevel, "access-level", "A", "Access level of the token: one of 'guest', 'reporter', 'developer', 'maintainer', 'owner'.")
cmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as 'text' for the token value, 'json' for the actual API token structure.")
return cmd
}
func createTokenRun(opts *CreateOptions) error {
httpClient, err := opts.HTTPClient()
if err != nil {
return err
}
var outputToken any
var outputTokenValue string
expirationDate := gitlab.ISOTime(opts.ExpireAt)
if opts.User != "" {
user, err := api.UserByName(httpClient, opts.User)
if err != nil {
return err
}
options := &gitlab.ListPersonalAccessTokensOptions{
ListOptions: gitlab.ListOptions{PerPage: 100},
UserID: &user.ID,
}
tokens, err := api.ListPersonalAccessTokens(httpClient, options)
if err != nil {
return err
}
tokens = filter.Filter(tokens, func(t *gitlab.PersonalAccessToken) bool {
return t.Active && t.Name == opts.Name
})
if len(tokens) > 0 {
return cmdutils.FlagError{Err: fmt.Errorf("a personal access token with the name %s already exists.", opts.Name)}
}
if opts.User == "@me" {
token, err := api.CreatePersonalAccessTokenForCurrentUser(httpClient, opts.Name, opts.Scopes, time.Time(expirationDate))
if err != nil {
return err
}
outputToken = token
outputTokenValue = token.Token
} else {
createOptions := &gitlab.CreatePersonalAccessTokenOptions{
Name: &opts.Name,
Description: &opts.Description,
ExpiresAt: &expirationDate,
Scopes: &opts.Scopes,
}
token, err := api.CreatePersonalAccessToken(httpClient, user.ID, createOptions)
if err != nil {
return err
}
outputToken = token
outputTokenValue = token.Token
}
} else {
if opts.Group != "" {
listOptions := &gitlab.ListGroupAccessTokensOptions{PerPage: 100}
tokens, err := api.ListGroupAccessTokens(httpClient, opts.Group, listOptions)
if err != nil {
return err
}
tokens = filter.Filter(tokens, func(t *gitlab.GroupAccessToken) bool {
return t.Active && t.Name == opts.Name
})
if len(tokens) > 0 {
return cmdutils.FlagError{Err: fmt.Errorf("a group access token with the name %s already exists.", opts.Name)}
}
options := gitlab.CreateGroupAccessTokenOptions{
Name: &opts.Name,
Description: &opts.Description,
Scopes: &opts.Scopes,
AccessLevel: &opts.AccessLevel.Value,
ExpiresAt: &expirationDate,
}
token, err := api.CreateGroupAccessToken(httpClient, opts.Group, &options)
if err != nil {
return err
}
outputToken = token
outputTokenValue = token.Token
} else {
repo, err := opts.BaseRepo()
if err != nil {
return err
}
listOptions := &gitlab.ListProjectAccessTokensOptions{ListOptions: gitlab.ListOptions{PerPage: 100}}
tokens, err := api.ListProjectAccessTokens(httpClient, repo.FullName(), listOptions)
if err != nil {
return err
}
tokens = filter.Filter(tokens, func(t *gitlab.ProjectAccessToken) bool {
return t.Active && t.Name == opts.Name
})
if len(tokens) > 0 {
return cmdutils.FlagError{Err: fmt.Errorf("a project access token with name %s already exists.", opts.Name)}
}
options := gitlab.CreateProjectAccessTokenOptions{
Name: &opts.Name,
Description: &opts.Description,
Scopes: &opts.Scopes,
AccessLevel: &opts.AccessLevel.Value,
ExpiresAt: &expirationDate,
}
token, err := api.CreateProjectAccessToken(httpClient, repo.FullName(), &options)
if err != nil {
return err
}
outputToken = token
outputTokenValue = token.Token
}
}
if opts.OutputFormat == "json" {
encoder := json.NewEncoder(opts.IO.StdOut)
encoder.SetIndent(" ", " ")
if err := encoder.Encode(outputToken); err != nil {
return err
}
} else {
if _, err := fmt.Fprintf(opts.IO.StdOut, "%s\n", outputTokenValue); err != nil {
return err
}
}
return nil
}