Skip to content

Commit

Permalink
Merge pull request #93 from Scalr/feature/SCALRCORE-21783
Browse files Browse the repository at this point in the history
SCALRCORE-21783 Scalr Provider > Token generation
  • Loading branch information
emocharnik committed Jan 17, 2023
2 parents 6329293 + acdac16 commit a8d77cb
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Default global owners
* v.mihun@scalr.com p.protsakh@scalr.com
45 changes: 39 additions & 6 deletions access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var _ AccessTokens = (*accessTokens)(nil)
// AccessTokens describes all the access token related methods that the
// Scalr IACP API supports.
type AccessTokens interface {
Read(ctx context.Context, accessTokenID string) (*AccessToken, error)
Update(ctx context.Context, accessTokenID string, options AccessTokenUpdateOptions) (*AccessToken, error)
Delete(ctx context.Context, accessTokenID string) error
}
Expand All @@ -37,10 +38,46 @@ type AccessToken struct {
Token string `jsonapi:"attr,token"`
}

// AccessTokenListOptions represents the options for listing access tokens.
type AccessTokenListOptions struct {
ListOptions
}

// AccessTokenCreateOptions represents the options for creating a new AccessToken.
type AccessTokenCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,access-tokens"`

Description *string `jsonapi:"attr,description,omitempty"`
}

// AccessTokenUpdateOptions represents the options for updating an AccessToken.
type AccessTokenUpdateOptions struct {
ID string `jsonapi:"primary,access-tokens"`
Description *string `jsonapi:"attr,description"`
// For internal use only!
ID string `jsonapi:"primary,access-tokens"`

Description *string `jsonapi:"attr,description,omitempty"`
}

// Read access token by its ID
func (s *accessTokens) Read(ctx context.Context, accessTokenID string) (*AccessToken, error) {
if !validStringID(&accessTokenID) {
return nil, errors.New("invalid value for access token ID")
}

u := fmt.Sprintf("access-tokens/%s", url.QueryEscape(accessTokenID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}

at := &AccessToken{}
err = s.client.do(ctx, req, at)
if err != nil {
return nil, err
}

return at, nil
}

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

if !validString(options.Description) {
return nil, errors.New("value for description must be a valid string")
}

req, err := s.client.newRequest("PATCH", fmt.Sprintf("access-tokens/%s", url.QueryEscape(accessTokenID)), &options)
if err != nil {
return nil, err
Expand Down
29 changes: 28 additions & 1 deletion access_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,33 @@ import (
"github.com/stretchr/testify/require"
)

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

ap, apCleanup := createAgentPool(t, client)
defer apCleanup()

atTest, atTestCleanup := createAgentPoolToken(t, client, ap.ID)
defer atTestCleanup()

t.Run("when the token exists", func(t *testing.T) {
at, err := client.AccessTokens.Read(ctx, atTest.ID)
require.NoError(t, err)
assert.Equal(t, atTest.ID, at.ID)
})

t.Run("when the token does not exist", func(t *testing.T) {
_, err := client.AccessTokens.Read(ctx, "at-nonexisting")
assert.Error(t, err)
})

t.Run("with invalid token ID", func(t *testing.T) {
_, err := client.AccessTokens.Read(ctx, badIdentifier)
assert.EqualError(t, err, "invalid value for access token ID")
})
}

func TestAccessTokenUpdate(t *testing.T) {
client := testClient(t)
ctx := context.Background()
Expand Down Expand Up @@ -57,7 +84,7 @@ func TestAccessTokenDelete(t *testing.T) {
err := client.AccessTokens.Delete(ctx, apt.ID)
require.NoError(t, err)

l, err := client.AgentPoolTokens.List(ctx, ap.ID, AgentPoolTokenListOptions{})
l, err := client.AgentPoolTokens.List(ctx, ap.ID, AccessTokenListOptions{})
assert.Len(t, l.Items, 0)
})

Expand Down
42 changes: 8 additions & 34 deletions agent_pool_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/url"
"time"
)

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

// agentPoolTokens implements AgentPoolTokens.
type agentPoolTokens struct {
client *Client
}

// AgentPoolTokenList represents a list of agent pools.
type AgentPoolTokenList struct {
*Pagination
Items []*AgentPoolToken
}

// AgentPoolToken represents a Scalr agent pool.
type AgentPoolToken struct {
ID string `jsonapi:"primary,access-tokens"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Description string `jsonapi:"attr,description"`
Token string `jsonapi:"attr,token"`
}

// AgentPoolTokenCreateOptions represents the options for creating a new AgentPoolToken.
type AgentPoolTokenListOptions struct {
ListOptions
}

// AgentPoolTokenCreateOptions represents the options for creating a new AgentPoolToken.
type AgentPoolTokenCreateOptions struct {
ID string `jsonapi:"primary,access-tokens"`
Description *string `jsonapi:"attr,description,omitempty"`
}

// List all the agent pools.
func (s *agentPoolTokens) List(ctx context.Context, agentPoolID string, options AgentPoolTokenListOptions) (*AgentPoolTokenList, error) {
// List all the agent pool's tokens.
func (s *agentPoolTokens) List(ctx context.Context, agentPoolID string, options AccessTokenListOptions) (*AccessTokenList, error) {
req, err := s.client.newRequest("GET", fmt.Sprintf("agent-pools/%s/access-tokens", url.QueryEscape(agentPoolID)), &options)
if err != nil {
return nil, err
}

tl := &AgentPoolTokenList{}
tl := &AccessTokenList{}
err = s.client.do(ctx, req, tl)
if err != nil {
return nil, err
Expand All @@ -63,8 +37,8 @@ func (s *agentPoolTokens) List(ctx context.Context, agentPoolID string, options
return tl, nil
}

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

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

agentPoolToken := &AgentPoolToken{}
agentPoolToken := &AccessToken{}
err = s.client.do(ctx, req, agentPoolToken)
if err != nil {
return nil, err
Expand Down
16 changes: 8 additions & 8 deletions agent_pool_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ func TestAgentPoolTokenList(t *testing.T) {
defer aptCleanup()

t.Run("with valid agent pool", func(t *testing.T) {
tList, err := client.AgentPoolTokens.List(ctx, ap.ID, AgentPoolTokenListOptions{})
tList, err := client.AgentPoolTokens.List(ctx, ap.ID, AccessTokenListOptions{})
require.NoError(t, err)
assert.Len(t, tList.Items, 1)
assert.Equal(t, tList.Items[0].ID, apt.ID)
})
t.Run("with nonexistent agent pool", func(t *testing.T) {
_, err := client.AgentPoolTokens.List(ctx, "ap-123", AgentPoolTokenListOptions{})
_, err := client.AgentPoolTokens.List(ctx, "ap-123", AccessTokenListOptions{})
assert.Equal(
t,
ResourceNotFoundError{
Expand All @@ -45,15 +45,15 @@ func TestAgentPoolTokenCreate(t *testing.T) {
defer apCleanup()

t.Run("when description is provided", func(t *testing.T) {
options := AgentPoolTokenCreateOptions{
options := AccessTokenCreateOptions{
Description: String("provider tests token"),
}

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

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

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

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

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

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

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

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

Expand Down
22 changes: 20 additions & 2 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ func createAgentPool(t *testing.T, client *Client) (*AgentPool, func()) {
}
}

func createAgentPoolToken(t *testing.T, client *Client, poolID string) (*AgentPoolToken, func()) {
func createAgentPoolToken(t *testing.T, client *Client, poolID string) (*AccessToken, func()) {
ctx := context.Background()
apt, err := client.AgentPoolTokens.Create(ctx, poolID, AgentPoolTokenCreateOptions{Description: String("provider test token")})
apt, err := client.AgentPoolTokens.Create(ctx, poolID, AccessTokenCreateOptions{Description: String("provider test token")})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -442,6 +442,24 @@ func createServiceAccount(
}
}

func createServiceAccountToken(t *testing.T, client *Client, serviceAccountID string) (*AccessToken, func()) {
ctx := context.Background()
sat, err := client.ServiceAccountTokens.Create(
ctx, serviceAccountID, AccessTokenCreateOptions{Description: String("tst-description-" + randomString(t))},
)
if err != nil {
t.Fatal(err)
}

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

func assignTagsToWorkspace(t *testing.T, client *Client, workspace *Workspace, tags []*Tag) {
ctx := context.Background()
tagRels := make([]*TagRelation, len(tags))
Expand Down
2 changes: 2 additions & 0 deletions scalr.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ type Client struct {
Roles Roles
RunTriggers RunTriggers
Runs Runs
ServiceAccountTokens ServiceAccountTokens
ServiceAccounts ServiceAccounts
Tags Tags
Teams Teams
Expand Down Expand Up @@ -232,6 +233,7 @@ func NewClient(cfg *Config) (*Client, error) {
client.Roles = &roles{client: client}
client.RunTriggers = &runTriggers{client: client}
client.Runs = &runs{client: client}
client.ServiceAccountTokens = &serviceAccountTokens{client: client}
client.ServiceAccounts = &serviceAccounts{client: client}
client.Tags = &tags{client: client}
client.Teams = &teams{client: client}
Expand Down
77 changes: 77 additions & 0 deletions service_account_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package scalr

import (
"context"
"errors"
"fmt"
"net/url"
)

// Compile-time proof of interface implementation.
var _ ServiceAccountTokens = (*serviceAccountTokens)(nil)

// ServiceAccountTokens describes all the access token related methods that the
// Scalr IACP API supports.
type ServiceAccountTokens interface {
// List service account's access tokens
List(ctx context.Context, serviceAccountID string, options AccessTokenListOptions) (*AccessTokenList, error)
// Create new access token for service account
Create(ctx context.Context, serviceAccountID string, options AccessTokenCreateOptions) (*AccessToken, error)
}

// serviceAccountTokens implements ServiceAccountTokens.
type serviceAccountTokens struct {
client *Client
}

// List the access tokens of ServiceAccount.
func (s *serviceAccountTokens) List(
ctx context.Context, serviceAccountID string, options AccessTokenListOptions,
) (*AccessTokenList, error) {
req, err := s.client.newRequest(
"GET",
fmt.Sprintf("service-accounts/%s/access-tokens", url.QueryEscape(serviceAccountID)),
&options,
)
if err != nil {
return nil, err
}

atl := &AccessTokenList{}
err = s.client.do(ctx, req, atl)
if err != nil {
return nil, err
}

return atl, nil
}

// Create is used to create a new AccessToken for ServiceAccount.
func (s *serviceAccountTokens) Create(
ctx context.Context, serviceAccountID string, options AccessTokenCreateOptions,
) (*AccessToken, error) {

// Make sure we don't send a user provided ID.
options.ID = ""

if !validStringID(&serviceAccountID) {
return nil, errors.New("invalid value for service account ID")
}

req, err := s.client.newRequest(
"POST",
fmt.Sprintf("service-accounts/%s/access-tokens", url.QueryEscape(serviceAccountID)),
&options,
)
if err != nil {
return nil, err
}

at := &AccessToken{}
err = s.client.do(ctx, req, at)
if err != nil {
return nil, err
}

return at, nil
}
Loading

0 comments on commit a8d77cb

Please sign in to comment.