Skip to content

Conversation

mkam
Copy link
Contributor

@mkam mkam commented Apr 3, 2025

Description

This PR adds support for creating multiple team tokens via the new team token APIs (teams/:team_id/authentication-tokens). The changes made are backwards compatible with the existing team token API (teams/:team_id/authentication-token) that assumes there is only a single token for the team.

The new team token API requires the tokens to have a unique description per team. I’ve used the presence of the description in the create options to determine whether CreateWithOptions should make the request against the old endpoint or the new endpoint.

For deleting and reading tokens, I added new methods for getting the team token by its token ID, as opposed to the existing methods that use the team ID. These methods work still with the legacy style tokens, so they are not part of the beta feature.

Testing plan

  1. Create a team
  2. Create a token for that team without a description using the legacy endpoint
  3. Regenerate the team token without a description using the legacy endpoint
  4. Read the team token using the legacy endpoint
  5. Delete the team token using the legacy endpoint
  6. Create a new token for that team with a description
  7. Create another token for that team with a different description
  8. Read the team tokens by ID
  9. Delete the team tokens by ID

Output from tests

Test output of all team token tests
-> % ENABLE_BETA=1 go test ./... -v -run "TestTeamTokens"
?   	github.com/hashicorp/go-tfe/examples/backing_data	[no test files]
?   	github.com/hashicorp/go-tfe/examples/configuration_versions	[no test files]
?   	github.com/hashicorp/go-tfe/examples/organizations	[no test files]
?   	github.com/hashicorp/go-tfe/examples/projects	[no test files]
?   	github.com/hashicorp/go-tfe/examples/registry_modules	[no test files]
?   	github.com/hashicorp/go-tfe/examples/run_errors	[no test files]
?   	github.com/hashicorp/go-tfe/examples/state_versions	[no test files]
?   	github.com/hashicorp/go-tfe/examples/users	[no test files]
?   	github.com/hashicorp/go-tfe/examples/workspaces	[no test files]
?   	github.com/hashicorp/go-tfe/mocks	[no test files]
=== RUN   TestTeamTokensCreate
=== RUN   TestTeamTokensCreate/with_valid_options
=== RUN   TestTeamTokensCreate/when_a_token_already_exists
=== RUN   TestTeamTokensCreate/without_valid_team_ID
--- PASS: TestTeamTokensCreate (2.29s)
    --- PASS: TestTeamTokensCreate/with_valid_options (0.23s)
    --- PASS: TestTeamTokensCreate/when_a_token_already_exists (0.50s)
    --- PASS: TestTeamTokensCreate/without_valid_team_ID (0.00s)
=== RUN   TestTeamTokens_CreateWithOptions
=== RUN   TestTeamTokens_CreateWithOptions/with_valid_options
=== RUN   TestTeamTokens_CreateWithOptions/when_a_token_already_exists
=== RUN   TestTeamTokens_CreateWithOptions/without_valid_team_ID
=== RUN   TestTeamTokens_CreateWithOptions/without_an_expiration_date
=== RUN   TestTeamTokens_CreateWithOptions/with_an_expiration_date
--- PASS: TestTeamTokens_CreateWithOptions (3.94s)
    --- PASS: TestTeamTokens_CreateWithOptions/with_valid_options (0.24s)
    --- PASS: TestTeamTokens_CreateWithOptions/when_a_token_already_exists (0.57s)
    --- PASS: TestTeamTokens_CreateWithOptions/without_valid_team_ID (0.00s)
    --- PASS: TestTeamTokens_CreateWithOptions/without_an_expiration_date (0.60s)
    --- PASS: TestTeamTokens_CreateWithOptions/with_an_expiration_date (0.67s)
=== RUN   TestTeamTokens_CreateWithOptions_MultipleTokens
=== RUN   TestTeamTokens_CreateWithOptions_MultipleTokens/with_multiple_tokens
=== RUN   TestTeamTokens_CreateWithOptions_MultipleTokens/with_an_expiration_date
=== RUN   TestTeamTokens_CreateWithOptions_MultipleTokens/without_an_expiration_date
=== RUN   TestTeamTokens_CreateWithOptions_MultipleTokens/when_a_token_already_exists_with_the_same_description
=== RUN   TestTeamTokens_CreateWithOptions_MultipleTokens/without_valid_team_ID
--- PASS: TestTeamTokens_CreateWithOptions_MultipleTokens (4.54s)
    --- PASS: TestTeamTokens_CreateWithOptions_MultipleTokens/with_multiple_tokens (0.78s)
    --- PASS: TestTeamTokens_CreateWithOptions_MultipleTokens/with_an_expiration_date (0.58s)
    --- PASS: TestTeamTokens_CreateWithOptions_MultipleTokens/without_an_expiration_date (0.58s)
    --- PASS: TestTeamTokens_CreateWithOptions_MultipleTokens/when_a_token_already_exists_with_the_same_description (1.23s)
    --- PASS: TestTeamTokens_CreateWithOptions_MultipleTokens/without_valid_team_ID (0.00s)
=== RUN   TestTeamTokensRead
=== RUN   TestTeamTokensRead/with_valid_options
=== RUN   TestTeamTokensRead/with_an_expiration_date_passed_as_a_valid_option
=== RUN   TestTeamTokensRead/when_a_token_doesn't_exists
=== RUN   TestTeamTokensRead/without_valid_organization
--- PASS: TestTeamTokensRead (3.57s)
    --- PASS: TestTeamTokensRead/with_valid_options (0.99s)
    --- PASS: TestTeamTokensRead/with_an_expiration_date_passed_as_a_valid_option (1.06s)
    --- PASS: TestTeamTokensRead/when_a_token_doesn't_exists (0.22s)
    --- PASS: TestTeamTokensRead/without_valid_organization (0.00s)
=== RUN   TestTeamTokensReadByID
=== RUN   TestTeamTokensReadByID/with_legacy,_descriptionless_tokens
=== RUN   TestTeamTokensReadByID/with_multiple_team_tokens
=== RUN   TestTeamTokensReadByID/when_a_token_doesn't_exists
--- PASS: TestTeamTokensReadByID (4.91s)
    --- PASS: TestTeamTokensReadByID/with_legacy,_descriptionless_tokens (0.99s)
    --- PASS: TestTeamTokensReadByID/with_multiple_team_tokens (2.38s)
    --- PASS: TestTeamTokensReadByID/when_a_token_doesn't_exists (0.19s)
=== RUN   TestTeamTokensDelete
=== RUN   TestTeamTokensDelete/with_valid_options
=== RUN   TestTeamTokensDelete/when_a_token_does_not_exist
=== RUN   TestTeamTokensDelete/without_valid_team_ID
--- PASS: TestTeamTokensDelete (2.43s)
    --- PASS: TestTeamTokensDelete/with_valid_options (0.56s)
    --- PASS: TestTeamTokensDelete/when_a_token_does_not_exist (0.21s)
    --- PASS: TestTeamTokensDelete/without_valid_team_ID (0.00s)
=== RUN   TestTeamTokensDeleteByID
=== RUN   TestTeamTokensDeleteByID/with_legacy,_descriptionless_tokens
=== RUN   TestTeamTokensDeleteByID/with_multiple_team_tokens
=== RUN   TestTeamTokensDeleteByID/when_a_token_does_not_exist
=== RUN   TestTeamTokensDeleteByID/with_invalid_token_ID
--- PASS: TestTeamTokensDeleteByID (3.89s)
    --- PASS: TestTeamTokensDeleteByID/with_legacy,_descriptionless_tokens (0.77s)
    --- PASS: TestTeamTokensDeleteByID/with_multiple_team_tokens (1.54s)
    --- PASS: TestTeamTokensDeleteByID/when_a_token_does_not_exist (0.21s)
    --- PASS: TestTeamTokensDeleteByID/with_invalid_token_ID (0.00s)
PASS
ok  	github.com/hashicorp/go-tfe	26.186s

@mkam mkam changed the title Mkam/tf 24514/multiple team tokens Support creation of multiple team tokens Apr 3, 2025
@datadog-terraform-cloud-hashicorp
Copy link

datadog-terraform-cloud-hashicorp bot commented Apr 3, 2025

Datadog Report

Branch report: mkam/TF-24514/multiple-team-tokens
Commit report: 5cedd4d
Test service: hashicorp/go-tfe

✅ 0 Failed, 1437 Passed, 167 Skipped, 20m 46.89s Total Time
⬆️ Test Sessions change in coverage: 1 increased (+0.1%)

@mkam mkam force-pushed the mkam/TF-24514/multiple-team-tokens branch from 43190a6 to 6a08f10 Compare April 3, 2025 21:39
team_token.go Outdated
@@ -13,23 +13,36 @@ import (
// Compile-time proof of interface implementation.
var _ TeamTokens = (*teamTokens)(nil)

const (
AuthenticationTokensPath = "authentication-tokens/%s"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter complained that this needed to be made a constant. I didn't see a good common file to place this in and didn't really want to create a new file with just one constant, so I put it here. Open to feedback, though!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, here's the lint error:

golangci-lint run .
Error: agent_token.go:119:19: string `authentication-tokens/%s` has 6 occurrences, make it a constant (goconst)
	u := fmt.Sprintf("authentication-tokens/%s", url.PathEscape(agentTokenID))
	                 ^

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes! I see that our threshold for that lint rule is 5 occurrences of the same string. Can you please create a const.go file and add the constant there?
Also, can you update the delete method in agent_token.go to also use this constant? There is 1 occurrence there too.

mkam added 4 commits April 3, 2025 17:13
Tokens created with the new token API that supports multiple
team tokens must be read and deleted by ID rather than by
team ID. Legacy tokens can also be deleted via this endpoint.
@mkam mkam force-pushed the mkam/TF-24514/multiple-team-tokens branch from 402d08e to 53fa3ea Compare April 3, 2025 22:13
@mkam mkam requested review from a team, tylerwolf and juliannatetreault April 3, 2025 22:35
@mkam mkam marked this pull request as ready for review April 3, 2025 22:36
@mkam mkam requested a review from a team as a code owner April 3, 2025 22:36
When fetching by ID, we do not necessarily know the team ID, so
include the team information as part of the fetch response.
@mkam mkam force-pushed the mkam/TF-24514/multiple-team-tokens branch from 5433106 to 494878b Compare April 7, 2025 20:55
team_token.go Outdated
@@ -13,23 +13,36 @@ import (
// Compile-time proof of interface implementation.
var _ TeamTokens = (*teamTokens)(nil)

const (
AuthenticationTokensPath = "authentication-tokens/%s"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes! I see that our threshold for that lint rule is 5 occurrences of the same string. Can you please create a const.go file and add the constant there?
Also, can you update the delete method in agent_token.go to also use this constant? There is 1 occurrence there too.

CHANGELOG.md Outdated
@@ -3,6 +3,8 @@
## Enhancements

* Remove `DefaultProject` from `OrganizationUpdateOptions` to prevent updating an organization's default project, by @netramali [#1078](https://github.com/hashicorp/go-tfe/pull/1078)
* 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 [#1056](https://github.com/hashicorp/go-tfe/pull/1083)
* Adds support for reading and deleting team tokens by ID, by @mkam [#1056](https://github.com/hashicorp/go-tfe/pull/1083)
Copy link
Contributor

@uturunku1 uturunku1 Apr 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to link PR 1083 instead of 1056?

@uturunku1 uturunku1 self-requested a review April 8, 2025 20:42
@mkam mkam force-pushed the mkam/TF-24514/multiple-team-tokens branch from c75fb4c to 054efca Compare April 8, 2025 20:48
Delete(ctx context.Context, teamID string) error

// Delete a team token by its token ID.
DeleteByID(ctx context.Context, tokenID string) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be more clear to say DeleteByTokenID? because Delete and DeleteByID is a bit ambigious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally prefer DeleteByID since the struct and interface is TeamToken, so this implies that the ID is the team token ID. Here's a similar example with workspaces:

go-tfe/workspace.go

Lines 31 to 42 in 303a2a8

// Read a workspace by its name and organization name.
Read(ctx context.Context, organization string, workspace string) (*Workspace, error)
// ReadWithOptions reads a workspace by name and organization name with given options.
ReadWithOptions(ctx context.Context, organization string, workspace string, options *WorkspaceReadOptions) (*Workspace, error)
// Readme gets the readme of a workspace by its ID.
Readme(ctx context.Context, workspaceID string) (io.Reader, error)
// ReadByID reads a workspace by its ID.
ReadByID(ctx context.Context, workspaceID string) (*Workspace, error)

Copy link
Contributor

@netramali netramali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a small nit comment.

@mkam mkam merged commit b0ae5a7 into main Apr 14, 2025
8 checks passed
@mkam mkam deleted the mkam/TF-24514/multiple-team-tokens branch April 14, 2025 16:17
Copy link

Reminder to the contributor that merged this PR: if your changes have added important functionality or fixed a relevant bug, open a follow-up PR to update CHANGELOG.md with a note on your changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants