Skip to content

Commit 6b6e6e7

Browse files
authored
Merge pull request #1149 from planetscale/st-creation
Support creating service tokens with a name
2 parents c0549a7 + 3416524 commit 6b6e6e7

File tree

6 files changed

+128
-15
lines changed

6 files changed

+128
-15
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ require (
2525
github.com/mattn/go-shellwords v1.0.12
2626
github.com/mitchellh/go-homedir v1.1.0
2727
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
28-
github.com/planetscale/planetscale-go v0.143.0
28+
github.com/planetscale/planetscale-go v0.145.0
2929
github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4
3030
github.com/planetscale/psdbproxy v0.0.0-20250728082226-3f4ea3a74ec7
3131
github.com/spf13/cobra v1.10.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL
175175
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
176176
github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e h1:MZ8D+Z3m2vvqGZLvoQfpaGg/j1fNDr4j03s3PRz4rVY=
177177
github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e/go.mod h1:hwAsSPQdvPa3WcfKfzTXxtEq/HlqwLjQasfO6QbGo4Q=
178-
github.com/planetscale/planetscale-go v0.143.0 h1:3LeJXrPYIkXdxUcGIK5rhitA44D6HMp9ZxmVTy7ozO0=
179-
github.com/planetscale/planetscale-go v0.143.0/go.mod h1:PheYDHAwF14wfCBak1M0J64AdPW8NUeyvgPgWqe7zpI=
178+
github.com/planetscale/planetscale-go v0.145.0 h1:jdmAzU5sfdBZxVGMQXkT+BBxvOcND7cakCIQc0vdeVg=
179+
github.com/planetscale/planetscale-go v0.145.0/go.mod h1:PheYDHAwF14wfCBak1M0J64AdPW8NUeyvgPgWqe7zpI=
180180
github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4 h1:Xv5pj20Rhfty1Tv0OVcidg4ez4PvGrpKvb6rvUwQgDs=
181181
github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4/go.mod h1:M52h5IWxAcbdQ1hSZrLAGQC4ZXslxEsK/Wh9nu3wdWs=
182182
github.com/planetscale/psdbproxy v0.0.0-20250728082226-3f4ea3a74ec7 h1:aRd6vdE1fyuSI4RVj7oCr8lFmgqXvpnPUmN85VbZCp8=

internal/cmd/token/create.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
)
1111

1212
func CreateCmd(ch *cmdutil.Helper) *cobra.Command {
13+
var name string
14+
1315
cmd := &cobra.Command{
1416
Use: "create",
1517
Short: "create a service token for the organization",
@@ -22,6 +24,7 @@ func CreateCmd(ch *cmdutil.Helper) *cobra.Command {
2224

2325
req := &planetscale.CreateServiceTokenRequest{
2426
Organization: ch.Config.Organization,
27+
Name: stringPtrOrNil(name),
2528
}
2629

2730
end := ch.Printer.PrintProgress(fmt.Sprintf("Creating service token in org %s", printer.BoldBlue(ch.Config.Organization)))
@@ -39,9 +42,18 @@ func CreateCmd(ch *cmdutil.Helper) *cobra.Command {
3942

4043
end()
4144

42-
return ch.Printer.PrintResource(toServiceToken(token))
45+
return ch.Printer.PrintResource(toServiceTokenWithSecret(token))
4346
},
4447
}
4548

49+
cmd.Flags().StringVar(&name, "name", "", "optional name for the service token")
50+
4651
return cmd
4752
}
53+
54+
func stringPtrOrNil(s string) *string {
55+
if s == "" {
56+
return nil
57+
}
58+
return &s
59+
}

internal/cmd/token/create_test.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"testing"
7+
"time"
78

89
"github.com/planetscale/cli/internal/cmdutil"
910
"github.com/planetscale/cli/internal/config"
@@ -24,12 +25,14 @@ func TestServiceToken_CreateCmd(t *testing.T) {
2425

2526
org := "planetscale"
2627
id := "123456"
28+
createdAt := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)
2729

28-
orig := &ps.ServiceToken{ID: id}
30+
orig := &ps.ServiceToken{ID: id, CreatedAt: createdAt}
2931

3032
svc := &mock.ServiceTokenService{
3133
CreateFn: func(ctx context.Context, req *ps.CreateServiceTokenRequest) (*ps.ServiceToken, error) {
3234
c.Assert(req.Organization, qt.Equals, org)
35+
c.Assert(req.Name, qt.IsNil)
3336
return orig, nil
3437
},
3538
}
@@ -52,6 +55,53 @@ func TestServiceToken_CreateCmd(t *testing.T) {
5255
c.Assert(err, qt.IsNil)
5356
c.Assert(svc.CreateFnInvoked, qt.IsTrue)
5457

55-
res := &ServiceToken{orig: orig}
58+
res := &ServiceTokenWithSecret{orig: orig}
59+
c.Assert(buf.String(), qt.JSONEquals, res)
60+
}
61+
62+
func TestServiceToken_CreateCmdWithName(t *testing.T) {
63+
c := qt.New(t)
64+
65+
var buf bytes.Buffer
66+
format := printer.JSON
67+
p := printer.NewPrinter(&format)
68+
p.SetResourceOutput(&buf)
69+
70+
org := "planetscale"
71+
id := "123456"
72+
name := "my-token"
73+
createdAt := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)
74+
75+
orig := &ps.ServiceToken{ID: id, Name: &name, CreatedAt: createdAt}
76+
77+
svc := &mock.ServiceTokenService{
78+
CreateFn: func(ctx context.Context, req *ps.CreateServiceTokenRequest) (*ps.ServiceToken, error) {
79+
c.Assert(req.Organization, qt.Equals, org)
80+
c.Assert(req.Name, qt.IsNotNil)
81+
c.Assert(*req.Name, qt.Equals, name)
82+
return orig, nil
83+
},
84+
}
85+
86+
ch := &cmdutil.Helper{
87+
Printer: p,
88+
Config: &config.Config{
89+
Organization: org,
90+
},
91+
Client: func() (*ps.Client, error) {
92+
return &ps.Client{
93+
ServiceTokens: svc,
94+
}, nil
95+
},
96+
}
97+
98+
cmd := CreateCmd(ch)
99+
cmd.SetArgs([]string{"--name", name})
100+
err := cmd.Execute()
101+
102+
c.Assert(err, qt.IsNil)
103+
c.Assert(svc.CreateFnInvoked, qt.IsTrue)
104+
105+
res := &ServiceTokenWithSecret{orig: orig}
56106
c.Assert(buf.String(), qt.JSONEquals, res)
57107
}

internal/cmd/token/list_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"testing"
7+
"time"
78

89
qt "github.com/frankban/quicktest"
910
"github.com/planetscale/cli/internal/cmdutil"
@@ -22,10 +23,14 @@ func TestServiceToken_ListCmd(t *testing.T) {
2223
p.SetResourceOutput(&buf)
2324

2425
org := "planetscale"
26+
name1 := "token-one"
27+
createdAt1 := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)
28+
lastUsedAt1 := time.Date(2025, 1, 20, 14, 45, 0, 0, time.UTC)
29+
createdAt2 := time.Date(2025, 1, 16, 11, 0, 0, 0, time.UTC)
2530

2631
orig := []*ps.ServiceToken{
27-
{ID: "1"},
28-
{ID: "2"},
32+
{ID: "1", Name: &name1, CreatedAt: createdAt1, LastUsedAt: &lastUsedAt1},
33+
{ID: "2", CreatedAt: createdAt2},
2934
}
3035

3136
svc := &mock.ServiceTokenService{

internal/cmd/token/token.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@ func TokenCmd(ch *cmdutil.Helper) *cobra.Command {
4545
return cmd
4646
}
4747

48-
// ServiceToken returns a table and json serializable schema snapshot.
48+
// ServiceToken returns a table and json serializable service token for listing.
4949
type ServiceToken struct {
50-
ID string `header:"id" json:"id"`
51-
Token string `header:"token" json:"token"`
50+
ID string `header:"id" json:"id"`
51+
Name string `header:"name" json:"name"`
52+
LastUsedAt int64 `header:"last_used_at,timestamp(ms|utc|human)" json:"last_used_at"`
53+
CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)" json:"created_at"`
5254

5355
orig *ps.ServiceToken
5456
}
@@ -58,12 +60,56 @@ func (s *ServiceToken) MarshalJSON() ([]byte, error) {
5860
}
5961

6062
// toServiceToken returns a struct that prints out the various fields
61-
// of a schema snapshot model.
63+
// of a service token model.
6264
func toServiceToken(st *ps.ServiceToken) *ServiceToken {
65+
var name string
66+
if st.Name != nil {
67+
name = *st.Name
68+
}
69+
70+
// Avoid using GetMillisecondsIfExists as the table printer
71+
// will not order the columns correctly if lastUsedAt is *int64
72+
var lastUsedAt int64
73+
if st.LastUsedAt != nil {
74+
lastUsedAt = printer.GetMilliseconds(*st.LastUsedAt)
75+
}
76+
6377
return &ServiceToken{
64-
ID: st.ID,
65-
Token: st.Token,
66-
orig: st,
78+
ID: st.ID,
79+
Name: name,
80+
LastUsedAt: lastUsedAt,
81+
CreatedAt: printer.GetMilliseconds(st.CreatedAt),
82+
orig: st,
83+
}
84+
}
85+
86+
// ServiceTokenWithSecret is used for the create response where the token is returned.
87+
type ServiceTokenWithSecret struct {
88+
ID string `header:"id" json:"id"`
89+
Name string `header:"name" json:"name"`
90+
Token string `header:"token" json:"token"`
91+
CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)" json:"created_at"`
92+
93+
orig *ps.ServiceToken
94+
}
95+
96+
func (s *ServiceTokenWithSecret) MarshalJSON() ([]byte, error) {
97+
return json.MarshalIndent(s.orig, "", " ")
98+
}
99+
100+
// toServiceTokenWithSecret returns a struct that includes the token secret
101+
func toServiceTokenWithSecret(st *ps.ServiceToken) *ServiceTokenWithSecret {
102+
var name string
103+
if st.Name != nil {
104+
name = *st.Name
105+
}
106+
107+
return &ServiceTokenWithSecret{
108+
ID: st.ID,
109+
Name: name,
110+
Token: st.Token,
111+
CreatedAt: printer.GetMilliseconds(st.CreatedAt),
112+
orig: st,
67113
}
68114
}
69115

0 commit comments

Comments
 (0)