diff --git a/go.mod b/go.mod index 401f7638..5045e650 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/mattn/go-shellwords v1.0.12 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c - github.com/planetscale/planetscale-go v0.147.0 + github.com/planetscale/planetscale-go v0.148.0 github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4 github.com/planetscale/psdbproxy v0.0.0-20250728082226-3f4ea3a74ec7 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index b187bf2b..e718af44 100644 --- a/go.sum +++ b/go.sum @@ -182,8 +182,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e h1:MZ8D+Z3m2vvqGZLvoQfpaGg/j1fNDr4j03s3PRz4rVY= github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e/go.mod h1:hwAsSPQdvPa3WcfKfzTXxtEq/HlqwLjQasfO6QbGo4Q= -github.com/planetscale/planetscale-go v0.147.0 h1:dof3HNIlEhJPN+gAM7BJlUCiuhVsf8mX2wlGtjVbp6U= -github.com/planetscale/planetscale-go v0.147.0/go.mod h1:PheYDHAwF14wfCBak1M0J64AdPW8NUeyvgPgWqe7zpI= +github.com/planetscale/planetscale-go v0.148.0 h1:9tMRq4btsuOe5s/QCx6DNKDwKnGVEZYu++RX0rOaC6g= +github.com/planetscale/planetscale-go v0.148.0/go.mod h1:PheYDHAwF14wfCBak1M0J64AdPW8NUeyvgPgWqe7zpI= github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4 h1:Xv5pj20Rhfty1Tv0OVcidg4ez4PvGrpKvb6rvUwQgDs= github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4/go.mod h1:M52h5IWxAcbdQ1hSZrLAGQC4ZXslxEsK/Wh9nu3wdWs= github.com/planetscale/psdbproxy v0.0.0-20250728082226-3f4ea3a74ec7 h1:aRd6vdE1fyuSI4RVj7oCr8lFmgqXvpnPUmN85VbZCp8= diff --git a/internal/cmd/token/create.go b/internal/cmd/token/create.go index 32f4c9a8..367c4506 100644 --- a/internal/cmd/token/create.go +++ b/internal/cmd/token/create.go @@ -11,6 +11,7 @@ import ( func CreateCmd(ch *cmdutil.Helper) *cobra.Command { var name string + var ttl int cmd := &cobra.Command{ Use: "create", @@ -25,6 +26,7 @@ func CreateCmd(ch *cmdutil.Helper) *cobra.Command { req := &planetscale.CreateServiceTokenRequest{ Organization: ch.Config.Organization, Name: stringPtrOrNil(name), + TTL: intPtrOrNil(ttl), } end := ch.Printer.PrintProgress(fmt.Sprintf("Creating service token in org %s", printer.BoldBlue(ch.Config.Organization))) @@ -47,6 +49,7 @@ func CreateCmd(ch *cmdutil.Helper) *cobra.Command { } cmd.Flags().StringVar(&name, "name", "", "optional name for the service token") + cmd.Flags().IntVar(&ttl, "ttl", 0, "Time to live (in seconds) for the service token. The token will be invalid when TTL has passed") return cmd } @@ -57,3 +60,10 @@ func stringPtrOrNil(s string) *string { } return &s } + +func intPtrOrNil(i int) *int { + if i == 0 { + return nil + } + return &i +} diff --git a/internal/cmd/token/create_test.go b/internal/cmd/token/create_test.go index 492a8a00..762bef87 100644 --- a/internal/cmd/token/create_test.go +++ b/internal/cmd/token/create_test.go @@ -105,3 +105,54 @@ func TestServiceToken_CreateCmdWithName(t *testing.T) { res := &ServiceTokenWithSecret{orig: orig} c.Assert(buf.String(), qt.JSONEquals, res) } + +func TestServiceToken_CreateCmdWithTTL(t *testing.T) { + c := qt.New(t) + + var buf bytes.Buffer + format := printer.JSON + p := printer.NewPrinter(&format) + p.SetResourceOutput(&buf) + + org := "planetscale" + id := "123456" + name := "my-token" + ttl := 3600 + createdAt := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC) + expiresAt := time.Date(2025, 1, 15, 11, 30, 0, 0, time.UTC) + + orig := &ps.ServiceToken{ID: id, Name: &name, CreatedAt: createdAt, ExpiresAt: &expiresAt} + + svc := &mock.ServiceTokenService{ + CreateFn: func(ctx context.Context, req *ps.CreateServiceTokenRequest) (*ps.ServiceToken, error) { + c.Assert(req.Organization, qt.Equals, org) + c.Assert(req.Name, qt.IsNotNil) + c.Assert(*req.Name, qt.Equals, name) + c.Assert(req.TTL, qt.IsNotNil) + c.Assert(*req.TTL, qt.Equals, ttl) + return orig, nil + }, + } + + ch := &cmdutil.Helper{ + Printer: p, + Config: &config.Config{ + Organization: org, + }, + Client: func() (*ps.Client, error) { + return &ps.Client{ + ServiceTokens: svc, + }, nil + }, + } + + cmd := CreateCmd(ch) + cmd.SetArgs([]string{"--name", name, "--ttl", "3600"}) + err := cmd.Execute() + + c.Assert(err, qt.IsNil) + c.Assert(svc.CreateFnInvoked, qt.IsTrue) + + res := &ServiceTokenWithSecret{orig: orig} + c.Assert(buf.String(), qt.JSONEquals, res) +} diff --git a/internal/cmd/token/list_test.go b/internal/cmd/token/list_test.go index ae9698c1..f9c9efa0 100644 --- a/internal/cmd/token/list_test.go +++ b/internal/cmd/token/list_test.go @@ -64,3 +64,56 @@ func TestServiceToken_ListCmd(t *testing.T) { } c.Assert(buf.String(), qt.JSONEquals, res) } + +func TestServiceToken_ListCmdWithExpiresAt(t *testing.T) { + c := qt.New(t) + + var buf bytes.Buffer + format := printer.JSON + p := printer.NewPrinter(&format) + p.SetResourceOutput(&buf) + + org := "planetscale" + name1 := "token-one" + createdAt1 := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC) + lastUsedAt1 := time.Date(2025, 1, 20, 14, 45, 0, 0, time.UTC) + expiresAt1 := time.Date(2025, 2, 15, 10, 30, 0, 0, time.UTC) + createdAt2 := time.Date(2025, 1, 16, 11, 0, 0, 0, time.UTC) + expiresAt2 := time.Date(2025, 2, 16, 11, 0, 0, 0, time.UTC) + + orig := []*ps.ServiceToken{ + {ID: "1", Name: &name1, CreatedAt: createdAt1, LastUsedAt: &lastUsedAt1, ExpiresAt: &expiresAt1}, + {ID: "2", CreatedAt: createdAt2, ExpiresAt: &expiresAt2}, + } + + svc := &mock.ServiceTokenService{ + ListFn: func(ctx context.Context, req *ps.ListServiceTokensRequest) ([]*ps.ServiceToken, error) { + c.Assert(req.Organization, qt.Equals, org) + return orig, nil + }, + } + + ch := &cmdutil.Helper{ + Printer: p, + Config: &config.Config{ + Organization: org, + }, + Client: func() (*ps.Client, error) { + return &ps.Client{ + ServiceTokens: svc, + }, nil + }, + } + + cmd := ListCmd(ch) + err := cmd.Execute() + + c.Assert(err, qt.IsNil) + c.Assert(svc.ListFnInvoked, qt.IsTrue) + + res := []*ServiceToken{ + {orig: orig[0]}, + {orig: orig[1]}, + } + c.Assert(buf.String(), qt.JSONEquals, res) +} diff --git a/internal/cmd/token/token.go b/internal/cmd/token/token.go index 59d14b84..7d30540e 100644 --- a/internal/cmd/token/token.go +++ b/internal/cmd/token/token.go @@ -50,6 +50,7 @@ type ServiceToken struct { ID string `header:"id" json:"id"` Name string `header:"name" json:"name"` LastUsedAt int64 `header:"last_used_at,timestamp(ms|utc|human)" json:"last_used_at"` + ExpiresAt int64 `header:"expires_at,timestamp(ms|utc|human)" json:"expires_at"` CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)" json:"created_at"` orig *ps.ServiceToken @@ -74,10 +75,16 @@ func toServiceToken(st *ps.ServiceToken) *ServiceToken { lastUsedAt = printer.GetMilliseconds(*st.LastUsedAt) } + var expiresAt int64 + if st.ExpiresAt != nil { + expiresAt = printer.GetMilliseconds(*st.ExpiresAt) + } + return &ServiceToken{ ID: st.ID, Name: name, LastUsedAt: lastUsedAt, + ExpiresAt: expiresAt, CreatedAt: printer.GetMilliseconds(st.CreatedAt), orig: st, } @@ -88,6 +95,7 @@ type ServiceTokenWithSecret struct { ID string `header:"id" json:"id"` Name string `header:"name" json:"name"` Token string `header:"token" json:"token"` + ExpiresAt int64 `header:"expires_at,timestamp(ms|utc|human)" json:"expires_at"` CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)" json:"created_at"` orig *ps.ServiceToken @@ -104,10 +112,16 @@ func toServiceTokenWithSecret(st *ps.ServiceToken) *ServiceTokenWithSecret { name = *st.Name } + var expiresAt int64 + if st.ExpiresAt != nil { + expiresAt = printer.GetMilliseconds(*st.ExpiresAt) + } + return &ServiceTokenWithSecret{ ID: st.ID, Name: name, Token: st.Token, + ExpiresAt: expiresAt, CreatedAt: printer.GetMilliseconds(st.CreatedAt), orig: st, }