Skip to content

Commit 1318444

Browse files
authored
Merge pull request #995 from hashicorp/TF-21287-patch-endpoint-for-adding-but-never-replacing-tag-bindings2
#991 was missing from release
2 parents 0385759 + 61821ca commit 1318444

File tree

7 files changed

+248
-4
lines changed

7 files changed

+248
-4
lines changed

errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ var (
382382

383383
ErrRequiredRegistryModule = errors.New("registry module is required")
384384

385+
ErrRequiredTagBindings = errors.New("TagBindings are required")
386+
385387
ErrInvalidTestRunID = errors.New("invalid value for test run id")
386388

387389
ErrTerraformVersionValidForPlanOnly = errors.New("setting terraform-version is only valid when plan-only is set to true")

mocks/project_mocks.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mocks/workspace_mocks.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

project.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ type Projects interface {
3434

3535
// ListTagBindings lists all tag bindings associated with the project.
3636
ListTagBindings(ctx context.Context, projectID string) ([]*TagBinding, error)
37+
38+
// AddTagBindings adds or modifies the value of existing tag binding keys for a project.
39+
AddTagBindings(ctx context.Context, projectID string, options ProjectAddTagBindingsOptions) ([]*TagBinding, error)
3740
}
3841

3942
// projects implements Projects
@@ -113,6 +116,12 @@ type ProjectUpdateOptions struct {
113116
TagBindings []*TagBinding `jsonapi:"relation,tag-bindings,omitempty"`
114117
}
115118

119+
// ProjectAddTagBindingsOptions represents the options for adding tag bindings
120+
// to a project.
121+
type ProjectAddTagBindingsOptions struct {
122+
TagBindings []*TagBinding
123+
}
124+
116125
// List all projects.
117126
func (s *projects) List(ctx context.Context, organization string, options *ProjectListOptions) (*ProjectList, error) {
118127
if !validStringID(&organization) {
@@ -209,6 +218,31 @@ func (s *projects) ListTagBindings(ctx context.Context, projectID string) ([]*Ta
209218
return list.Items, nil
210219
}
211220

221+
// AddTagBindings adds or modifies the value of existing tag binding keys for a project
222+
func (s *projects) AddTagBindings(ctx context.Context, projectID string, options ProjectAddTagBindingsOptions) ([]*TagBinding, error) {
223+
if !validStringID(&projectID) {
224+
return nil, ErrInvalidProjectID
225+
}
226+
227+
if err := options.valid(); err != nil {
228+
return nil, err
229+
}
230+
231+
u := fmt.Sprintf("projects/%s/tag-bindings", url.PathEscape(projectID))
232+
req, err := s.client.NewRequest("PATCH", u, options.TagBindings)
233+
if err != nil {
234+
return nil, err
235+
}
236+
237+
var response = struct {
238+
*Pagination
239+
Items []*TagBinding
240+
}{}
241+
err = req.Do(ctx, &response)
242+
243+
return response.Items, err
244+
}
245+
212246
// Update a project by its ID
213247
func (s *projects) Update(ctx context.Context, projectID string, options ProjectUpdateOptions) (*Project, error) {
214248
if !validStringID(&projectID) {
@@ -259,3 +293,11 @@ func (o ProjectCreateOptions) valid() error {
259293
func (o ProjectUpdateOptions) valid() error {
260294
return nil
261295
}
296+
297+
func (o ProjectAddTagBindingsOptions) valid() error {
298+
if len(o.TagBindings) == 0 {
299+
return ErrRequiredTagBindings
300+
}
301+
302+
return nil
303+
}

projects_integration_test.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,22 @@ func TestProjectsList(t *testing.T) {
6666
t.Run("when using a tags filter", func(t *testing.T) {
6767
skipUnlessBeta(t)
6868

69-
p1, wTestCleanup1 := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{
69+
p1, pTestCleanup1 := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{
7070
Name: randomStringWithoutSpecialChar(t),
7171
TagBindings: []*TagBinding{
7272
{Key: "key1", Value: "value1"},
7373
{Key: "key2", Value: "value2a"},
7474
},
7575
})
76-
p2, wTestCleanup2 := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{
76+
p2, pTestCleanup2 := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{
7777
Name: randomStringWithoutSpecialChar(t),
7878
TagBindings: []*TagBinding{
7979
{Key: "key2", Value: "value2b"},
8080
{Key: "key3", Value: "value3"},
8181
},
8282
})
83-
t.Cleanup(wTestCleanup1)
84-
t.Cleanup(wTestCleanup2)
83+
t.Cleanup(pTestCleanup1)
84+
t.Cleanup(pTestCleanup2)
8585

8686
// List all the workspaces under the given tag
8787
pl, err := client.Projects.List(ctx, orgTest.Name, &ProjectListOptions{
@@ -247,6 +247,70 @@ func TestProjectsUpdate(t *testing.T) {
247247
})
248248
}
249249

250+
func TestProjectsAddTagBindings(t *testing.T) {
251+
skipUnlessBeta(t)
252+
253+
client := testClient(t)
254+
ctx := context.Background()
255+
256+
pTest, wCleanup := createProject(t, client, nil)
257+
t.Cleanup(wCleanup)
258+
259+
t.Run("when adding tag bindings to a project", func(t *testing.T) {
260+
tagBindings := []*TagBinding{
261+
{Key: "foo", Value: "bar"},
262+
{Key: "baz", Value: "qux"},
263+
}
264+
265+
bindings, err := client.Projects.AddTagBindings(ctx, pTest.ID, ProjectAddTagBindingsOptions{
266+
TagBindings: tagBindings,
267+
})
268+
require.NoError(t, err)
269+
270+
assert.Len(t, bindings, 2)
271+
assert.Equal(t, tagBindings[0].Key, bindings[0].Key)
272+
assert.Equal(t, tagBindings[0].Value, bindings[0].Value)
273+
assert.Equal(t, tagBindings[1].Key, bindings[1].Key)
274+
assert.Equal(t, tagBindings[1].Value, bindings[1].Value)
275+
})
276+
277+
t.Run("when adding 26 tags", func(t *testing.T) {
278+
tagBindings := []*TagBinding{
279+
{Key: "alpha"},
280+
{Key: "bravo"},
281+
{Key: "charlie"},
282+
{Key: "delta"},
283+
{Key: "echo"},
284+
{Key: "foxtrot"},
285+
{Key: "golf"},
286+
{Key: "hotel"},
287+
{Key: "india"},
288+
{Key: "juliet"},
289+
{Key: "kilo"},
290+
{Key: "lima"},
291+
{Key: "mike"},
292+
{Key: "november"},
293+
{Key: "oscar"},
294+
{Key: "papa"},
295+
{Key: "quebec"},
296+
{Key: "romeo"},
297+
{Key: "sierra"},
298+
{Key: "tango"},
299+
{Key: "uniform"},
300+
{Key: "victor"},
301+
{Key: "whiskey"},
302+
{Key: "xray"},
303+
{Key: "yankee"},
304+
{Key: "zulu"},
305+
}
306+
307+
_, err := client.Workspaces.AddTagBindings(ctx, pTest.ID, WorkspaceAddTagBindingsOptions{
308+
TagBindings: tagBindings,
309+
})
310+
require.Error(t, err, "cannot exceed 10 bindings per resource")
311+
})
312+
}
313+
250314
func TestProjectsDelete(t *testing.T) {
251315
client := testClient(t)
252316
ctx := context.Background()

workspace.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ type Workspaces interface {
134134

135135
// ListTagBindings lists all tag bindings associated with the workspace.
136136
ListTagBindings(ctx context.Context, workspaceID string) ([]*TagBinding, error)
137+
138+
// AddTagBindings adds or modifies the value of existing tag binding keys for a workspace.
139+
AddTagBindings(ctx context.Context, workspaceID string, options WorkspaceAddTagBindingsOptions) ([]*TagBinding, error)
137140
}
138141

139142
// workspaces implements Workspaces.
@@ -147,6 +150,12 @@ type WorkspaceList struct {
147150
Items []*Workspace
148151
}
149152

153+
// WorkspaceAddTagBindingsOptions represents the options for adding tag bindings
154+
// to a workspace.
155+
type WorkspaceAddTagBindingsOptions struct {
156+
TagBindings []*TagBinding
157+
}
158+
150159
// LockedByChoice is a choice type struct that represents the possible values
151160
// within a polymorphic relation. If a value is available, exactly one field
152161
// will be non-nil.
@@ -760,6 +769,31 @@ func (s *workspaces) ListTagBindings(ctx context.Context, workspaceID string) ([
760769
return list.Items, nil
761770
}
762771

772+
// AddTagBindings adds or modifies the value of existing tag binding keys for a workspace.
773+
func (s *workspaces) AddTagBindings(ctx context.Context, workspaceID string, options WorkspaceAddTagBindingsOptions) ([]*TagBinding, error) {
774+
if !validStringID(&workspaceID) {
775+
return nil, ErrInvalidWorkspaceID
776+
}
777+
778+
if err := options.valid(); err != nil {
779+
return nil, err
780+
}
781+
782+
u := fmt.Sprintf("workspaces/%s/tag-bindings", url.PathEscape(workspaceID))
783+
req, err := s.client.NewRequest("PATCH", u, options.TagBindings)
784+
if err != nil {
785+
return nil, err
786+
}
787+
788+
var response = struct {
789+
*Pagination
790+
Items []*TagBinding
791+
}{}
792+
err = req.Do(ctx, &response)
793+
794+
return response.Items, err
795+
}
796+
763797
// Create is used to create a new workspace.
764798
func (s *workspaces) Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error) {
765799
if !validStringID(&organization) {
@@ -1465,6 +1499,14 @@ func (s *workspaces) DeleteDataRetentionPolicy(ctx context.Context, workspaceID
14651499
return req.Do(ctx, nil)
14661500
}
14671501

1502+
func (o WorkspaceAddTagBindingsOptions) valid() error {
1503+
if len(o.TagBindings) == 0 {
1504+
return ErrRequiredTagBindings
1505+
}
1506+
1507+
return nil
1508+
}
1509+
14681510
func (o WorkspaceCreateOptions) valid() error {
14691511
if !validString(o.Name) {
14701512
return ErrRequiredName

workspace_integration_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,70 @@ func TestWorkspacesReadByID(t *testing.T) {
11731173
})
11741174
}
11751175

1176+
func TestWorkspacesAddTagBindings(t *testing.T) {
1177+
skipUnlessBeta(t)
1178+
1179+
client := testClient(t)
1180+
ctx := context.Background()
1181+
1182+
wTest, wCleanup := createWorkspace(t, client, nil)
1183+
t.Cleanup(wCleanup)
1184+
1185+
t.Run("when adding tag bindings to a workspace", func(t *testing.T) {
1186+
tagBindings := []*TagBinding{
1187+
{Key: "foo", Value: "bar"},
1188+
{Key: "baz", Value: "qux"},
1189+
}
1190+
1191+
bindings, err := client.Workspaces.AddTagBindings(ctx, wTest.ID, WorkspaceAddTagBindingsOptions{
1192+
TagBindings: tagBindings,
1193+
})
1194+
require.NoError(t, err)
1195+
1196+
assert.Len(t, bindings, 2)
1197+
assert.Equal(t, tagBindings[0].Key, bindings[0].Key)
1198+
assert.Equal(t, tagBindings[0].Value, bindings[0].Value)
1199+
assert.Equal(t, tagBindings[1].Key, bindings[1].Key)
1200+
assert.Equal(t, tagBindings[1].Value, bindings[1].Value)
1201+
})
1202+
1203+
t.Run("when adding 26 tags", func(t *testing.T) {
1204+
tagBindings := []*TagBinding{
1205+
{Key: "alpha"},
1206+
{Key: "bravo"},
1207+
{Key: "charlie"},
1208+
{Key: "delta"},
1209+
{Key: "echo"},
1210+
{Key: "foxtrot"},
1211+
{Key: "golf"},
1212+
{Key: "hotel"},
1213+
{Key: "india"},
1214+
{Key: "juliet"},
1215+
{Key: "kilo"},
1216+
{Key: "lima"},
1217+
{Key: "mike"},
1218+
{Key: "november"},
1219+
{Key: "oscar"},
1220+
{Key: "papa"},
1221+
{Key: "quebec"},
1222+
{Key: "romeo"},
1223+
{Key: "sierra"},
1224+
{Key: "tango"},
1225+
{Key: "uniform"},
1226+
{Key: "victor"},
1227+
{Key: "whiskey"},
1228+
{Key: "xray"},
1229+
{Key: "yankee"},
1230+
{Key: "zulu"},
1231+
}
1232+
1233+
_, err := client.Workspaces.AddTagBindings(ctx, wTest.ID, WorkspaceAddTagBindingsOptions{
1234+
TagBindings: tagBindings,
1235+
})
1236+
require.Error(t, err, "cannot exceed 10 bindings per resource")
1237+
})
1238+
}
1239+
11761240
func TestWorkspacesUpdate(t *testing.T) {
11771241
client := testClient(t)
11781242
ctx := context.Background()

0 commit comments

Comments
 (0)