-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: add agent token support Co-authored-by: Chris Arcand <[email protected]>
- Loading branch information
1 parent
46f8be3
commit f3a4dd1
Showing
4 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package tfe | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"net/url" | ||
"time" | ||
) | ||
|
||
// Compile-time proof of interface implementation. | ||
var _ AgentTokens = (*agentTokens)(nil) | ||
|
||
// AgentTokens describes all the agent token related methods that the | ||
// Terraform Cloud API supports. | ||
// | ||
// TFE API docs: | ||
// https://www.terraform.io/docs/cloud/api/agent-tokens.html | ||
type AgentTokens interface { | ||
// List all the agent tokens of the given agent pool. | ||
List(ctx context.Context, agentPoolID string) (*AgentTokenList, error) | ||
|
||
// Generate a new agent token with the given options. | ||
Generate(ctx context.Context, agentPoolID string, options AgentTokenGenerateOptions) (*AgentToken, error) | ||
|
||
// Read an agent token by its ID. | ||
Read(ctx context.Context, agentTokenID string) (*AgentToken, error) | ||
|
||
// Delete an agent token by its ID. | ||
Delete(ctx context.Context, agentTokenID string) error | ||
} | ||
|
||
// agentTokens implements AgentTokens. | ||
type agentTokens struct { | ||
client *Client | ||
} | ||
|
||
// AgentTokenList represents a list of agent tokens. | ||
type AgentTokenList struct { | ||
*Pagination | ||
Items []*AgentToken | ||
} | ||
|
||
// AgentToken represents a Terraform Cloud agent token. | ||
type AgentToken struct { | ||
ID string `jsonapi:"primary,authentication-tokens"` | ||
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` | ||
Description string `jsonapi:"attr,description"` | ||
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"` | ||
Token string `jsonapi:"attr,token"` | ||
} | ||
|
||
// List all the agent tokens of the given agent pool. | ||
func (s *agentTokens) List(ctx context.Context, agentPoolID string) (*AgentTokenList, error) { | ||
if !validStringID(&agentPoolID) { | ||
return nil, errors.New("invalid value for agent pool ID") | ||
} | ||
|
||
u := fmt.Sprintf("agent-pools/%s/authentication-tokens", url.QueryEscape(agentPoolID)) | ||
req, err := s.client.newRequest("GET", u, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
tokenList := &AgentTokenList{} | ||
err = s.client.do(ctx, req, tokenList) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return tokenList, nil | ||
} | ||
|
||
// AgentTokenGenerateOptions represents the options for creating an agent token. | ||
type AgentTokenGenerateOptions struct { | ||
// For internal use only! | ||
ID string `jsonapi:"primary,agent-tokens"` | ||
|
||
// Description of the token | ||
Description *string `jsonapi:"attr,description"` | ||
} | ||
|
||
// Generate a new agent token with the given options. | ||
func (s *agentTokens) Generate(ctx context.Context, agentPoolID string, options AgentTokenGenerateOptions) (*AgentToken, error) { | ||
if !validStringID(&agentPoolID) { | ||
return nil, errors.New("invalid value for agent pool ID") | ||
} | ||
|
||
if !validString(options.Description) { | ||
return nil, errors.New("agent token description can't be blank") | ||
} | ||
|
||
// Make sure we don't send a user provided ID. | ||
options.ID = "" | ||
|
||
u := fmt.Sprintf("agent-pools/%s/authentication-tokens", url.QueryEscape(agentPoolID)) | ||
req, err := s.client.newRequest("POST", u, &options) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
at := &AgentToken{} | ||
err = s.client.do(ctx, req, at) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return at, err | ||
} | ||
|
||
// Read an agent token by its ID. | ||
func (s *agentTokens) Read(ctx context.Context, agentTokenID string) (*AgentToken, error) { | ||
if !validStringID(&agentTokenID) { | ||
return nil, errors.New("invalid value for agent token ID") | ||
} | ||
|
||
u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(agentTokenID)) | ||
req, err := s.client.newRequest("GET", u, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
at := &AgentToken{} | ||
err = s.client.do(ctx, req, at) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return at, err | ||
} | ||
|
||
// Delete an agent token by its ID. | ||
func (s *agentTokens) Delete(ctx context.Context, agentTokenID string) error { | ||
if !validStringID(&agentTokenID) { | ||
return errors.New("invalid value for agent token ID") | ||
} | ||
|
||
u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(agentTokenID)) | ||
req, err := s.client.newRequest("DELETE", u, nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return s.client.do(ctx, req, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package tfe | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestAgentTokensList(t *testing.T) { | ||
client := testClient(t) | ||
ctx := context.Background() | ||
|
||
apTest, apTestCleanup := createAgentPool(t, client, nil) | ||
defer apTestCleanup() | ||
|
||
agentToken1, agentToken1Cleanup := createAgentToken(t, client, apTest) | ||
defer agentToken1Cleanup() | ||
_, agentToken2Cleanup := createAgentToken(t, client, apTest) | ||
defer agentToken2Cleanup() | ||
|
||
t.Run("with no list options", func(t *testing.T) { | ||
tokenlist, err := client.AgentTokens.List(ctx, apTest.ID) | ||
require.NoError(t, err) | ||
var found bool | ||
for _, j := range tokenlist.Items { | ||
if j.ID == agentToken1.ID { | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
t.Fatalf("agent token (%s) not found in token list", agentToken1.ID) | ||
} | ||
|
||
assert.Equal(t, 1, tokenlist.CurrentPage) | ||
assert.Equal(t, 2, tokenlist.TotalCount) | ||
}) | ||
|
||
t.Run("without a valid agent pool ID", func(t *testing.T) { | ||
tokenlist, err := client.AgentTokens.List(ctx, badIdentifier) | ||
assert.Nil(t, tokenlist) | ||
assert.EqualError(t, err, "invalid value for agent pool ID") | ||
}) | ||
} | ||
|
||
func TestAgentTokensGenerate(t *testing.T) { | ||
client := testClient(t) | ||
ctx := context.Background() | ||
|
||
apTest, apTestCleanup := createAgentPool(t, client, nil) | ||
defer apTestCleanup() | ||
|
||
t.Run("with valid description", func(t *testing.T) { | ||
token, err := client.AgentTokens.Generate(ctx, apTest.ID, AgentTokenGenerateOptions{ | ||
Description: String(randomString(t)), | ||
}) | ||
require.NoError(t, err) | ||
require.NotEmpty(t, token.Token) | ||
}) | ||
|
||
t.Run("without valid description", func(t *testing.T) { | ||
at, err := client.AgentTokens.Generate(ctx, badIdentifier, AgentTokenGenerateOptions{}) | ||
assert.Nil(t, at) | ||
assert.EqualError(t, err, "invalid value for agent pool ID") | ||
}) | ||
|
||
t.Run("without valid agent pool ID", func(t *testing.T) { | ||
at, err := client.AgentTokens.Generate(ctx, badIdentifier, AgentTokenGenerateOptions{ | ||
Description: String(randomString(t)), | ||
}) | ||
assert.Nil(t, at) | ||
assert.EqualError(t, err, "invalid value for agent pool ID") | ||
}) | ||
} | ||
func TestAgentTokensRead(t *testing.T) { | ||
client := testClient(t) | ||
ctx := context.Background() | ||
|
||
apTest, apTestCleanup := createAgentPool(t, client, nil) | ||
defer apTestCleanup() | ||
|
||
token, tokenTestCleanup := createAgentToken(t, client, apTest) | ||
defer tokenTestCleanup() | ||
|
||
t.Run("read token with valid token ID", func(t *testing.T) { | ||
at, err := client.AgentTokens.Read(ctx, token.ID) | ||
assert.NoError(t, err) | ||
// The initial API call to create a token will return a value in the token | ||
// object. Empty that out for comparison | ||
token.Token = "" | ||
assert.Equal(t, token, at) | ||
}) | ||
|
||
t.Run("read token without valid token ID", func(t *testing.T) { | ||
_, err := client.AgentTokens.Read(ctx, badIdentifier) | ||
assert.EqualError(t, err, "invalid value for agent token ID") | ||
}) | ||
} | ||
|
||
func TestAgentTokensDelete(t *testing.T) { | ||
client := testClient(t) | ||
ctx := context.Background() | ||
|
||
apTest, apTestCleanup := createAgentPool(t, client, nil) | ||
defer apTestCleanup() | ||
|
||
token, _ := createAgentToken(t, client, apTest) | ||
|
||
t.Run("with valid token ID", func(t *testing.T) { | ||
err := client.AgentTokens.Delete(ctx, token.ID) | ||
assert.NoError(t, err) | ||
}) | ||
|
||
t.Run("without valid token ID", func(t *testing.T) { | ||
err := client.AgentTokens.Delete(ctx, badIdentifier) | ||
assert.EqualError(t, err, "invalid value for agent token ID") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters