From 243a197ed947e620b00fe2db2ac960d4cecd4d20 Mon Sep 17 00:00:00 2001
From: Petro Protsakh
Date: Fri, 29 Jul 2022 11:53:40 +0300
Subject: [PATCH] SCALRCORE-22261 Adjust workspace tags methods
[API_BRANCH]
---
helper_test.go | 15 +++-
tag.go | 45 +++---------
tag_test.go | 56 +++++---------
workspace.go | 5 +-
workspace_tags.go | 66 +++++------------
workspace_tags_test.go | 162 +++++++++++++++++++++++++----------------
6 files changed, 166 insertions(+), 183 deletions(-)
diff --git a/helper_test.go b/helper_test.go
index 438a061..3aec72e 100644
--- a/helper_test.go
+++ b/helper_test.go
@@ -261,7 +261,7 @@ func createVcsProvider(t *testing.T, client *Client, envs []*Environment) (*VcsP
func createTag(t *testing.T, client *Client) (*Tag, func()) {
ctx := context.Background()
tag, err := client.Tags.Create(ctx, TagCreateOptions{
- Name: String("test-role-" + randomString(t)),
+ Name: String("tst-" + randomString(t)),
Account: &Account{ID: defaultAccountID},
})
if err != nil {
@@ -424,3 +424,16 @@ func createProviderConfigurationScalr(t *testing.T, client *Client, providerName
}
}
}
+
+func assignTagsToWorkspace(t *testing.T, client *Client, workspace *Workspace, tags []*Tag) {
+ ctx := context.Background()
+ tagRels := make([]*TagRelation, len(tags))
+ for i, tag := range tags {
+ tagRels[i] = &TagRelation{ID: tag.ID}
+ }
+ err := client.WorkspaceTags.Add(ctx, workspace.ID, tagRels)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/tag.go b/tag.go
index bab28a2..77f1a01 100644
--- a/tag.go
+++ b/tag.go
@@ -16,10 +16,8 @@ type Tags interface {
List(ctx context.Context, options TagListOptions) (*TagList, error)
// Create is used to create a new tag.
Create(ctx context.Context, options TagCreateOptions) (*Tag, error)
- // Read a tag by its account ID and name.
- Read(ctx context.Context, accountID, tagName string) (*Tag, error)
- // ReadByID reads a tag by its ID.
- ReadByID(ctx context.Context, tagID string) (*Tag, error)
+ // Read reads a tag by its ID.
+ Read(ctx context.Context, tagID string) (*Tag, error)
// Update existing tag by its ID.
Update(ctx context.Context, tagID string, options TagUpdateOptions) (*Tag, error)
// Delete deletes a tag by its ID.
@@ -45,12 +43,17 @@ type Tag struct {
Account *Account `jsonapi:"relation,account"`
}
+type TagRelation struct {
+ ID string `jsonapi:"primary,tags"`
+}
+
// TagListOptions represents the options for listing tags.
type TagListOptions struct {
ListOptions
Account *string `url:"filter[account],omitempty"`
- Name *string `url:"query,omitempty"`
+ Name *string `url:"filter[name],omitempty"`
+ Query *string `url:"query,omitempty"`
}
// TagCreateOptions represents the options for creating a new tag.
@@ -87,36 +90,8 @@ func (s *tags) List(ctx context.Context, options TagListOptions) (*TagList, erro
return tl, nil
}
-// Read tag by account ID and name.
-func (s *tags) Read(ctx context.Context, accountID, tagName string) (*Tag, error) {
- if !validStringID(&accountID) {
- return nil, errors.New("invalid value for account")
- }
- if !validStringID(&tagName) {
- return nil, errors.New("invalid value for tag")
- }
-
- options := TagListOptions{Account: &accountID, Name: &tagName}
-
- req, err := s.client.newRequest("GET", "tags", &options)
- if err != nil {
- return nil, err
- }
-
- tl := &TagList{}
- err = s.client.do(ctx, req, tl)
- if err != nil {
- return nil, err
- }
- if len(tl.Items) != 1 {
- return nil, errors.New("invalid filters")
- }
-
- return tl.Items[0], nil
-}
-
-// ReadByID reads a tag by its ID.
-func (s *tags) ReadByID(ctx context.Context, tagID string) (*Tag, error) {
+// Read reads a tag by its ID.
+func (s *tags) Read(ctx context.Context, tagID string) (*Tag, error) {
if !validStringID(&tagID) {
return nil, errors.New("invalid value for tag ID")
}
diff --git a/tag_test.go b/tag_test.go
index fe653f0..9301e81 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -21,23 +21,23 @@ func TestTagsList(t *testing.T) {
t.Run("without options", func(t *testing.T) {
tagl, err := client.Tags.List(ctx, TagListOptions{})
require.NoError(t, err)
- taglIDs := make([]string, len(tagl.Items))
- for _, tag := range tagl.Items {
- taglIDs = append(taglIDs, tag.ID)
+ assert.Equal(t, 2, tagl.TotalCount)
+
+ tagIDs := make([]string, len(tagl.Items))
+ for i, tag := range tagl.Items {
+ tagIDs[i] = tag.ID
}
- assert.Contains(t, taglIDs, tagTest1.ID)
- assert.Contains(t, taglIDs, tagTest2.ID)
+ assert.Contains(t, tagIDs, tagTest1.ID)
+ assert.Contains(t, tagIDs, tagTest2.ID)
})
t.Run("with options", func(t *testing.T) {
- tagl, err := client.Tags.List(ctx, TagListOptions{Account: String(defaultAccountID)})
+ tagl, err := client.Tags.List(ctx,
+ TagListOptions{Account: String(defaultAccountID), Name: String(tagTest1.Name)},
+ )
require.NoError(t, err)
- taglIDs := make([]string, len(tagl.Items))
- for _, tag := range tagl.Items {
- taglIDs = append(taglIDs, tag.ID)
- }
- assert.Contains(t, taglIDs, tagTest1.ID)
- assert.Contains(t, taglIDs, tagTest2.ID)
+ assert.Equal(t, 1, tagl.TotalCount)
+ assert.Equal(t, tagTest1.ID, tagl.Items[0].ID)
})
}
@@ -47,7 +47,7 @@ func TestTagsCreate(t *testing.T) {
t.Run("with valid options", func(t *testing.T) {
options := TagCreateOptions{
- Name: String("test-role-" + randomString(t)),
+ Name: String("tst-" + randomString(t)),
Account: &Account{ID: defaultAccountID},
}
@@ -55,7 +55,7 @@ func TestTagsCreate(t *testing.T) {
require.NoError(t, err)
// Get a refreshed view from the API.
- refreshed, err := client.Tags.ReadByID(ctx, tag.ID)
+ refreshed, err := client.Tags.Read(ctx, tag.ID)
require.NoError(t, err)
for _, item := range []*Tag{
@@ -111,40 +111,22 @@ func TestTagsRead(t *testing.T) {
defer tagTestCleanup()
t.Run("by ID when the tag exists", func(t *testing.T) {
- tag, err := client.Tags.ReadByID(ctx, tagTest.ID)
+ tag, err := client.Tags.Read(ctx, tagTest.ID)
require.NoError(t, err)
assert.Equal(t, tagTest.ID, tag.ID)
})
t.Run("by ID when the tag does not exist", func(t *testing.T) {
- tag, err := client.Tags.ReadByID(ctx, "tag-nonexisting")
+ tag, err := client.Tags.Read(ctx, "tag-nonexisting")
assert.Nil(t, tag)
assert.Error(t, err)
})
t.Run("by ID without a valid tag ID", func(t *testing.T) {
- tag, err := client.Tags.ReadByID(ctx, badIdentifier)
+ tag, err := client.Tags.Read(ctx, badIdentifier)
assert.Nil(t, tag)
assert.EqualError(t, err, "invalid value for tag ID")
})
-
- t.Run("by name when the tag exists", func(t *testing.T) {
- tag, err := client.Tags.Read(ctx, defaultAccountID, tagTest.Name)
- require.NoError(t, err)
- assert.Equal(t, tagTest.ID, tag.ID)
- })
-
- t.Run("by name when the tag does not exist", func(t *testing.T) {
- tag, err := client.Tags.Read(ctx, defaultAccountID, "tag-nonexisting")
- assert.Nil(t, tag)
- assert.Error(t, err)
- })
-
- t.Run("by name without a valid account ID", func(t *testing.T) {
- tag, err := client.Tags.Read(ctx, "acc-nonexisting", tagTest.Name)
- assert.Nil(t, tag)
- assert.Error(t, err)
- })
}
func TestTagsUpdate(t *testing.T) {
@@ -163,7 +145,7 @@ func TestTagsUpdate(t *testing.T) {
require.NoError(t, err)
// Get a refreshed view from the API.
- refreshed, err := client.Tags.ReadByID(ctx, tagTest.ID)
+ refreshed, err := client.Tags.Read(ctx, tagTest.ID)
require.NoError(t, err)
for _, item := range []*Tag{
@@ -193,7 +175,7 @@ func TestTagsDelete(t *testing.T) {
err := client.Tags.Delete(ctx, tagTest.ID)
require.NoError(t, err)
- _, err = client.Tags.ReadByID(ctx, tagTest.ID)
+ _, err = client.Tags.Read(ctx, tagTest.ID)
assert.Equal(
t,
ResourceNotFoundError{
diff --git a/workspace.go b/workspace.go
index a397d04..e211946 100644
--- a/workspace.go
+++ b/workspace.go
@@ -86,7 +86,7 @@ type Workspace struct {
VcsProvider *VcsProvider `jsonapi:"relation,vcs-provider"`
AgentPool *AgentPool `jsonapi:"relation,agent-pool"`
ModuleVersion *ModuleVersion `jsonapi:"relation,module-version,omitempty"`
- Tags []*Tag `jsonapi:"relation,tags,omitempty"`
+ Tags []*Tag `jsonapi:"relation,tags"`
}
// Hooks contains the custom hooks field.
@@ -211,6 +211,9 @@ type WorkspaceCreateOptions struct {
// Specifies the number of minutes run operation can be executed before termination.
RunOperationTimeout *int `jsonapi:"attr,run-operation-timeout"`
+
+ // Specifies tags assigned to the workspace
+ Tags []*Tag `jsonapi:"relation,tags,omitempty"`
}
// WorkspaceVCSRepoOptions represents the configuration options of a VCS integration.
diff --git a/workspace_tags.go b/workspace_tags.go
index b45e357..f2fbd99 100644
--- a/workspace_tags.go
+++ b/workspace_tags.go
@@ -2,7 +2,6 @@ package scalr
import (
"context"
- "errors"
"fmt"
"net/url"
)
@@ -13,8 +12,9 @@ var _ WorkspaceTags = (*workspaceTag)(nil)
// WorkspaceTags describes all the workspace tags related methods that the
// Scalr API supports.
type WorkspaceTags interface {
- Create(ctx context.Context, options WorkspaceTagsCreateOptions) error
- Update(ctx context.Context, options WorkspaceTagsUpdateOptions) error
+ Add(ctx context.Context, wsID string, tags []*TagRelation) error
+ Replace(ctx context.Context, wsID string, tags []*TagRelation) error
+ Delete(ctx context.Context, wsID string, tags []*TagRelation) error
}
// workspaceTag implements WorkspaceTags.
@@ -22,47 +22,21 @@ type workspaceTag struct {
client *Client
}
-// WorkspaceTag represents a single workspace tag relation.
-type WorkspaceTag struct {
- ID string `jsonapi:"primary,tags"`
-}
-
-// WorkspaceTagsCreateOptions represents options for adding tags to a workspace.
-type WorkspaceTagsCreateOptions struct {
- WorkspaceID string
- WorkspaceTags []*WorkspaceTag
-}
-
-// WorkspaceTagsUpdateOptions represents options for updating tags in a workspace.
-type WorkspaceTagsUpdateOptions struct {
- WorkspaceID string
- WorkspaceTags []*WorkspaceTag
-}
-
-func (o WorkspaceTagsCreateOptions) valid() error {
- if !validStringID(&o.WorkspaceID) {
- return errors.New("invalid value for workspace ID")
- }
- if o.WorkspaceTags == nil || len(o.WorkspaceTags) < 1 {
- return errors.New("list of tags is required")
+// Add tags to the workspace
+func (s *workspaceTag) Add(ctx context.Context, wsID string, trs []*TagRelation) error {
+ u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(wsID))
+ req, err := s.client.newRequest("POST", u, trs)
+ if err != nil {
+ return err
}
- return nil
-}
-func (o WorkspaceTagsUpdateOptions) valid() error {
- if !validStringID(&o.WorkspaceID) {
- return errors.New("invalid value for workspace ID")
- }
- return nil
+ return s.client.do(ctx, req, nil)
}
-// Create is used for adding tags to the workspace.
-func (s *workspaceTag) Create(ctx context.Context, options WorkspaceTagsCreateOptions) error {
- if err := options.valid(); err != nil {
- return err
- }
- u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(options.WorkspaceID))
- req, err := s.client.newRequest("POST", u, options.WorkspaceTags)
+// Replace workspace's tags
+func (s *workspaceTag) Replace(ctx context.Context, wsID string, trs []*TagRelation) error {
+ u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(wsID))
+ req, err := s.client.newRequest("PATCH", u, trs)
if err != nil {
return err
}
@@ -70,14 +44,10 @@ func (s *workspaceTag) Create(ctx context.Context, options WorkspaceTagsCreateOp
return s.client.do(ctx, req, nil)
}
-// Update is used for tags replacement in the workspace.
-func (s *workspaceTag) Update(ctx context.Context, options WorkspaceTagsUpdateOptions) error {
- if err := options.valid(); err != nil {
- return err
- }
-
- u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(options.WorkspaceID))
- req, err := s.client.newRequest("PATCH", u, options.WorkspaceTags)
+// Delete workspace's tags
+func (s *workspaceTag) Delete(ctx context.Context, wsID string, trs []*TagRelation) error {
+ u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(wsID))
+ req, err := s.client.newRequest("DELETE", u, trs)
if err != nil {
return err
}
diff --git a/workspace_tags_test.go b/workspace_tags_test.go
index 6a2f788..31ea87e 100644
--- a/workspace_tags_test.go
+++ b/workspace_tags_test.go
@@ -8,112 +8,113 @@ import (
"testing"
)
-func TestWorkspaceTagsCreate(t *testing.T) {
+func TestWorkspaceTagsAdd(t *testing.T) {
client := testClient(t)
ctx := context.Background()
- environment, deleteEnvironment := createEnvironment(t, client)
- defer deleteEnvironment()
-
- workspace, deleteWorkspace := createWorkspace(t, client, environment)
+ workspace, deleteWorkspace := createWorkspace(t, client, nil)
defer deleteWorkspace()
- tag, deleteTag := createTag(t, client)
- defer deleteTag()
+ tag1, deleteTag1 := createTag(t, client)
+ defer deleteTag1()
+ tag2, deleteTag2 := createTag(t, client)
+ defer deleteTag2()
+ tag3, deleteTag3 := createTag(t, client)
+ defer deleteTag3()
t.Run("with valid options", func(t *testing.T) {
- options := WorkspaceTagsCreateOptions{
- WorkspaceID: workspace.ID,
- WorkspaceTags: []*WorkspaceTag{{ID: tag.ID}},
- }
-
- err := client.WorkspaceTags.Create(ctx, options)
+ err := client.WorkspaceTags.Add(ctx, workspace.ID,
+ []*TagRelation{
+ {ID: tag1.ID},
+ {ID: tag2.ID},
+ },
+ )
require.NoError(t, err)
// Get a refreshed view from the API.
refreshed, err := client.Workspaces.ReadByID(ctx, workspace.ID)
require.NoError(t, err)
+ assert.Len(t, refreshed.Tags, 2)
- for _, item := range refreshed.Tags {
- assert.Equal(t, tag.ID, item.ID)
+ tagIDs := make([]string, len(refreshed.Tags))
+ for _, tag := range refreshed.Tags {
+ tagIDs = append(tagIDs, tag.ID)
}
+ assert.Contains(t, tagIDs, tag1.ID)
+ assert.Contains(t, tagIDs, tag2.ID)
})
- t.Run("without valid workspace ID", func(t *testing.T) {
- err := client.WorkspaceTags.Create(ctx, WorkspaceTagsCreateOptions{
- WorkspaceTags: []*WorkspaceTag{{ID: tag.ID}},
- })
- assert.EqualError(t, err, "invalid value for workspace ID")
- })
+ t.Run("add another one", func(t *testing.T) {
+ err := client.WorkspaceTags.Add(ctx, workspace.ID, []*TagRelation{{ID: tag3.ID}})
+ require.NoError(t, err)
- t.Run("without valid workspace tags", func(t *testing.T) {
- err := client.WorkspaceTags.Create(ctx, WorkspaceTagsCreateOptions{
- WorkspaceID: workspace.ID,
- })
- assert.EqualError(t, err, "list of tags is required")
+ // Get a refreshed view from the API.
+ refreshed, err := client.Workspaces.ReadByID(ctx, workspace.ID)
+ require.NoError(t, err)
+ assert.Len(t, refreshed.Tags, 3)
+
+ tagIDs := make([]string, len(refreshed.Tags))
+ for _, tag := range refreshed.Tags {
+ tagIDs = append(tagIDs, tag.ID)
+ }
+ assert.Contains(t, tagIDs, tag1.ID)
+ assert.Contains(t, tagIDs, tag2.ID)
+ assert.Contains(t, tagIDs, tag3.ID)
})
- t.Run("when options have an invalid tag", func(t *testing.T) {
+ t.Run("with invalid tag", func(t *testing.T) {
tagID := "tag-invalid-id"
- err := client.WorkspaceTags.Create(ctx, WorkspaceTagsCreateOptions{
- WorkspaceID: workspace.ID,
- WorkspaceTags: []*WorkspaceTag{{ID: tagID}},
- })
+ err := client.WorkspaceTags.Add(ctx, workspace.ID, []*TagRelation{{ID: tagID}})
assert.EqualError(t, err, fmt.Sprintf("Not Found\n\nTag with ID '%s' not found or user unauthorized.", tagID))
})
}
-func TestWorkspaceTagsUpdate(t *testing.T) {
+func TestWorkspaceTagsReplace(t *testing.T) {
client := testClient(t)
ctx := context.Background()
- environment, deleteEnvironment := createEnvironment(t, client)
- defer deleteEnvironment()
-
- workspace, deleteWorkspace := createWorkspace(t, client, environment)
+ workspace, deleteWorkspace := createWorkspace(t, client, nil)
defer deleteWorkspace()
- tag, deleteTag := createTag(t, client)
- defer deleteTag()
+ tag1, deleteTag1 := createTag(t, client)
+ defer deleteTag1()
+ tag2, deleteTag2 := createTag(t, client)
+ defer deleteTag2()
+ tag3, deleteTag3 := createTag(t, client)
+ defer deleteTag3()
- t.Run("with valid options", func(t *testing.T) {
- options := WorkspaceTagsUpdateOptions{
- WorkspaceID: workspace.ID,
- WorkspaceTags: []*WorkspaceTag{{ID: tag.ID}},
- }
+ assignTagsToWorkspace(t, client, workspace, []*Tag{tag1})
- err := client.WorkspaceTags.Update(ctx, options)
+ t.Run("with valid options", func(t *testing.T) {
+ err := client.WorkspaceTags.Replace(ctx, workspace.ID,
+ []*TagRelation{
+ {ID: tag2.ID},
+ {ID: tag3.ID},
+ },
+ )
require.NoError(t, err)
// Get a refreshed view from the API.
refreshed, err := client.Workspaces.ReadByID(ctx, workspace.ID)
require.NoError(t, err)
+ assert.Len(t, refreshed.Tags, 2)
- for _, item := range refreshed.Tags {
- assert.Equal(t, tag.ID, item.ID)
+ tagIDs := make([]string, len(refreshed.Tags))
+ for _, tag := range refreshed.Tags {
+ tagIDs = append(tagIDs, tag.ID)
}
+ assert.Contains(t, tagIDs, tag2.ID)
+ assert.Contains(t, tagIDs, tag3.ID)
})
- t.Run("without valid workspace ID", func(t *testing.T) {
- err := client.WorkspaceTags.Update(ctx, WorkspaceTagsUpdateOptions{})
- assert.EqualError(t, err, "invalid value for workspace ID")
- })
-
- t.Run("with invalid workspace tag", func(t *testing.T) {
+ t.Run("with invalid tag", func(t *testing.T) {
tagID := "tag-invalid-id"
- options := WorkspaceTagsUpdateOptions{
- WorkspaceID: workspace.ID,
- WorkspaceTags: []*WorkspaceTag{{ID: tagID}},
- }
-
- err := client.WorkspaceTags.Update(ctx, options)
+ err := client.WorkspaceTags.Replace(ctx, workspace.ID, []*TagRelation{{ID: tagID}})
assert.EqualError(t, err, fmt.Sprintf("Not Found\n\nTag with ID '%s' not found or user unauthorized.", tagID))
})
t.Run("when all tags should be removed", func(t *testing.T) {
- err := client.WorkspaceTags.Update(ctx, WorkspaceTagsUpdateOptions{
- WorkspaceID: workspace.ID,
- })
+ err := client.WorkspaceTags.Replace(ctx, workspace.ID, make([]*TagRelation, 0))
require.NoError(t, err)
// Get a refreshed view from the API.
@@ -122,3 +123,42 @@ func TestWorkspaceTagsUpdate(t *testing.T) {
assert.Empty(t, refreshed.Tags)
})
}
+
+func TestWorkspaceTagsDelete(t *testing.T) {
+ client := testClient(t)
+ ctx := context.Background()
+
+ workspace, deleteWorkspace := createWorkspace(t, client, nil)
+ defer deleteWorkspace()
+
+ tag1, deleteTag1 := createTag(t, client)
+ defer deleteTag1()
+ tag2, deleteTag2 := createTag(t, client)
+ defer deleteTag2()
+ tag3, deleteTag3 := createTag(t, client)
+ defer deleteTag3()
+
+ assignTagsToWorkspace(t, client, workspace, []*Tag{tag1, tag2, tag3})
+
+ t.Run("with valid options", func(t *testing.T) {
+ err := client.WorkspaceTags.Delete(ctx, workspace.ID,
+ []*TagRelation{
+ {ID: tag1.ID},
+ {ID: tag2.ID},
+ },
+ )
+ require.NoError(t, err)
+
+ // Get a refreshed view from the API.
+ refreshed, err := client.Workspaces.ReadByID(ctx, workspace.ID)
+ require.NoError(t, err)
+ assert.Len(t, refreshed.Tags, 1)
+ assert.Equal(t, tag3.ID, refreshed.Tags[0].ID)
+ })
+
+ t.Run("with invalid tag", func(t *testing.T) {
+ tagID := "tag-invalid-id"
+ err := client.WorkspaceTags.Replace(ctx, workspace.ID, []*TagRelation{{ID: tagID}})
+ assert.EqualError(t, err, fmt.Sprintf("Not Found\n\nTag with ID '%s' not found or user unauthorized.", tagID))
+ })
+}