Skip to content

Commit 4c63cff

Browse files
authored
Merge pull request #1087 from hashicorp/add-inherited-from-relationship-effective-tags
fix: Add links attribute to effective tags
2 parents fd1a115 + 29bd116 commit 4c63cff

File tree

7 files changed

+177
-8
lines changed

7 files changed

+177
-8
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Unreleased
22

3+
# v1.78.0
4+
5+
## Enhancements
6+
* Adds `Links` field to `EffectiveTagBindings` to check whether an effective tag binding is inherited, by @sebasslash [#1087](https://github.com/hashicorp/go-tfe/pull/1087)
7+
38
# v1.77.0
49

510
## Enhancements
@@ -17,7 +22,7 @@ In the last release, Runs interface method `ListForOrganization` included pagina
1722
## Enhancements
1823

1924
* Adds `DefaultProject` to `OrganizationUpdateOptions` to support updating an organization's default project. This provides BETA support, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users, by @mkam [#1056](https://github.com/hashicorp/go-tfe/pull/1056)
20-
* Adds `ReadTerraformRegistryModule` to support reading a registry module from Terraform Registry's proxied endpoints by @paladin-devops [#1057](https://github.com/hashicorp/go-tfe/pull/1057)
25+
* Adds `ReadTerraformRegistryModule` to support reading a registry module from Terraform Registry's proxied endpoints by @paladin-devops [#1057](https://github.com/hashicorp/go-tfe/pull/1057)
2126
* Adds a new method `ListForOrganization` to list Runs in an organization by @arybolovlev [#1059](https://github.com/hashicorp/go-tfe/pull/1059)
2227

2328
## Bug fixes

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.

project.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type Projects interface {
2828
// Read a project by its ID.
2929
Read(ctx context.Context, projectID string) (*Project, error)
3030

31+
// ReadWithOptions a project by its ID.
32+
ReadWithOptions(ctx context.Context, projectID string, options ProjectReadOptions) (*Project, error)
33+
3134
// Update a project.
3235
Update(ctx context.Context, projectID string, options ProjectUpdateOptions) (*Project, error)
3336

@@ -101,6 +104,11 @@ type ProjectListOptions struct {
101104
Include []ProjectIncludeOpt `url:"include,omitempty"`
102105
}
103106

107+
type ProjectReadOptions struct {
108+
// Optional: A list of relations to include
109+
Include []ProjectIncludeOpt `url:"include,omitempty"`
110+
}
111+
104112
// ProjectCreateOptions represents the options for creating a project
105113
type ProjectCreateOptions struct {
106114
// Type is a public field utilized by JSON:API to
@@ -205,6 +213,27 @@ func (s *projects) Create(ctx context.Context, organization string, options Proj
205213
return p, nil
206214
}
207215

216+
// ReadWithOptions a project by its ID.
217+
func (s *projects) ReadWithOptions(ctx context.Context, projectID string, options ProjectReadOptions) (*Project, error) {
218+
if !validStringID(&projectID) {
219+
return nil, ErrInvalidProjectID
220+
}
221+
222+
u := fmt.Sprintf("projects/%s", url.PathEscape(projectID))
223+
req, err := s.client.NewRequest("GET", u, options)
224+
if err != nil {
225+
return nil, err
226+
}
227+
228+
p := &Project{}
229+
err = req.Do(ctx, p)
230+
if err != nil {
231+
return nil, err
232+
}
233+
234+
return p, nil
235+
}
236+
208237
// Read a single project by its ID.
209238
func (s *projects) Read(ctx context.Context, projectID string) (*Project, error) {
210239
if !validStringID(&projectID) {

projects_integration_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,35 @@ func TestProjectsList(t *testing.T) {
142142
})
143143
}
144144

145+
func TestProjectsReadWithOptions(t *testing.T) {
146+
client := testClient(t)
147+
ctx := context.Background()
148+
149+
orgTest, orgTestCleanup := createOrganization(t, client)
150+
defer orgTestCleanup()
151+
152+
pTest, pTestCleanup := createProjectWithOptions(t, client, orgTest, ProjectCreateOptions{
153+
Name: "project-with-tags",
154+
TagBindings: []*TagBinding{
155+
{Key: "foo", Value: "bar"},
156+
},
157+
})
158+
defer pTestCleanup()
159+
160+
t.Run("when the project exists", func(t *testing.T) {
161+
p, err := client.Projects.ReadWithOptions(ctx, pTest.ID, ProjectReadOptions{
162+
Include: []ProjectIncludeOpt{ProjectEffectiveTagBindings},
163+
})
164+
require.NoError(t, err)
165+
assert.Equal(t, orgTest.Name, p.Organization.Name)
166+
167+
// Tag data is included
168+
assert.Len(t, p.EffectiveTagBindings, 1)
169+
assert.Equal(t, "foo", p.EffectiveTagBindings[0].Key)
170+
assert.Equal(t, "bar", p.EffectiveTagBindings[0].Value)
171+
})
172+
}
173+
145174
func TestProjectsRead(t *testing.T) {
146175
client := testClient(t)
147176
ctx := context.Background()

tag.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
package tfe
55

6-
import "fmt"
6+
import (
7+
"fmt"
8+
)
79

810
type TagList struct {
911
*Pagination
@@ -23,9 +25,10 @@ type TagBinding struct {
2325
}
2426

2527
type EffectiveTagBinding struct {
26-
ID string `jsonapi:"primary,effective-tag-bindings"`
27-
Key string `jsonapi:"attr,key"`
28-
Value string `jsonapi:"attr,value,omitempty"`
28+
ID string `jsonapi:"primary,effective-tag-bindings"`
29+
Key string `jsonapi:"attr,key"`
30+
Value string `jsonapi:"attr,value,omitempty"`
31+
Links map[string]interface{} `jsonapi:"links,omitempty"`
2932
}
3033

3134
func encodeTagFiltersAsParams(filters []*TagBinding) map[string][]string {

tfe.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ func checkResponseCode(r *http.Response) error {
987987
return err
988988
}
989989

990-
if errorPayloadContains(errs, "Invalid include parameter") {
990+
if errorPayloadContains(errs, "include parameter") {
991991
return ErrInvalidIncludeValue
992992
}
993993
return errors.New(strings.Join(errs, "\n"))

workspace_integration_test.go

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,17 @@ func TestWorkspacesList(t *testing.T) {
305305
orgTest2, orgTest2Cleanup := createOrganization(t, client)
306306
t.Cleanup(orgTest2Cleanup)
307307

308+
prj, pTestCleanup1 := createProjectWithOptions(t, client, orgTest2, ProjectCreateOptions{
309+
Name: randomStringWithoutSpecialChar(t),
310+
TagBindings: []*TagBinding{
311+
{Key: "key3", Value: "value3"},
312+
},
313+
})
314+
t.Cleanup(pTestCleanup1)
315+
308316
_, wTestCleanup1 := createWorkspaceWithOptions(t, client, orgTest2, WorkspaceCreateOptions{
309-
Name: String(randomString(t)),
317+
Name: String(randomString(t)),
318+
Project: prj,
310319
TagBindings: []*TagBinding{
311320
{Key: "key1", Value: "value1"},
312321
{Key: "key2", Value: "value2a"},
@@ -319,11 +328,24 @@ func TestWorkspacesList(t *testing.T) {
319328
})
320329
require.NoError(t, err)
321330
require.Len(t, wl.Items, 1)
322-
require.Len(t, wl.Items[0].EffectiveTagBindings, 2)
331+
require.Len(t, wl.Items[0].EffectiveTagBindings, 3)
323332
assert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[0].Key)
324333
assert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[0].Value)
325334
assert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[1].Key)
326335
assert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[1].Value)
336+
assert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[2].Key)
337+
assert.NotEmpty(t, wl.Items[0].EffectiveTagBindings[2].Value)
338+
339+
inheritedTagsFound := 0
340+
for _, tag := range wl.Items[0].EffectiveTagBindings {
341+
if tag.Links["inherited-from"] != nil {
342+
inheritedTagsFound += 1
343+
}
344+
}
345+
346+
if inheritedTagsFound != 1 {
347+
t.Fatalf("Expected 1 inherited tag, got %d", inheritedTagsFound)
348+
}
327349
})
328350

329351
t.Run("when using project id filter and project contains workspaces", func(t *testing.T) {
@@ -3089,3 +3111,69 @@ func TestWorkspacesAutoDestroyDuration(t *testing.T) {
30893111
require.Equal(t, wTest.InheritsProjectAutoDestroy, false)
30903112
})
30913113
}
3114+
3115+
func TestWorkspaces_effectiveTagBindingsInheritedFrom(t *testing.T) {
3116+
skipUnlessBeta(t)
3117+
3118+
client := testClient(t)
3119+
ctx := context.Background()
3120+
3121+
orgTest, orgTestCleanup := createOrganization(t, client)
3122+
t.Cleanup(orgTestCleanup)
3123+
3124+
projTest, projTestCleanup := createProject(t, client, orgTest)
3125+
t.Cleanup(projTestCleanup)
3126+
3127+
ws, wsCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
3128+
Name: String("mycoolworkspace"),
3129+
Project: projTest,
3130+
})
3131+
t.Cleanup(wsCleanup)
3132+
3133+
_, err := client.Workspaces.AddTagBindings(ctx, ws.ID, WorkspaceAddTagBindingsOptions{
3134+
TagBindings: []*TagBinding{
3135+
{
3136+
Key: "a",
3137+
Value: "1",
3138+
},
3139+
{
3140+
Key: "b",
3141+
Value: "2",
3142+
},
3143+
},
3144+
})
3145+
require.NoError(t, err)
3146+
3147+
t.Run("when no tags are inherited from the project", func(t *testing.T) {
3148+
effectiveBindings, err := client.Workspaces.ListEffectiveTagBindings(ctx, ws.ID)
3149+
require.NoError(t, err)
3150+
3151+
for _, binding := range effectiveBindings {
3152+
require.Nil(t, binding.Links)
3153+
}
3154+
})
3155+
3156+
t.Run("when tags are inherited from the project", func(t *testing.T) {
3157+
_, err := client.Projects.AddTagBindings(ctx, projTest.ID, ProjectAddTagBindingsOptions{
3158+
TagBindings: []*TagBinding{
3159+
{
3160+
Key: "inherited",
3161+
Value: "foo",
3162+
},
3163+
},
3164+
})
3165+
require.NoError(t, err)
3166+
3167+
effectiveBindings, err := client.Workspaces.ListEffectiveTagBindings(ctx, ws.ID)
3168+
require.NoError(t, err)
3169+
3170+
for _, binding := range effectiveBindings {
3171+
if binding.Key == "inherited" {
3172+
require.NotNil(t, binding.Links)
3173+
require.NotNil(t, binding.Links["inherited-from"])
3174+
} else {
3175+
require.Nil(t, binding.Links)
3176+
}
3177+
}
3178+
})
3179+
}

0 commit comments

Comments
 (0)