Skip to content

Commit 6ed0c28

Browse files
committed
lab: Add token support
Add support for showing the current token, listing all of a user's tokens, revoking tokens, and creating tokens (creating tokens is limited by the GitLab API to Administrator level only). Signed-off-by: Prarit Bhargava <prarit@redhat.com>
1 parent 6754755 commit 6ed0c28

File tree

7 files changed

+331
-0
lines changed

7 files changed

+331
-0
lines changed

cmd/token.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
var tokenCmd = &cobra.Command{
8+
Use: "token",
9+
Short: `Show, list, create, and revoke personal access tokens`,
10+
Long: ``,
11+
PersistentPreRun: labPersistentPreRun,
12+
Run: func(cmd *cobra.Command, args []string) {
13+
// Show data about current token
14+
if show, _ := cmd.Flags().GetBool("show"); show {
15+
tokenShowCmd.Run(cmd, args)
16+
return
17+
}
18+
// List all tokens
19+
if list, _ := cmd.Flags().GetBool("list"); list {
20+
tokenListCmd.Run(cmd, args)
21+
return
22+
}
23+
// Create a token
24+
if create, _ := cmd.Flags().GetBool("create"); create {
25+
tokenCreateCmd.Run(cmd, args)
26+
return
27+
}
28+
// Revoke a token
29+
if revoke, _ := cmd.Flags().GetBool("revoke"); revoke {
30+
tokenRevokeCmd.Run(cmd, args)
31+
return
32+
}
33+
},
34+
}
35+
36+
func init() {
37+
tokenCmd.Flags().BoolP("show", "s", false, "show details about current token")
38+
tokenCmd.Flags().BoolP("list", "l", false, "list all personal access tokens")
39+
tokenCmd.Flags().BoolP("create", "c", false, "create a personal access tokens")
40+
tokenCmd.Flags().BoolP("revoke", "r", false, "revoke a personal access tokens")
41+
RootCmd.AddCommand(tokenCmd)
42+
}

cmd/token_create.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
"time"
8+
9+
"github.com/MakeNowJust/heredoc/v2"
10+
"github.com/spf13/cobra"
11+
lab "github.com/zaquestion/lab/internal/gitlab"
12+
)
13+
14+
var (
15+
expiresAt time.Time
16+
)
17+
18+
var tokenCreateCmd = &cobra.Command{
19+
Use: "create",
20+
Short: "create a new Personal Access Token",
21+
Args: cobra.MaximumNArgs(1),
22+
Example: heredoc.Doc(`
23+
lab token create`),
24+
PersistentPreRun: labPersistentPreRun,
25+
Run: func(cmd *cobra.Command, args []string) {
26+
// The values of name and scopes must be specified, they are not optional.
27+
name, _ := cmd.Flags().GetString("name")
28+
if name == "" {
29+
log.Fatal("The name of the token must be specified.")
30+
}
31+
32+
scopes, _ := cmd.Flags().GetStringSlice("scopes")
33+
if len(scopes) == 0 {
34+
log.Fatal("Scopes must be specified. See --help for available options.")
35+
}
36+
37+
// expiresat is optional
38+
expiresat, _ := cmd.Flags().GetString("expiresat")
39+
if expiresat != "" {
40+
s := strings.Split(expiresat, "-")
41+
if len(s) != 3 {
42+
log.Fatal("Incorrect date specified, must be YYYY-MM-DD format")
43+
}
44+
45+
year, err := strconv.Atoi(s[0])
46+
if err != nil {
47+
log.Fatal("Invalid year specified")
48+
}
49+
month, err := strconv.Atoi(s[1])
50+
if err != nil {
51+
log.Fatal("Invalid month specified")
52+
}
53+
day, err := strconv.Atoi(s[2])
54+
if err != nil {
55+
log.Fatal("Invalid day specified")
56+
}
57+
58+
loc, _ := time.LoadLocation("UTC")
59+
expiresAt = time.Date(year, time.Month(month), day, 0, 0, 0, 0, loc)
60+
61+
yearNow, monthNow, dayNow := time.Now().UTC().Date()
62+
yearFromNow := time.Date(yearNow, monthNow, dayNow, 0, 0, 0, 0, loc).AddDate(1, 0, 0)
63+
if expiresAt.After(yearFromNow) {
64+
log.Fatalf("Expires date can only be a maximum of one year from now (%s)", yearFromNow.String())
65+
}
66+
} else {
67+
loc, _ := time.LoadLocation("UTC")
68+
yearNow, monthNow, dayNow := time.Now().UTC().Date()
69+
expiresAt = time.Date(yearNow, monthNow, dayNow, 0, 0, 0, 0, loc).AddDate(0, 0, 30)
70+
}
71+
72+
pat, err := lab.CreatePAT(name, expiresAt, scopes)
73+
if err != nil {
74+
log.Fatal(err)
75+
}
76+
fmt.Printf("%s created set to expire on %s", pat, expiresat)
77+
},
78+
}
79+
80+
func init() {
81+
tokenCreateCmd.Flags().StringP("name", "n", "", "name of token")
82+
tokenCreateCmd.Flags().StringSliceP("scopes", "s", []string{}, "Comma separated scopes for this token. (Available scopes are: api, read_api, read_user, read_repository, write_repository, read_registry, write_registry, sudo, admin_mode.")
83+
tokenCreateCmd.Flags().StringP("expiresat", "e", "", "YYYY-MM-DD formatted date the token will expire on (Default: 30 days from now, Maximum: One year from today.)")
84+
tokenCmd.AddCommand(tokenCreateCmd)
85+
}

cmd/token_list.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cmd
2+
3+
import (
4+
"github.com/MakeNowJust/heredoc/v2"
5+
"github.com/spf13/cobra"
6+
lab "github.com/zaquestion/lab/internal/gitlab"
7+
)
8+
9+
var tokenListCmd = &cobra.Command{
10+
Use: "list",
11+
Short: "list details about all Personal Access Tokens",
12+
Args: cobra.MaximumNArgs(1),
13+
Example: heredoc.Doc(`
14+
lab token list`),
15+
PersistentPreRun: labPersistentPreRun,
16+
Run: func(cmd *cobra.Command, args []string) {
17+
tokens, err := lab.GetAllPATs()
18+
if err != nil {
19+
log.Fatal(err)
20+
}
21+
22+
all, _ := cmd.Flags().GetBool("all")
23+
for _, token := range tokens {
24+
if token.Active || all {
25+
dumpToken(token)
26+
}
27+
}
28+
},
29+
}
30+
31+
func init() {
32+
tokenListCmd.Flags().BoolP("all", "a", false, "list all tokens (including inactive)")
33+
tokenCmd.AddCommand(tokenListCmd)
34+
}

cmd/token_revoke.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cmd
2+
3+
import (
4+
"strconv"
5+
6+
"github.com/MakeNowJust/heredoc/v2"
7+
"github.com/spf13/cobra"
8+
9+
lab "github.com/zaquestion/lab/internal/gitlab"
10+
)
11+
12+
var tokenRevokeCmd = &cobra.Command{
13+
Use: "revoke [token_name]",
14+
Short: "revoke a Personal Access Token",
15+
Args: cobra.MaximumNArgs(1),
16+
Example: heredoc.Doc(`
17+
lab token revoke`),
18+
19+
PersistentPreRun: labPersistentPreRun,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
id := 0
22+
if len(args) == 1 {
23+
var err error
24+
id, err = strconv.Atoi(args[0])
25+
if err != nil {
26+
log.Fatal(err)
27+
}
28+
} else {
29+
name, _ := cmd.Flags().GetString("name")
30+
if name == "" {
31+
log.Fatalf("Must specify a valid token ID or name\n")
32+
}
33+
PATs, err := lab.GetAllPATs()
34+
if err != nil {
35+
log.Fatal(err)
36+
}
37+
for _, PAT := range PATs {
38+
if PAT.Name == name {
39+
id = PAT.ID
40+
break
41+
}
42+
}
43+
log.Fatalf("%s is not a valid Token name\n", name)
44+
}
45+
46+
if id == 0 {
47+
log.Fatalf("Must specify a valid token ID or name\n")
48+
}
49+
50+
err := lab.RevokePAT(id)
51+
if err != nil {
52+
log.Fatal(err)
53+
}
54+
55+
PATs, err := lab.GetAllPATs()
56+
for _, PAT := range PATs {
57+
if PAT.ID == id {
58+
dumpToken(PAT)
59+
}
60+
}
61+
},
62+
}
63+
64+
func init() {
65+
tokenRevokeCmd.Flags().StringP("name", "", "", "name of token (can be obtained from 'lab token list'")
66+
tokenCmd.AddCommand(tokenRevokeCmd)
67+
}

cmd/token_show.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cmd
2+
3+
import (
4+
"github.com/MakeNowJust/heredoc/v2"
5+
"github.com/spf13/cobra"
6+
lab "github.com/zaquestion/lab/internal/gitlab"
7+
)
8+
9+
var tokenShowCmd = &cobra.Command{
10+
Use: "show",
11+
Aliases: []string{"details", "current"},
12+
Short: "show details about current Personal Access Token",
13+
Args: cobra.MaximumNArgs(1),
14+
Example: heredoc.Doc(`
15+
lab token show
16+
lab token details
17+
lab token current`),
18+
19+
PersistentPreRun: labPersistentPreRun,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
tokendata, err := lab.GetCurrentPAT()
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
dumpToken(tokendata)
26+
},
27+
}
28+
29+
func init() {
30+
tokenCmd.AddCommand(tokenShowCmd)
31+
}

cmd/util.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os/exec"
99
"strconv"
1010
"strings"
11+
"time"
1112

1213
"github.com/pkg/errors"
1314
"github.com/spf13/cobra"
@@ -718,3 +719,17 @@ func mapLabelsAsLabels(rn string, labelTerms []string) (gitlab.Labels, error) {
718719

719720
return gitlab.Labels(matches), nil
720721
}
722+
723+
// dumpToken dumps information about a specific Personal Access Token
724+
func dumpToken(tokendata *gitlab.PersonalAccessToken) {
725+
fmt.Println("ID: ", tokendata.ID)
726+
fmt.Println("Name: ", tokendata.Name)
727+
fmt.Println("Revoked: ", tokendata.Revoked)
728+
fmt.Println("CreatedAt: ", tokendata.CreatedAt)
729+
fmt.Println("Scopes: ", strings.Join(tokendata.Scopes, ","))
730+
fmt.Println("UserID: ", tokendata.UserID)
731+
fmt.Println("LastUsedAt:", tokendata.LastUsedAt)
732+
fmt.Println("Active: ", tokendata.Active)
733+
fmt.Println("ExpiresAt: ", time.Time(*tokendata.ExpiresAt).String())
734+
fmt.Println("")
735+
}

internal/gitlab/gitlab.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,3 +1857,60 @@ func hasNextPage(resp *gitlab.Response) (int, bool) {
18571857
}
18581858
return resp.NextPage, true
18591859
}
1860+
1861+
func GetCurrentPAT() (*gitlab.PersonalAccessToken, error) {
1862+
tokendata, _, err := lab.PersonalAccessTokens.GetSinglePersonalAccessToken()
1863+
if err != nil {
1864+
return nil, err
1865+
}
1866+
1867+
return tokendata, nil
1868+
}
1869+
1870+
func GetAllPATs() ([]*gitlab.PersonalAccessToken, error) {
1871+
1872+
opt := &gitlab.ListPersonalAccessTokensOptions{}
1873+
1874+
tokens, _, err := lab.PersonalAccessTokens.ListPersonalAccessTokens(opt)
1875+
if err != nil {
1876+
return nil, err
1877+
}
1878+
1879+
return tokens, nil
1880+
}
1881+
1882+
func CreatePAT(name string, ExpiresAt time.Time, scopes []string) (*gitlab.PersonalAccessToken, error) {
1883+
isotime := gitlab.ISOTime(ExpiresAt)
1884+
opt := &gitlab.CreatePersonalAccessTokenOptions{
1885+
Name: &name,
1886+
ExpiresAt: &isotime,
1887+
Scopes: &scopes,
1888+
}
1889+
1890+
userID, err := UserID()
1891+
if err != nil {
1892+
return nil, err
1893+
}
1894+
1895+
token, _, err := lab.Users.CreatePersonalAccessToken(userID, opt)
1896+
if err != nil {
1897+
return nil, err
1898+
}
1899+
return token, nil
1900+
}
1901+
1902+
func RevokePAT(id int) error {
1903+
PATs, err := GetAllPATs()
1904+
if err != nil {
1905+
return err
1906+
}
1907+
1908+
for _, PAT := range PATs {
1909+
if PAT.ID == id {
1910+
_, err := lab.PersonalAccessTokens.RevokePersonalAccessToken(PAT.ID)
1911+
return err
1912+
}
1913+
}
1914+
1915+
return fmt.Errorf("%d is not a valid Token ID\n", id)
1916+
}

0 commit comments

Comments
 (0)