Skip to content

Commit b0ae5a7

Browse files
authored
Merge pull request #1083 from hashicorp/mkam/TF-24514/multiple-team-tokens
Support creation of multiple team tokens
2 parents 303a2a8 + 054efca commit b0ae5a7

File tree

9 files changed

+299
-15
lines changed

9 files changed

+299
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Enhancements
44

55
* Remove `DefaultProject` from `OrganizationUpdateOptions` to prevent updating an organization's default project, by @netramali [#1078](https://github.com/hashicorp/go-tfe/pull/1078)
6+
* Adds support for creating multiple team tokens by adding `Description` to `TeamTokenCreateOptions`. This provides BETA support, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users, by @mkam [#1083](https://github.com/hashicorp/go-tfe/pull/1083)
7+
* Adds support for reading and deleting team tokens by ID, by @mkam [#1083](https://github.com/hashicorp/go-tfe/pull/1083)
68

79
## BREAKING CHANGES
810

agent_token.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func (s *agentTokens) Read(ctx context.Context, agentTokenID string) (*AgentToke
116116
return nil, ErrInvalidAgentTokenID
117117
}
118118

119-
u := fmt.Sprintf("authentication-tokens/%s", url.PathEscape(agentTokenID))
119+
u := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(agentTokenID))
120120
req, err := s.client.NewRequest("GET", u, nil)
121121
if err != nil {
122122
return nil, err
@@ -137,7 +137,7 @@ func (s *agentTokens) Delete(ctx context.Context, agentTokenID string) error {
137137
return ErrInvalidAgentTokenID
138138
}
139139

140-
u := fmt.Sprintf("authentication-tokens/%s", url.PathEscape(agentTokenID))
140+
u := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(agentTokenID))
141141
req, err := s.client.NewRequest("DELETE", u, nil)
142142
if err != nil {
143143
return err

const.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package tfe
5+
6+
const (
7+
AuthenticationTokensPath = "authentication-tokens/%s"
8+
)

errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ var (
236236
ErrInvalidAccessToken = errors.New("invalid value for access token")
237237

238238
ErrInvalidTaskResultsCallbackStatus = fmt.Errorf("invalid value for task result status. Must be either `%s`, `%s`, or `%s`", TaskFailed, TaskPassed, TaskRunning)
239+
240+
ErrInvalidDescriptionConflict = errors.New("invalid attributes\n\nValidation failed: Description has already been taken")
239241
)
240242

241243
var (

helper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2196,7 +2196,7 @@ func createTeamTokenWithOptions(t *testing.T, client *Client, tm *Team, options
21962196
}
21972197

21982198
return tt, func() {
2199-
if err := client.TeamTokens.Delete(ctx, tm.ID); err != nil {
2199+
if err := client.TeamTokens.DeleteByID(ctx, tt.ID); err != nil {
22002200
t.Errorf("Error destroying team token! WARNING: Dangling resources\n"+
22012201
"may exist! The full error is shown below.\n\n"+
22022202
"TeamToken: %s\nError: %s", tm.ID, err)

mocks/team_token_mocks.go

Lines changed: 29 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: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,26 @@ var _ TeamTokens = (*teamTokens)(nil)
1919
// TFE API docs:
2020
// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/team-tokens
2121
type TeamTokens interface {
22-
// Create a new team token, replacing any existing token.
22+
// Create a new team token using the legacy creation behavior, which creates a token without a description
23+
// or regenerates the existing, descriptionless token.
2324
Create(ctx context.Context, teamID string) (*TeamToken, error)
2425

25-
// CreateWithOptions a new team token, with options, replacing any existing token.
26+
// CreateWithOptions creates a team token, with options. If no description is provided, it uses the legacy
27+
// creation behavior, which regenerates the descriptionless token if it already exists. Otherwise, it create
28+
// a new token with the given unique description, allowing for the creation of multiple team tokens.
2629
CreateWithOptions(ctx context.Context, teamID string, options TeamTokenCreateOptions) (*TeamToken, error)
2730

28-
// Read a team token by its ID.
31+
// Read a team token by its team ID.
2932
Read(ctx context.Context, teamID string) (*TeamToken, error)
3033

31-
// Delete a team token by its ID.
34+
// Read a team token by its token ID.
35+
ReadByID(ctx context.Context, teamID string) (*TeamToken, error)
36+
37+
// Delete a team token by its team ID.
3238
Delete(ctx context.Context, teamID string) error
39+
40+
// Delete a team token by its token ID.
41+
DeleteByID(ctx context.Context, tokenID string) error
3342
}
3443

3544
// teamTokens implements TeamTokens.
@@ -46,27 +55,41 @@ type TeamToken struct {
4655
Token string `jsonapi:"attr,token"`
4756
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
4857
CreatedBy *CreatedByChoice `jsonapi:"polyrelation,created-by"`
58+
Team *Team `jsonapi:"relation,team"`
4959
}
5060

5161
// TeamTokenCreateOptions contains the options for creating a team token.
5262
type TeamTokenCreateOptions struct {
5363
// Optional: The token's expiration date.
5464
// This feature is available in TFE release v202305-1 and later
5565
ExpiredAt *time.Time `jsonapi:"attr,expired-at,iso8601,omitempty"`
66+
67+
// Optional: The token's description, which must unique per team.
68+
// This feature is considered BETA, SUBJECT TO CHANGE, and likely unavailable to most users.
69+
Description string `jsonapi:"attr,description,omitempty"`
5670
}
5771

58-
// Create a new team token, replacing any existing token.
72+
// Create a new team token using the legacy creation behavior, which creates a token without a description
73+
// or regenerates the existing, descriptionless token.
5974
func (s *teamTokens) Create(ctx context.Context, teamID string) (*TeamToken, error) {
6075
return s.CreateWithOptions(ctx, teamID, TeamTokenCreateOptions{})
6176
}
6277

63-
// CreateWithOptions a new team token, with options, replacing any existing token.
78+
// CreateWithOptions creates a team token, with options. If no description is provided, it uses the legacy
79+
// creation behavior, which regenerates the descriptionless token if it already exists. Otherwise, it create
80+
// a new token with the given unique description, allowing for the creation of multiple team tokens.
6481
func (s *teamTokens) CreateWithOptions(ctx context.Context, teamID string, options TeamTokenCreateOptions) (*TeamToken, error) {
6582
if !validStringID(&teamID) {
6683
return nil, ErrInvalidTeamID
6784
}
6885

69-
u := fmt.Sprintf("teams/%s/authentication-token", url.PathEscape(teamID))
86+
var u string
87+
if options.Description != "" {
88+
u = fmt.Sprintf("teams/%s/authentication-tokens", url.PathEscape(teamID))
89+
} else {
90+
u = fmt.Sprintf("teams/%s/authentication-token", url.PathEscape(teamID))
91+
}
92+
7093
req, err := s.client.NewRequest("POST", u, &options)
7194
if err != nil {
7295
return nil, err
@@ -81,7 +104,7 @@ func (s *teamTokens) CreateWithOptions(ctx context.Context, teamID string, optio
81104
return tt, err
82105
}
83106

84-
// Read a team token by its ID.
107+
// Read a team token by its team ID.
85108
func (s *teamTokens) Read(ctx context.Context, teamID string) (*TeamToken, error) {
86109
if !validStringID(&teamID) {
87110
return nil, ErrInvalidTeamID
@@ -102,7 +125,28 @@ func (s *teamTokens) Read(ctx context.Context, teamID string) (*TeamToken, error
102125
return tt, err
103126
}
104127

105-
// Delete a team token by its ID.
128+
// Read a team token by its token ID.
129+
func (s *teamTokens) ReadByID(ctx context.Context, tokenID string) (*TeamToken, error) {
130+
if !validStringID(&tokenID) {
131+
return nil, ErrInvalidTokenID
132+
}
133+
134+
u := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(tokenID))
135+
req, err := s.client.NewRequest("GET", u, nil)
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
tt := &TeamToken{}
141+
err = req.Do(ctx, tt)
142+
if err != nil {
143+
return nil, err
144+
}
145+
146+
return tt, err
147+
}
148+
149+
// Delete a team token by its team ID.
106150
func (s *teamTokens) Delete(ctx context.Context, teamID string) error {
107151
if !validStringID(&teamID) {
108152
return ErrInvalidTeamID
@@ -116,3 +160,18 @@ func (s *teamTokens) Delete(ctx context.Context, teamID string) error {
116160

117161
return req.Do(ctx, nil)
118162
}
163+
164+
// Delete a team token by its token ID.
165+
func (s *teamTokens) DeleteByID(ctx context.Context, tokenID string) error {
166+
if !validStringID(&tokenID) {
167+
return ErrInvalidTokenID
168+
}
169+
170+
u := fmt.Sprintf(AuthenticationTokensPath, url.PathEscape(tokenID))
171+
req, err := s.client.NewRequest("DELETE", u, nil)
172+
if err != nil {
173+
return err
174+
}
175+
176+
return req.Do(ctx, nil)
177+
}

0 commit comments

Comments
 (0)