Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ExpiredAt to Organization, Team, User Authentication Tokens #672

Merged
merged 15 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Enhancements
* Adds support for a new variable field `version-id` by @arybolovlev [#697](https://github.com/hashicorp/go-tfe/pull/697)
* Adds `ExpiredAt` field to `OrganizationToken`, `TeamToken`, and `UserToken`. This feature will be available in TFE release, v202305-1. @JuliannaTetreault [#672](https://github.com/hashicorp/go-tfe/pull/672)

# v1.23.0

Expand Down
52 changes: 52 additions & 0 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,32 @@ func createOrganizationToken(t *testing.T, client *Client, org *Organization) (*
}
}

func createOrganizationTokenWithOptions(t *testing.T, client *Client, org *Organization, options OrganizationTokenCreateOptions) (*OrganizationToken, func()) {
var orgCleanup func()

if org == nil {
org, orgCleanup = createOrganization(t, client)
}

ctx := context.Background()
tk, err := client.OrganizationTokens.CreateWithOptions(ctx, org.Name, options)
if err != nil {
t.Fatal(err)
}

return tk, func() {
if err := client.OrganizationTokens.Delete(ctx, org.Name); err != nil {
t.Errorf("Error destroying organization token! WARNING: Dangling resources\n"+
"may exist! The full error is shown below.\n\n"+
"OrganizationToken: %s\nError: %s", tk.ID, err)
}

if orgCleanup != nil {
orgCleanup()
}
}
}

func createRunTrigger(t *testing.T, client *Client, w, sourceable *Workspace) (*RunTrigger, func()) {
var wCleanup func()
var sourceableCleanup func()
Expand Down Expand Up @@ -1753,6 +1779,32 @@ func createTeamToken(t *testing.T, client *Client, tm *Team) (*TeamToken, func()
}
}

func createTeamTokenWithOptions(t *testing.T, client *Client, tm *Team, options TeamTokenCreateOptions) (*TeamToken, func()) {
var tmCleanup func()

if tm == nil {
tm, tmCleanup = createTeam(t, client, nil)
}

ctx := context.Background()
tt, err := client.TeamTokens.CreateWithOptions(ctx, tm.ID, options)
if err != nil {
t.Fatal(err)
}

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

if tmCleanup != nil {
tmCleanup()
}
}
}

func createVariable(t *testing.T, client *Client, w *Workspace) (*Variable, func()) {
var wCleanup func()

Expand Down
15 changes: 15 additions & 0 deletions mocks/organization_token_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions mocks/team_token_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion organization_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ type OrganizationTokens interface {
// Create a new organization token, replacing any existing token.
Create(ctx context.Context, organization string) (*OrganizationToken, error)

// CreateWithOptions a new organization token with options, replacing any existing token.
CreateWithOptions(ctx context.Context, organization string, options OrganizationTokenCreateOptions) (*OrganizationToken, error)

// Read an organization token.
Read(ctx context.Context, organization string) (*OrganizationToken, error)

Expand All @@ -41,16 +44,29 @@ type OrganizationToken struct {
Description string `jsonapi:"attr,description"`
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
Token string `jsonapi:"attr,token"`
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
}

// OrganizationTokenCreateOptions contains the options for creating an organization token.
type OrganizationTokenCreateOptions struct {
// Optional: The token's expiration date.
// This feature is available in TFE release v202305-1 and later
ExpiredAt *time.Time `jsonapi:"attr,expired-at,iso8601,omitempty"`
}

// Create a new organization token, replacing any existing token.
func (s *organizationTokens) Create(ctx context.Context, organization string) (*OrganizationToken, error) {
return s.CreateWithOptions(ctx, organization, OrganizationTokenCreateOptions{})
}

// CreateWithOptions a new organization token with options, replacing any existing token.
func (s *organizationTokens) CreateWithOptions(ctx context.Context, organization string, options OrganizationTokenCreateOptions) (*OrganizationToken, error) {
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
if !validStringID(&organization) {
return nil, ErrInvalidOrg
}

u := fmt.Sprintf("organizations/%s/authentication-token", url.QueryEscape(organization))
req, err := s.client.NewRequest("POST", u, nil)
req, err := s.client.NewRequest("POST", u, &options)
if err != nil {
return nil, err
}
Expand Down
62 changes: 62 additions & 0 deletions organization_token_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package tfe
import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -40,6 +41,54 @@ func TestOrganizationTokensCreate(t *testing.T) {
})
}

func TestOrganizationTokens_CreateWithOptions(t *testing.T) {
client := testClient(t)
ctx := context.Background()

orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

var tkToken string
t.Run("with valid options", func(t *testing.T) {
ot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{})
require.NoError(t, err)
require.NotEmpty(t, ot.Token)
tkToken = ot.Token
})

t.Run("when a token already exists", func(t *testing.T) {
ot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{})
require.NoError(t, err)
require.NotEmpty(t, ot.Token)
assert.NotEqual(t, tkToken, ot.Token)
})

t.Run("without valid organization", func(t *testing.T) {
ot, err := client.OrganizationTokens.CreateWithOptions(ctx, badIdentifier, OrganizationTokenCreateOptions{})
assert.Nil(t, ot)
assert.EqualError(t, err, ErrInvalidOrg.Error())
})

t.Run("without an expiration date", func(t *testing.T) {
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
ot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{})
require.NoError(t, err)
require.NotEmpty(t, ot.Token)
assert.Empty(t, ot.ExpiredAt)
tkToken = ot.Token
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
})

t.Run("with an expiration date", func(t *testing.T) {
start := time.Date(2024, 1, 15, 22, 3, 4, 0, time.UTC)
ot, err := client.OrganizationTokens.CreateWithOptions(ctx, orgTest.Name, OrganizationTokenCreateOptions{
ExpiredAt: &start,
})
require.NoError(t, err)
require.NotEmpty(t, ot.Token)
assert.Equal(t, ot.ExpiredAt, start)
tkToken = ot.Token
})
}

func TestOrganizationTokensRead(t *testing.T) {
client := testClient(t)
ctx := context.Background()
Expand All @@ -57,6 +106,19 @@ func TestOrganizationTokensRead(t *testing.T) {
otTestCleanup()
})

t.Run("with an expiration date passed as a valid option", func(t *testing.T) {
start := time.Date(2024, 1, 15, 22, 3, 4, 0, time.UTC)

_, otTestCleanup := createOrganizationTokenWithOptions(t, client, orgTest, OrganizationTokenCreateOptions{ExpiredAt: &start})

ot, err := client.OrganizationTokens.Read(ctx, orgTest.Name)
require.NoError(t, err)
assert.NotEmpty(t, ot)
assert.Equal(t, ot.ExpiredAt, start)

otTestCleanup()
})

t.Run("when a token doesn't exists", func(t *testing.T) {
ot, err := client.OrganizationTokens.Read(ctx, orgTest.Name)
assert.Equal(t, ErrResourceNotFound, err)
Expand Down
18 changes: 17 additions & 1 deletion team_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ type TeamTokens interface {
// Create a new team token, replacing any existing token.
Create(ctx context.Context, teamID string) (*TeamToken, error)

// CreateWithOptions a new team token, with options, replacing any existing token.
CreateWithOptions(ctx context.Context, teamID string, options TeamTokenCreateOptions) (*TeamToken, error)

// Read a team token by its ID.
Read(ctx context.Context, teamID string) (*TeamToken, error)

Expand All @@ -41,16 +44,29 @@ type TeamToken struct {
Description string `jsonapi:"attr,description"`
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
Token string `jsonapi:"attr,token"`
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
}

// TeamTokenCreateOptions contains the options for creating a team token.
type TeamTokenCreateOptions struct {
// Optional: The token's expiration date.
// This feature is available in TFE release v202305-1 and later
ExpiredAt *time.Time `jsonapi:"attr,expired-at,iso8601,omitempty"`
}

// Create a new team token, replacing any existing token.
func (s *teamTokens) Create(ctx context.Context, teamID string) (*TeamToken, error) {
return s.CreateWithOptions(ctx, teamID, TeamTokenCreateOptions{})
}

// CreateWithOptions a new team token, with options, replacing any existing token.
func (s *teamTokens) CreateWithOptions(ctx context.Context, teamID string, options TeamTokenCreateOptions) (*TeamToken, error) {
if !validStringID(&teamID) {
return nil, ErrInvalidTeamID
}

u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID))
req, err := s.client.NewRequest("POST", u, nil)
req, err := s.client.NewRequest("POST", u, &options)
if err != nil {
return nil, err
}
Expand Down
63 changes: 63 additions & 0 deletions team_token_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package tfe
import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -39,6 +40,55 @@ func TestTeamTokensCreate(t *testing.T) {
assert.Equal(t, err, ErrInvalidTeamID)
})
}

func TestTeamTokens_CreateWithOptions(t *testing.T) {
client := testClient(t)
ctx := context.Background()

tmTest, tmTestCleanup := createTeam(t, client, nil)
defer tmTestCleanup()

var tmToken string
t.Run("with valid options", func(t *testing.T) {
tt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{})
require.NoError(t, err)
require.NotEmpty(t, tt.Token)
tmToken = tt.Token
})

t.Run("when a token already exists", func(t *testing.T) {
tt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{})
require.NoError(t, err)
require.NotEmpty(t, tt.Token)
assert.NotEqual(t, tmToken, tt.Token)
})

t.Run("without valid team ID", func(t *testing.T) {
tt, err := client.TeamTokens.CreateWithOptions(ctx, badIdentifier, TeamTokenCreateOptions{})
assert.Nil(t, tt)
assert.Equal(t, err, ErrInvalidTeamID)
})

t.Run("without an expiration date", func(t *testing.T) {
tt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{})
require.NoError(t, err)
require.NotEmpty(t, tt.Token)
assert.Empty(t, tt.ExpiredAt)
tmToken = tt.Token
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
})

t.Run("with an expiration date", func(t *testing.T) {
start := time.Date(2024, 1, 15, 22, 3, 4, 0, time.UTC)
tt, err := client.TeamTokens.CreateWithOptions(ctx, tmTest.ID, TeamTokenCreateOptions{
ExpiredAt: &start,
})
require.NoError(t, err)
require.NotEmpty(t, tt.Token)
assert.Equal(t, tt.ExpiredAt, start)
tmToken = tt.Token
})
}

func TestTeamTokensRead(t *testing.T) {
client := testClient(t)
ctx := context.Background()
Expand All @@ -56,6 +106,19 @@ func TestTeamTokensRead(t *testing.T) {
ttTestCleanup()
})

t.Run("with an expiration date passed as a valid option", func(t *testing.T) {
start := time.Date(2024, 1, 15, 22, 3, 4, 0, time.UTC)

_, ttTestCleanup := createTeamTokenWithOptions(t, client, tmTest, TeamTokenCreateOptions{ExpiredAt: &start})

tt, err := client.TeamTokens.Read(ctx, tmTest.ID)
require.NoError(t, err)
assert.NotEmpty(t, tt)
assert.Equal(t, tt.ExpiredAt, start)

ttTestCleanup()
})

t.Run("when a token doesn't exists", func(t *testing.T) {
tt, err := client.TeamTokens.Read(ctx, tmTest.ID)
assert.Equal(t, ErrResourceNotFound, err)
Expand Down
7 changes: 5 additions & 2 deletions user_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ type UserToken struct {
Description string `jsonapi:"attr,description"`
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
Token string `jsonapi:"attr,token"`
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
}

// UserTokenCreateOptions the options for creating a user token.
// UserTokenCreateOptions contains the options for creating a user token.
type UserTokenCreateOptions struct {
// Optional: Description of the token
Description string `jsonapi:"attr,description,omitempty"`
// Optional: The token's expiration date.
// This feature is available in TFE release v202305-1 and later
juliannatetreault marked this conversation as resolved.
Show resolved Hide resolved
ExpiredAt *time.Time `jsonapi:"attr,expired-at,iso8601,omitempty"`
}

// Create a new user token
Expand Down
Loading