diff --git a/CHANGELOG.md b/CHANGELOG.md index ec582f136..0e90e9d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Unreleased + +## Enhancements + +* Add `ListVariableSets` method to `Workspaces` interface to enable fetching variable sets associated with a workspace. [#551](https://github.com/hashicorp/go-tfe/pull/551) + # v1.10.0 ## Enhancements diff --git a/helper_test.go b/helper_test.go index 7e8b25f6e..1a3f6b194 100644 --- a/helper_test.go +++ b/helper_test.go @@ -1742,6 +1742,34 @@ func createVariableSet(t *testing.T, client *Client, org *Organization, options } } +func applyVariableSetToWorkspace(t *testing.T, client *Client, vsID string, wsID string) { + if vsID == "" { + t.Fatal("variable set ID must not be empty") + } + + if wsID == "" { + t.Fatal("workspace ID must not be empty") + } + + opts := &VariableSetApplyToWorkspacesOptions{} + opts.Workspaces = append(opts.Workspaces, &Workspace{ID: wsID}) + + ctx := context.Background() + if err := client.VariableSets.ApplyToWorkspaces(ctx, vsID, opts); err != nil { + t.Fatalf("Error applying variable set %s to workspace %s: %v", vsID, wsID, err) + } + + t.Cleanup(func() { + removeOpts := &VariableSetRemoveFromWorkspacesOptions{} + removeOpts.Workspaces = append(removeOpts.Workspaces, &Workspace{ID: wsID}) + if err := client.VariableSets.RemoveFromWorkspaces(ctx, vsID, removeOpts); err != nil { + t.Errorf("Error removing variable set from workspace! WARNING: Dangling resources\n"+ + "may exist! The full error is shown below.\n\n"+ + "VariableSet ID: %s\nError: %s", vsID, err) + } + }) +} + func createVariableSetVariable(t *testing.T, client *Client, vs *VariableSet, options VariableSetVariableCreateOptions) (*VariableSetVariable, func()) { var vsCleanup func() diff --git a/mocks/workspace_mocks.go b/mocks/workspace_mocks.go index f5fa01ce3..f30cfda48 100644 --- a/mocks/workspace_mocks.go +++ b/mocks/workspace_mocks.go @@ -182,6 +182,21 @@ func (mr *MockWorkspacesMockRecorder) ListTags(ctx, workspaceID, options interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTags", reflect.TypeOf((*MockWorkspaces)(nil).ListTags), ctx, workspaceID, options) } +// ListVariableSets mocks base method. +func (m *MockWorkspaces) ListVariableSets(ctx context.Context, workspaceID string, options *tfe.VariableSetListOptions) (*tfe.VariableSetList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListVariableSets", ctx, workspaceID, options) + ret0, _ := ret[0].(*tfe.VariableSetList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListVariableSets indicates an expected call of ListVariableSets. +func (mr *MockWorkspacesMockRecorder) ListVariableSets(ctx, workspaceID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVariableSets", reflect.TypeOf((*MockWorkspaces)(nil).ListVariableSets), ctx, workspaceID, options) +} + // Lock mocks base method. func (m *MockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) { m.ctrl.T.Helper() diff --git a/variable_set_test.go b/variable_set_test.go index 8af5b7652..1793773fc 100644 --- a/variable_set_test.go +++ b/variable_set_test.go @@ -51,7 +51,7 @@ func TestVariableSetsList(t *testing.T) { assert.Equal(t, 2, vsl.TotalCount) }) - t.Run("when Organization name is invalid ID", func(t *testing.T) { + t.Run("when Organization name is an invalid ID", func(t *testing.T) { vsl, err := client.VariableSets.List(ctx, badIdentifier, nil) assert.Nil(t, vsl) assert.EqualError(t, err, ErrInvalidOrg.Error()) diff --git a/workspace.go b/workspace.go index 462048e3b..58648ba09 100644 --- a/workspace.go +++ b/workspace.go @@ -92,6 +92,9 @@ type Workspaces interface { // RemoveTags removes tags from a workspace RemoveTags(ctx context.Context, workspaceID string, options WorkspaceRemoveTagsOptions) error + + // ListVariableSets reads the associated variable sets for a workspace. + ListVariableSets(ctx context.Context, workspaceID string, options *VariableSetListOptions) (*VariableSetList, error) } // workspaces implements Workspaces. @@ -1063,6 +1066,32 @@ func (s *workspaces) RemoveTags(ctx context.Context, workspaceID string, options return req.Do(ctx, nil) } +// ListVariableSets reads the associated variable sets for a workspace +func (s *workspaces) ListVariableSets(ctx context.Context, workspaceID string, options *VariableSetListOptions) (*VariableSetList, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidOrg + } + if options != nil { + if err := options.valid(); err != nil { + return nil, err + } + } + + u := fmt.Sprintf("workspaces/%s/varsets", url.QueryEscape(workspaceID)) + req, err := s.client.NewRequest("GET", u, options) + if err != nil { + return nil, err + } + + vl := &VariableSetList{} + err = req.Do(ctx, vl) + if err != nil { + return nil, err + } + + return vl, nil +} + func (o WorkspaceCreateOptions) valid() error { if !validString(o.Name) { return ErrRequiredName diff --git a/workspace_integration_test.go b/workspace_integration_test.go index cb915b788..d0609c665 100644 --- a/workspace_integration_test.go +++ b/workspace_integration_test.go @@ -1848,6 +1848,60 @@ func TestWorkspaces_RemoveTags(t *testing.T) { }) } +func TestWorkspacesListVariableSets(t *testing.T) { + skipIfNotCINode(t) + + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + t.Cleanup(orgTestCleanup) + + workspaceTest, workspaceTestCleanup := createWorkspace(t, client, orgTest) + t.Cleanup(workspaceTestCleanup) + + vsTest1, vsTestCleanup1 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{}) + t.Cleanup(vsTestCleanup1) + vsTest2, vsTestCleanup2 := createVariableSet(t, client, orgTest, VariableSetCreateOptions{}) + t.Cleanup(vsTestCleanup2) + + applyVariableSetToWorkspace(t, client, vsTest1.ID, workspaceTest.ID) + applyVariableSetToWorkspace(t, client, vsTest2.ID, workspaceTest.ID) + + t.Run("without list options", func(t *testing.T) { + vsl, err := client.Workspaces.ListVariableSets(ctx, workspaceTest.ID, nil) + require.NoError(t, err) + require.Len(t, vsl.Items, 2) + + ids := []string{vsTest1.ID, vsTest2.ID} + assert.Contains(t, ids, vsl.Items[0].ID) + assert.Contains(t, ids, vsl.Items[1].ID) + }) + + t.Run("with list options", func(t *testing.T) { + t.Skip("paging not supported yet in API") + // Request a page number which is out of range. The result should + // be successful, but return no results if the paging options are + // properly passed along. + vsl, err := client.Workspaces.ListVariableSets(ctx, workspaceTest.ID, &VariableSetListOptions{ + ListOptions: ListOptions{ + PageNumber: 999, + PageSize: 100, + }, + }) + require.NoError(t, err) + assert.Empty(t, vsl.Items) + assert.Equal(t, 999, vsl.CurrentPage) + assert.Equal(t, 2, vsl.TotalCount) + }) + + t.Run("when Workspace ID is an invalid ID", func(t *testing.T) { + vsl, err := client.Workspaces.ListVariableSets(ctx, badIdentifier, nil) + assert.Nil(t, vsl) + assert.EqualError(t, err, ErrInvalidOrg.Error()) + }) +} + func TestWorkspace_Unmarshal(t *testing.T) { skipIfNotCINode(t)