Skip to content

Commit 1bb5ca5

Browse files
authored
Support for listing team tokens of an organization (#1109)
* Add support for listing team tokens * Add changelog entry
1 parent 6d7a407 commit 1bb5ca5

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Enhancements
44

55
* Adds BETA support for speculative runs with `Stacks` resources and removes VCS repo validity check on `Stack` creation, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @hwatkins05-hashicorp [#1119](https://github.com/hashicorp/go-tfe/pull/1119)
6+
* Adds support for listing team tokens, by @mkam [#1109](https://github.com/hashicorp/go-tfe/pull/1109)
67

78
# v1.81.0
89

mocks/team_token_mocks.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

team_token.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ type TeamTokens interface {
3434
// Read a team token by its token ID.
3535
ReadByID(ctx context.Context, teamID string) (*TeamToken, error)
3636

37+
// List an organization's team tokens.
38+
List(ctx context.Context, organizationID string, options *TeamTokenListOptions) (*TeamTokenList, error)
39+
3740
// Delete a team token by its team ID.
3841
Delete(ctx context.Context, teamID string) error
3942

@@ -69,6 +72,25 @@ type TeamTokenCreateOptions struct {
6972
Description *string `jsonapi:"attr,description,omitempty"`
7073
}
7174

75+
// TeamTokenListOptions contains the options for listing team tokens.
76+
type TeamTokenListOptions struct {
77+
ListOptions
78+
79+
// Optional: A query string used to filter team tokens by
80+
// a specified team name.
81+
Query string `url:"q,omitempty"`
82+
83+
// Optional: Allows sorting the team tokens by "team-name",
84+
// "created-by", "expired-at", and "last-used-at"
85+
Sort string `url:"sort,omitempty"`
86+
}
87+
88+
// TeamTokenList represents a list of team tokens.
89+
type TeamTokenList struct {
90+
*Pagination
91+
Items []*TeamToken
92+
}
93+
7294
// Create a new team token using the legacy creation behavior, which creates a token without a description
7395
// or regenerates the existing, descriptionless token.
7496
func (s *teamTokens) Create(ctx context.Context, teamID string) (*TeamToken, error) {
@@ -146,6 +168,28 @@ func (s *teamTokens) ReadByID(ctx context.Context, tokenID string) (*TeamToken,
146168
return tt, err
147169
}
148170

171+
// List an organization's team tokens with the option to filter by team name.
172+
func (s *teamTokens) List(ctx context.Context, organizationID string, options *TeamTokenListOptions) (*TeamTokenList, error) {
173+
if !validStringID(&organizationID) {
174+
return nil, ErrInvalidOrg
175+
}
176+
177+
u := fmt.Sprintf("organizations/%s/team-tokens", url.PathEscape(organizationID))
178+
179+
req, err := s.client.NewRequest("GET", u, options)
180+
if err != nil {
181+
return nil, err
182+
}
183+
184+
tt := &TeamTokenList{}
185+
err = req.Do(ctx, tt)
186+
if err != nil {
187+
return nil, err
188+
}
189+
190+
return tt, err
191+
}
192+
149193
// Delete a team token by its team ID.
150194
func (s *teamTokens) Delete(ctx context.Context, teamID string) error {
151195
if !validStringID(&teamID) {

team_token_integration_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,97 @@ func TestTeamTokensReadByID(t *testing.T) {
300300
})
301301
}
302302

303+
func TestTeamTokensList(t *testing.T) {
304+
client := testClient(t)
305+
ctx := context.Background()
306+
307+
org, orgTestCleanup := createOrganization(t, client)
308+
t.Cleanup(orgTestCleanup)
309+
310+
// Create a team with a token
311+
team1, tmTestCleanup1 := createTeam(t, client, org)
312+
t.Cleanup(tmTestCleanup1)
313+
314+
currentTime := time.Now().UTC().Truncate(time.Second)
315+
oneDayLater := currentTime.Add(24 * time.Hour)
316+
token1, ttTestCleanup := createTeamTokenWithOptions(t, client, team1, TeamTokenCreateOptions{
317+
ExpiredAt: &oneDayLater,
318+
})
319+
t.Cleanup(ttTestCleanup)
320+
321+
// Create a second team with a token that has a later expiration date
322+
team2, tmTestCleanup2 := createTeam(t, client, org)
323+
t.Cleanup(tmTestCleanup2)
324+
325+
twoDaysLater := currentTime.Add(48 * time.Hour)
326+
token2, ttTestCleanup := createTeamTokenWithOptions(t, client, team2, TeamTokenCreateOptions{
327+
ExpiredAt: &twoDaysLater,
328+
})
329+
t.Cleanup(ttTestCleanup)
330+
331+
t.Run("with team tokens across multiple teams", func(t *testing.T) {
332+
tokens, err := client.TeamTokens.List(ctx, org.Name, nil)
333+
require.NoError(t, err)
334+
require.NotNil(t, tokens)
335+
require.Len(t, tokens.Items, 2)
336+
require.ElementsMatch(t, []string{token1.ID, token2.ID}, []string{tokens.Items[0].ID, tokens.Items[1].ID})
337+
})
338+
339+
t.Run("with filtering by team name", func(t *testing.T) {
340+
tokens, err := client.TeamTokens.List(ctx, org.Name, &TeamTokenListOptions{
341+
Query: team1.Name,
342+
})
343+
require.NoError(t, err)
344+
require.NotNil(t, tokens)
345+
require.Len(t, tokens.Items, 1)
346+
require.Equal(t, token1.ID, tokens.Items[0].ID)
347+
})
348+
349+
t.Run("with sorting", func(t *testing.T) {
350+
tokens, err := client.TeamTokens.List(ctx, org.Name, &TeamTokenListOptions{
351+
Sort: "expired-at",
352+
})
353+
require.NoError(t, err)
354+
require.NotNil(t, tokens)
355+
require.Len(t, tokens.Items, 2)
356+
require.Equal(t, []string{token1.ID, token2.ID}, []string{tokens.Items[0].ID, tokens.Items[1].ID})
357+
358+
tokens, err = client.TeamTokens.List(ctx, org.Name, &TeamTokenListOptions{
359+
Sort: "-expired-at",
360+
})
361+
require.NoError(t, err)
362+
require.NotNil(t, tokens)
363+
require.Len(t, tokens.Items, 2)
364+
require.Equal(t, []string{token2.ID, token1.ID}, []string{tokens.Items[0].ID, tokens.Items[1].ID})
365+
})
366+
367+
t.Run("with multiple team tokens in a single team", func(t *testing.T) {
368+
skipUnlessBeta(t)
369+
desc1 := fmt.Sprintf("go-tfe-team-token-test-%s", randomString(t))
370+
multiToken1, ttTestCleanup := createTeamTokenWithOptions(t, client, team1, TeamTokenCreateOptions{
371+
Description: &desc1,
372+
})
373+
t.Cleanup(ttTestCleanup)
374+
375+
desc2 := fmt.Sprintf("go-tfe-team-token-test-%s", randomString(t))
376+
multiToken2, ttTestCleanup := createTeamTokenWithOptions(t, client, team1, TeamTokenCreateOptions{
377+
Description: &desc2,
378+
})
379+
t.Cleanup(ttTestCleanup)
380+
381+
tokens, err := client.TeamTokens.List(ctx, org.Name, nil)
382+
require.NoError(t, err)
383+
require.NotNil(t, tokens)
384+
require.Len(t, tokens.Items, 4)
385+
actualIDs := []string{}
386+
for _, token := range tokens.Items {
387+
actualIDs = append(actualIDs, token.ID)
388+
}
389+
require.ElementsMatch(t, []string{token1.ID, token2.ID, multiToken1.ID, multiToken2.ID},
390+
actualIDs)
391+
})
392+
}
393+
303394
func TestTeamTokensDelete(t *testing.T) {
304395
client := testClient(t)
305396
ctx := context.Background()

0 commit comments

Comments
 (0)