diff --git a/internal/cmd/token/show_test.go b/internal/cmd/token/show_test.go index e7465f1e..838b1d5d 100644 --- a/internal/cmd/token/show_test.go +++ b/internal/cmd/token/show_test.go @@ -27,12 +27,16 @@ func TestServiceToken_ShowCmd(t *testing.T) { name := "deploy-token" createdAt := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC) lastUsedAt := time.Date(2025, 1, 20, 14, 45, 0, 0, time.UTC) + lastUsedIP := "203.0.113.5" + lastUsedOrganization := "Example Networks" token := &ps.ServiceToken{ - ID: id, - Name: &name, - Token: "must-not-be-printed", - CreatedAt: createdAt, - LastUsedAt: &lastUsedAt, + ID: id, + Name: &name, + Token: "must-not-be-printed", + CreatedAt: createdAt, + LastUsedAt: &lastUsedAt, + LastUsedIP: &lastUsedIP, + LastUsedAsnOrganization: &lastUsedOrganization, } svc := &mock.ServiceTokenService{ @@ -52,11 +56,13 @@ func TestServiceToken_ShowCmd(t *testing.T) { c.Assert(err, qt.IsNil) c.Assert(svc.GetFnInvoked, qt.IsTrue) c.Assert(buf.String(), qt.JSONEquals, map[string]any{ - "id": id, - "name": name, - "created_at": "2025-01-15T10:30:00Z", - "last_used_at": "2025-01-20T14:45:00Z", - "expires_at": nil, + "id": id, + "name": name, + "created_at": "2025-01-15T10:30:00Z", + "last_used_at": "2025-01-20T14:45:00Z", + "last_used_ip": "203.0.113.5", + "last_used_asn_organization": "Example Networks", + "expires_at": nil, }) c.Assert(buf.String(), qt.Not(qt.Contains), token.Token) } @@ -73,11 +79,15 @@ func TestServiceToken_ShowCmdHuman(t *testing.T) { name := "deploy-token" svc := &mock.ServiceTokenService{ GetFn: func(ctx context.Context, req *ps.GetServiceTokenRequest) (*ps.ServiceToken, error) { + lastUsedIP := "203.0.113.5" + lastUsedOrganization := "Example Networks" return &ps.ServiceToken{ - ID: "token-id", - Name: &name, - Token: "must-not-be-printed", - CreatedAt: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC), + ID: "token-id", + Name: &name, + Token: "must-not-be-printed", + CreatedAt: time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC), + LastUsedIP: &lastUsedIP, + LastUsedAsnOrganization: &lastUsedOrganization, }, nil }, } @@ -90,6 +100,8 @@ func TestServiceToken_ShowCmdHuman(t *testing.T) { c.Assert(err, qt.IsNil) c.Assert(buf.String(), qt.Contains, "token-id") c.Assert(buf.String(), qt.Contains, "deploy-token") + c.Assert(buf.String(), qt.Contains, "203.0.113.5") + c.Assert(buf.String(), qt.Contains, "Example Networks") c.Assert(buf.String(), qt.Not(qt.Contains), "must-not-be-printed") } diff --git a/internal/cmd/token/token.go b/internal/cmd/token/token.go index 2f7d5515..3d2246ce 100644 --- a/internal/cmd/token/token.go +++ b/internal/cmd/token/token.go @@ -49,11 +49,13 @@ func TokenCmd(ch *cmdutil.Helper) *cobra.Command { // ServiceToken returns a table and json serializable service token for listing. 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"` + 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"` + LastUsedIP string `header:"last_used_ip" json:"last_used_ip"` + LastUsedOrganization string `header:"last_used_organization" json:"last_used_organization"` + 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 } @@ -83,38 +85,53 @@ func toServiceToken(st *ps.ServiceToken) *ServiceToken { } return &ServiceToken{ - ID: st.ID, - Name: name, - LastUsedAt: lastUsedAt, - ExpiresAt: expiresAt, - CreatedAt: printer.GetMilliseconds(st.CreatedAt), - orig: st, + ID: st.ID, + Name: name, + LastUsedAt: lastUsedAt, + LastUsedIP: derefString(st.LastUsedIP), + LastUsedOrganization: derefString(st.LastUsedAsnOrganization), + ExpiresAt: expiresAt, + CreatedAt: printer.GetMilliseconds(st.CreatedAt), + orig: st, } } +func derefString(value *string) string { + if value == nil { + return "" + } + return *value +} + type ServiceTokenDetails struct { - ID string `header:"id"` - Name string `header:"name"` - LastUsedAt int64 `header:"last_used_at,timestamp(ms|utc|human)"` - ExpiresAt int64 `header:"expires_at,timestamp(ms|utc|human)"` - CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)"` + ID string `header:"id"` + Name string `header:"name"` + LastUsedAt int64 `header:"last_used_at,timestamp(ms|utc|human)"` + LastUsedIP string `header:"last_used_ip"` + LastUsedOrganization string `header:"last_used_organization"` + ExpiresAt int64 `header:"expires_at,timestamp(ms|utc|human)"` + CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)"` orig *ps.ServiceToken } func (s *ServiceTokenDetails) MarshalJSON() ([]byte, error) { return json.MarshalIndent(struct { - ID string `json:"id"` - Name *string `json:"name"` - CreatedAt time.Time `json:"created_at"` - LastUsedAt *time.Time `json:"last_used_at"` - ExpiresAt *time.Time `json:"expires_at"` + ID string `json:"id"` + Name *string `json:"name"` + CreatedAt time.Time `json:"created_at"` + LastUsedAt *time.Time `json:"last_used_at"` + LastUsedIP *string `json:"last_used_ip"` + LastUsedAsnOrganization *string `json:"last_used_asn_organization"` + ExpiresAt *time.Time `json:"expires_at"` }{ - ID: s.orig.ID, - Name: s.orig.Name, - CreatedAt: s.orig.CreatedAt, - LastUsedAt: s.orig.LastUsedAt, - ExpiresAt: s.orig.ExpiresAt, + ID: s.orig.ID, + Name: s.orig.Name, + CreatedAt: s.orig.CreatedAt, + LastUsedAt: s.orig.LastUsedAt, + LastUsedIP: s.orig.LastUsedIP, + LastUsedAsnOrganization: s.orig.LastUsedAsnOrganization, + ExpiresAt: s.orig.ExpiresAt, }, "", " ") } @@ -122,12 +139,14 @@ func toServiceTokenDetails(st *ps.ServiceToken) *ServiceTokenDetails { token := toServiceToken(st) return &ServiceTokenDetails{ - ID: token.ID, - Name: token.Name, - LastUsedAt: token.LastUsedAt, - ExpiresAt: token.ExpiresAt, - CreatedAt: token.CreatedAt, - orig: st, + ID: token.ID, + Name: token.Name, + LastUsedAt: token.LastUsedAt, + LastUsedIP: token.LastUsedIP, + LastUsedOrganization: token.LastUsedOrganization, + ExpiresAt: token.ExpiresAt, + CreatedAt: token.CreatedAt, + orig: st, } } diff --git a/internal/planetscale/service_tokens.go b/internal/planetscale/service_tokens.go index aa7ca1ac..f31d7a1d 100644 --- a/internal/planetscale/service_tokens.go +++ b/internal/planetscale/service_tokens.go @@ -172,13 +172,15 @@ type DeleteServiceTokenAccessRequest struct { } type ServiceToken struct { - ID string `json:"id"` - Type string `json:"type"` - Token string `json:"token"` - Name *string `json:"name"` - CreatedAt time.Time `json:"created_at"` - LastUsedAt *time.Time `json:"last_used_at"` - ExpiresAt *time.Time `json:"expires_at"` + ID string `json:"id"` + Type string `json:"type"` + Token string `json:"token"` + Name *string `json:"name"` + CreatedAt time.Time `json:"created_at"` + LastUsedAt *time.Time `json:"last_used_at"` + LastUsedIP *string `json:"last_used_ip"` + LastUsedAsnOrganization *string `json:"last_used_asn_organization"` + ExpiresAt *time.Time `json:"expires_at"` } type ServiceTokenGrant struct { diff --git a/internal/planetscale/service_tokens_test.go b/internal/planetscale/service_tokens_test.go index d95cc37b..cb750b34 100644 --- a/internal/planetscale/service_tokens_test.go +++ b/internal/planetscale/service_tokens_test.go @@ -103,7 +103,7 @@ func TestServiceTokens_Get(t *testing.T) { c.Assert(r.Method, qt.Equals, http.MethodGet) c.Assert(r.URL.String(), qt.Equals, wantURL) - out := `{"id":"1234","name":"my-token","token":null,"created_at":"2021-01-14T10:19:23.000Z","last_used_at":"2021-01-15T12:30:00.000Z","service_token_accesses":[{"access":"read_branch"}]}` + out := `{"id":"1234","name":"my-token","token":null,"created_at":"2021-01-14T10:19:23.000Z","last_used_at":"2021-01-15T12:30:00.000Z","last_used_ip":"203.0.113.5","last_used_asn_organization":"Example Networks","service_token_accesses":[{"access":"read_branch"}]}` _, err := w.Write([]byte(out)) c.Assert(err, qt.IsNil) })) @@ -119,11 +119,15 @@ func TestServiceTokens_Get(t *testing.T) { tokenName := "my-token" lastUsedAt := time.Date(2021, 1, 15, 12, 30, 0, 0, time.UTC) + lastUsedIP := "203.0.113.5" + lastUsedOrganization := "Example Networks" want := &ServiceToken{ - ID: "1234", - Name: &tokenName, - CreatedAt: time.Date(2021, 1, 14, 10, 19, 23, 0, time.UTC), - LastUsedAt: &lastUsedAt, + ID: "1234", + Name: &tokenName, + CreatedAt: time.Date(2021, 1, 14, 10, 19, 23, 0, time.UTC), + LastUsedAt: &lastUsedAt, + LastUsedIP: &lastUsedIP, + LastUsedAsnOrganization: &lastUsedOrganization, } c.Assert(err, qt.IsNil)