Skip to content

Commit a8d77cb

Browse files
authored
Merge pull request #93 from Scalr/feature/SCALRCORE-21783
SCALRCORE-21783 Scalr Provider > Token generation
2 parents 6329293 + acdac16 commit a8d77cb

File tree

9 files changed

+299
-51
lines changed

9 files changed

+299
-51
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Default global owners
2+
* v.mihun@scalr.com p.protsakh@scalr.com

access_token.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var _ AccessTokens = (*accessTokens)(nil)
1414
// AccessTokens describes all the access token related methods that the
1515
// Scalr IACP API supports.
1616
type AccessTokens interface {
17+
Read(ctx context.Context, accessTokenID string) (*AccessToken, error)
1718
Update(ctx context.Context, accessTokenID string, options AccessTokenUpdateOptions) (*AccessToken, error)
1819
Delete(ctx context.Context, accessTokenID string) error
1920
}
@@ -37,10 +38,46 @@ type AccessToken struct {
3738
Token string `jsonapi:"attr,token"`
3839
}
3940

41+
// AccessTokenListOptions represents the options for listing access tokens.
42+
type AccessTokenListOptions struct {
43+
ListOptions
44+
}
45+
46+
// AccessTokenCreateOptions represents the options for creating a new AccessToken.
47+
type AccessTokenCreateOptions struct {
48+
// For internal use only!
49+
ID string `jsonapi:"primary,access-tokens"`
50+
51+
Description *string `jsonapi:"attr,description,omitempty"`
52+
}
53+
4054
// AccessTokenUpdateOptions represents the options for updating an AccessToken.
4155
type AccessTokenUpdateOptions struct {
42-
ID string `jsonapi:"primary,access-tokens"`
43-
Description *string `jsonapi:"attr,description"`
56+
// For internal use only!
57+
ID string `jsonapi:"primary,access-tokens"`
58+
59+
Description *string `jsonapi:"attr,description,omitempty"`
60+
}
61+
62+
// Read access token by its ID
63+
func (s *accessTokens) Read(ctx context.Context, accessTokenID string) (*AccessToken, error) {
64+
if !validStringID(&accessTokenID) {
65+
return nil, errors.New("invalid value for access token ID")
66+
}
67+
68+
u := fmt.Sprintf("access-tokens/%s", url.QueryEscape(accessTokenID))
69+
req, err := s.client.newRequest("GET", u, nil)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
at := &AccessToken{}
75+
err = s.client.do(ctx, req, at)
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
return at, nil
4481
}
4582

4683
// Update is used to update an AccessToken.
@@ -53,10 +90,6 @@ func (s *accessTokens) Update(ctx context.Context, accessTokenID string, options
5390
return nil, fmt.Errorf("invalid value for access token ID: '%s'", accessTokenID)
5491
}
5592

56-
if !validString(options.Description) {
57-
return nil, errors.New("value for description must be a valid string")
58-
}
59-
6093
req, err := s.client.newRequest("PATCH", fmt.Sprintf("access-tokens/%s", url.QueryEscape(accessTokenID)), &options)
6194
if err != nil {
6295
return nil, err

access_token_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12+
func TestAccessTokenRead(t *testing.T) {
13+
client := testClient(t)
14+
ctx := context.Background()
15+
16+
ap, apCleanup := createAgentPool(t, client)
17+
defer apCleanup()
18+
19+
atTest, atTestCleanup := createAgentPoolToken(t, client, ap.ID)
20+
defer atTestCleanup()
21+
22+
t.Run("when the token exists", func(t *testing.T) {
23+
at, err := client.AccessTokens.Read(ctx, atTest.ID)
24+
require.NoError(t, err)
25+
assert.Equal(t, atTest.ID, at.ID)
26+
})
27+
28+
t.Run("when the token does not exist", func(t *testing.T) {
29+
_, err := client.AccessTokens.Read(ctx, "at-nonexisting")
30+
assert.Error(t, err)
31+
})
32+
33+
t.Run("with invalid token ID", func(t *testing.T) {
34+
_, err := client.AccessTokens.Read(ctx, badIdentifier)
35+
assert.EqualError(t, err, "invalid value for access token ID")
36+
})
37+
}
38+
1239
func TestAccessTokenUpdate(t *testing.T) {
1340
client := testClient(t)
1441
ctx := context.Background()
@@ -57,7 +84,7 @@ func TestAccessTokenDelete(t *testing.T) {
5784
err := client.AccessTokens.Delete(ctx, apt.ID)
5885
require.NoError(t, err)
5986

60-
l, err := client.AgentPoolTokens.List(ctx, ap.ID, AgentPoolTokenListOptions{})
87+
l, err := client.AgentPoolTokens.List(ctx, ap.ID, AccessTokenListOptions{})
6188
assert.Len(t, l.Items, 0)
6289
})
6390

agent_pool_token.go

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"net/url"
7-
"time"
87
)
98

109
// Compile-time proof of interface implementation.
@@ -13,48 +12,23 @@ var _ AgentPoolTokens = (*agentPoolTokens)(nil)
1312
// AgentPoolTokens describes all the access token related methods that the
1413
// Scalr IACP API supports.
1514
type AgentPoolTokens interface {
16-
List(ctx context.Context, agentPoolID string, options AgentPoolTokenListOptions) (*AgentPoolTokenList, error)
17-
Create(ctx context.Context, agentPoolID string, options AgentPoolTokenCreateOptions) (*AgentPoolToken, error)
15+
List(ctx context.Context, agentPoolID string, options AccessTokenListOptions) (*AccessTokenList, error)
16+
Create(ctx context.Context, agentPoolID string, options AccessTokenCreateOptions) (*AccessToken, error)
1817
}
1918

2019
// agentPoolTokens implements AgentPoolTokens.
2120
type agentPoolTokens struct {
2221
client *Client
2322
}
2423

25-
// AgentPoolTokenList represents a list of agent pools.
26-
type AgentPoolTokenList struct {
27-
*Pagination
28-
Items []*AgentPoolToken
29-
}
30-
31-
// AgentPoolToken represents a Scalr agent pool.
32-
type AgentPoolToken struct {
33-
ID string `jsonapi:"primary,access-tokens"`
34-
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
35-
Description string `jsonapi:"attr,description"`
36-
Token string `jsonapi:"attr,token"`
37-
}
38-
39-
// AgentPoolTokenCreateOptions represents the options for creating a new AgentPoolToken.
40-
type AgentPoolTokenListOptions struct {
41-
ListOptions
42-
}
43-
44-
// AgentPoolTokenCreateOptions represents the options for creating a new AgentPoolToken.
45-
type AgentPoolTokenCreateOptions struct {
46-
ID string `jsonapi:"primary,access-tokens"`
47-
Description *string `jsonapi:"attr,description,omitempty"`
48-
}
49-
50-
// List all the agent pools.
51-
func (s *agentPoolTokens) List(ctx context.Context, agentPoolID string, options AgentPoolTokenListOptions) (*AgentPoolTokenList, error) {
24+
// List all the agent pool's tokens.
25+
func (s *agentPoolTokens) List(ctx context.Context, agentPoolID string, options AccessTokenListOptions) (*AccessTokenList, error) {
5226
req, err := s.client.newRequest("GET", fmt.Sprintf("agent-pools/%s/access-tokens", url.QueryEscape(agentPoolID)), &options)
5327
if err != nil {
5428
return nil, err
5529
}
5630

57-
tl := &AgentPoolTokenList{}
31+
tl := &AccessTokenList{}
5832
err = s.client.do(ctx, req, tl)
5933
if err != nil {
6034
return nil, err
@@ -63,8 +37,8 @@ func (s *agentPoolTokens) List(ctx context.Context, agentPoolID string, options
6337
return tl, nil
6438
}
6539

66-
// Create is used to create a new AgentPoolToken.
67-
func (s *agentPoolTokens) Create(ctx context.Context, agentPoolID string, options AgentPoolTokenCreateOptions) (*AgentPoolToken, error) {
40+
// Create is used to create a new AccessToken for AgentPool.
41+
func (s *agentPoolTokens) Create(ctx context.Context, agentPoolID string, options AccessTokenCreateOptions) (*AccessToken, error) {
6842

6943
// Make sure we don't send a user provided ID.
7044
options.ID = ""
@@ -78,7 +52,7 @@ func (s *agentPoolTokens) Create(ctx context.Context, agentPoolID string, option
7852
return nil, err
7953
}
8054

81-
agentPoolToken := &AgentPoolToken{}
55+
agentPoolToken := &AccessToken{}
8256
err = s.client.do(ctx, req, agentPoolToken)
8357
if err != nil {
8458
return nil, err

agent_pool_token_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ func TestAgentPoolTokenList(t *testing.T) {
2020
defer aptCleanup()
2121

2222
t.Run("with valid agent pool", func(t *testing.T) {
23-
tList, err := client.AgentPoolTokens.List(ctx, ap.ID, AgentPoolTokenListOptions{})
23+
tList, err := client.AgentPoolTokens.List(ctx, ap.ID, AccessTokenListOptions{})
2424
require.NoError(t, err)
2525
assert.Len(t, tList.Items, 1)
2626
assert.Equal(t, tList.Items[0].ID, apt.ID)
2727
})
2828
t.Run("with nonexistent agent pool", func(t *testing.T) {
29-
_, err := client.AgentPoolTokens.List(ctx, "ap-123", AgentPoolTokenListOptions{})
29+
_, err := client.AgentPoolTokens.List(ctx, "ap-123", AccessTokenListOptions{})
3030
assert.Equal(
3131
t,
3232
ResourceNotFoundError{
@@ -45,15 +45,15 @@ func TestAgentPoolTokenCreate(t *testing.T) {
4545
defer apCleanup()
4646

4747
t.Run("when description is provided", func(t *testing.T) {
48-
options := AgentPoolTokenCreateOptions{
48+
options := AccessTokenCreateOptions{
4949
Description: String("provider tests token"),
5050
}
5151

5252
apToken, err := client.AgentPoolTokens.Create(ctx, ap.ID, options)
5353
require.NoError(t, err)
5454

5555
// Get a refreshed view from the API.
56-
aptList, err := client.AgentPoolTokens.List(ctx, ap.ID, AgentPoolTokenListOptions{})
56+
aptList, err := client.AgentPoolTokens.List(ctx, ap.ID, AccessTokenListOptions{})
5757
require.NoError(t, err)
5858

5959
refreshed := aptList.Items[0]
@@ -66,12 +66,12 @@ func TestAgentPoolTokenCreate(t *testing.T) {
6666
})
6767

6868
t.Run("when description is not provided", func(t *testing.T) {
69-
options := AgentPoolTokenCreateOptions{}
69+
options := AccessTokenCreateOptions{}
7070
apToken, err := client.AgentPoolTokens.Create(ctx, ap.ID, options)
7171
require.NoError(t, err)
7272

7373
// Get a refreshed view from the API.
74-
aptList, err := client.AgentPoolTokens.List(ctx, ap.ID, AgentPoolTokenListOptions{})
74+
aptList, err := client.AgentPoolTokens.List(ctx, ap.ID, AccessTokenListOptions{})
7575
require.NoError(t, err)
7676

7777
refreshed := aptList.Items[0]
@@ -85,7 +85,7 @@ func TestAgentPoolTokenCreate(t *testing.T) {
8585

8686
t.Run("with nonexistent pool id", func(t *testing.T) {
8787
var apID = "ap-234"
88-
_, err := client.AgentPoolTokens.Create(ctx, apID, AgentPoolTokenCreateOptions{})
88+
_, err := client.AgentPoolTokens.Create(ctx, apID, AccessTokenCreateOptions{})
8989
assert.Equal(
9090
t,
9191
ResourceNotFoundError{
@@ -97,7 +97,7 @@ func TestAgentPoolTokenCreate(t *testing.T) {
9797

9898
t.Run("with invalid pool id", func(t *testing.T) {
9999
apID := badIdentifier
100-
ap, err := client.AgentPoolTokens.Create(ctx, apID, AgentPoolTokenCreateOptions{})
100+
ap, err := client.AgentPoolTokens.Create(ctx, apID, AccessTokenCreateOptions{})
101101
assert.Nil(t, ap)
102102
assert.EqualError(t, err, fmt.Sprintf("invalid value for agent pool ID: '%s'", apID))
103103

helper_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ func createAgentPool(t *testing.T, client *Client) (*AgentPool, func()) {
6363
}
6464
}
6565

66-
func createAgentPoolToken(t *testing.T, client *Client, poolID string) (*AgentPoolToken, func()) {
66+
func createAgentPoolToken(t *testing.T, client *Client, poolID string) (*AccessToken, func()) {
6767
ctx := context.Background()
68-
apt, err := client.AgentPoolTokens.Create(ctx, poolID, AgentPoolTokenCreateOptions{Description: String("provider test token")})
68+
apt, err := client.AgentPoolTokens.Create(ctx, poolID, AccessTokenCreateOptions{Description: String("provider test token")})
6969
if err != nil {
7070
t.Fatal(err)
7171
}
@@ -442,6 +442,24 @@ func createServiceAccount(
442442
}
443443
}
444444

445+
func createServiceAccountToken(t *testing.T, client *Client, serviceAccountID string) (*AccessToken, func()) {
446+
ctx := context.Background()
447+
sat, err := client.ServiceAccountTokens.Create(
448+
ctx, serviceAccountID, AccessTokenCreateOptions{Description: String("tst-description-" + randomString(t))},
449+
)
450+
if err != nil {
451+
t.Fatal(err)
452+
}
453+
454+
return sat, func() {
455+
if err := client.AccessTokens.Delete(ctx, sat.ID); err != nil {
456+
t.Errorf("Error destroying service account token! WARNING: Dangling resources\n"+
457+
"may exist! The full error is shown below.\n\n"+
458+
"Service account token: %s\nError: %s", sat.ID, err)
459+
}
460+
}
461+
}
462+
445463
func assignTagsToWorkspace(t *testing.T, client *Client, workspace *Workspace, tags []*Tag) {
446464
ctx := context.Background()
447465
tagRels := make([]*TagRelation, len(tags))

scalr.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ type Client struct {
136136
Roles Roles
137137
RunTriggers RunTriggers
138138
Runs Runs
139+
ServiceAccountTokens ServiceAccountTokens
139140
ServiceAccounts ServiceAccounts
140141
Tags Tags
141142
Teams Teams
@@ -232,6 +233,7 @@ func NewClient(cfg *Config) (*Client, error) {
232233
client.Roles = &roles{client: client}
233234
client.RunTriggers = &runTriggers{client: client}
234235
client.Runs = &runs{client: client}
236+
client.ServiceAccountTokens = &serviceAccountTokens{client: client}
235237
client.ServiceAccounts = &serviceAccounts{client: client}
236238
client.Tags = &tags{client: client}
237239
client.Teams = &teams{client: client}

service_account_token.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package scalr
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/url"
8+
)
9+
10+
// Compile-time proof of interface implementation.
11+
var _ ServiceAccountTokens = (*serviceAccountTokens)(nil)
12+
13+
// ServiceAccountTokens describes all the access token related methods that the
14+
// Scalr IACP API supports.
15+
type ServiceAccountTokens interface {
16+
// List service account's access tokens
17+
List(ctx context.Context, serviceAccountID string, options AccessTokenListOptions) (*AccessTokenList, error)
18+
// Create new access token for service account
19+
Create(ctx context.Context, serviceAccountID string, options AccessTokenCreateOptions) (*AccessToken, error)
20+
}
21+
22+
// serviceAccountTokens implements ServiceAccountTokens.
23+
type serviceAccountTokens struct {
24+
client *Client
25+
}
26+
27+
// List the access tokens of ServiceAccount.
28+
func (s *serviceAccountTokens) List(
29+
ctx context.Context, serviceAccountID string, options AccessTokenListOptions,
30+
) (*AccessTokenList, error) {
31+
req, err := s.client.newRequest(
32+
"GET",
33+
fmt.Sprintf("service-accounts/%s/access-tokens", url.QueryEscape(serviceAccountID)),
34+
&options,
35+
)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
atl := &AccessTokenList{}
41+
err = s.client.do(ctx, req, atl)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
return atl, nil
47+
}
48+
49+
// Create is used to create a new AccessToken for ServiceAccount.
50+
func (s *serviceAccountTokens) Create(
51+
ctx context.Context, serviceAccountID string, options AccessTokenCreateOptions,
52+
) (*AccessToken, error) {
53+
54+
// Make sure we don't send a user provided ID.
55+
options.ID = ""
56+
57+
if !validStringID(&serviceAccountID) {
58+
return nil, errors.New("invalid value for service account ID")
59+
}
60+
61+
req, err := s.client.newRequest(
62+
"POST",
63+
fmt.Sprintf("service-accounts/%s/access-tokens", url.QueryEscape(serviceAccountID)),
64+
&options,
65+
)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
at := &AccessToken{}
71+
err = s.client.do(ctx, req, at)
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
return at, nil
77+
}

0 commit comments

Comments
 (0)