From 8538bb18a933a705f974a5172cf69247abdd6085 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Tue, 4 Oct 2022 14:10:30 -0400 Subject: [PATCH 1/8] Update README.md API coverage Check off GPG Keys in API coverage. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f36269c8c..317dd0fde 100644 --- a/README.md +++ b/README.md @@ -88,11 +88,11 @@ This API client covers most of the existing Terraform Cloud API calls and is upd - [x] Policy Checks - [x] Policy Sets - [x] Policy Set Parameters -- [ ] Private Registry +- [x] Private Registry - [x] Modules - [x] Providers - [x] Provider Provider Versions and Platforms - - [ ] GPG Keys + - [x] GPG Keys - [x] Runs - [x] Run Tasks - [ ] Run Tasks Integration From 0ef33a9f9b7ff1dd252000c972c3a01848fb05fa Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Fri, 14 Oct 2022 10:44:25 -0400 Subject: [PATCH 2/8] Add filter[status] and query search params to list opts --- organization_membership.go | 7 ++++ organization_membership_integration_test.go | 40 +++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/organization_membership.go b/organization_membership.go index 396b3a94a..db57642a6 100644 --- a/organization_membership.go +++ b/organization_membership.go @@ -80,6 +80,13 @@ type OrganizationMembershipListOptions struct { // Optional: A list of organization member emails to filter by. Emails []string `url:"filter[email],omitempty"` + + // Optional: If specified, restricts results to those matching status value. + Status OrganizationMembershipStatus `url:"filter[status],omitempty"` + + // Optional: A query string to search organization memberships by user name + // and email. + Query string `url:"q,omitempty"` } // OrganizationMembershipCreateOptions represents the options for creating an organization membership. diff --git a/organization_membership_integration_test.go b/organization_membership_integration_test.go index ab4a0381d..76525aae4 100644 --- a/organization_membership_integration_test.go +++ b/organization_membership_integration_test.go @@ -103,6 +103,46 @@ func TestOrganizationMembershipsList(t *testing.T) { }) }) + t.Run("with status filter option", func(t *testing.T) { + _, memTest1Cleanup := createOrganizationMembership(t, client, orgTest) + t.Cleanup(memTest1Cleanup) + _, memTest2Cleanup := createOrganizationMembership(t, client, orgTest) + t.Cleanup(memTest2Cleanup) + + ml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{ + Status: OrganizationMembershipInvited, + }) + require.NoError(t, err) + + require.Len(t, ml.Items, 2) + for _, member := range ml.Items { + assert.Equal(t, member.Status, OrganizationMembershipInvited) + } + }) + + t.Run("with search query string", func(t *testing.T) { + memTest1, memTest1Cleanup := createOrganizationMembership(t, client, orgTest) + t.Cleanup(memTest1Cleanup) + _, memTest2Cleanup := createOrganizationMembership(t, client, orgTest) + t.Cleanup(memTest2Cleanup) + _, memTest3Cleanup := createOrganizationMembership(t, client, orgTest) + t.Cleanup(memTest3Cleanup) + + t.Run("using an email", func(t *testing.T) { + ml, err := client.OrganizationMemberships.List(ctx, orgTest.Name, &OrganizationMembershipListOptions{ + Query: memTest1.Email, + }) + require.NoError(t, err) + + require.Len(t, ml.Items, 1) + assert.Equal(t, ml.Items[0].Email, memTest1.Email) + }) + + t.Run("using a user name", func(t *testing.T) { + t.Skip("Skipping, missing Account API support in order to set usernames") + }) + }) + t.Run("without a valid organization", func(t *testing.T) { ml, err := client.OrganizationMemberships.List(ctx, badIdentifier, nil) assert.Nil(t, ml) From 739497bd6ed8dce9d4b9b72777e70f6a81e7e7b0 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Fri, 14 Oct 2022 10:46:43 -0400 Subject: [PATCH 3/8] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec582f136..5954d9db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# (Unreleased) + +## Enhancements +* Adds `Query` and `Status` fields to `OrganizationMembershipListOptions` to allow filtering memberships by status or username by @sebasslash [#550](https://github.com/hashicorp/go-tfe/pull/550) + # v1.10.0 ## Enhancements From 6dbb1b33b2c6e5c95c90fbd7205e17701387f407 Mon Sep 17 00:00:00 2001 From: Tyler Stapler Date: Tue, 30 Aug 2022 13:00:38 -0700 Subject: [PATCH 4/8] Add method to list variable sets by workspace --- helper_test.go | 28 ++++++++++++++++++++++ variable_set.go | 29 +++++++++++++++++++++++ variable_set_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 1 deletion(-) 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/variable_set.go b/variable_set.go index 3a9a1f9ec..6bc37c6a5 100644 --- a/variable_set.go +++ b/variable_set.go @@ -17,6 +17,9 @@ type VariableSets interface { // List all the variable sets within an organization. List(ctx context.Context, organization string, options *VariableSetListOptions) (*VariableSetList, error) + // ListForWorkspace gets the associated variable sets for a workspace. + ListForWorkspace(ctx context.Context, workspaceID string, options *VariableSetListOptions) (*VariableSetList, error) + // Create is used to create a new variable set. Create(ctx context.Context, organization string, options *VariableSetCreateOptions) (*VariableSet, error) @@ -179,6 +182,32 @@ func (s *variableSets) List(ctx context.Context, organization string, options *V return vl, nil } +// ListForWorkspace gets the associated variable sets for a workspace. +func (s *variableSets) ListForWorkspace(ctx context.Context, workspaceID string, options *VariableSetListOptions) (*VariableSetList, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID + } + 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 +} + // Create is used to create a new variable set. func (s *variableSets) Create(ctx context.Context, organization string, options *VariableSetCreateOptions) (*VariableSet, error) { if !validStringID(&organization) { diff --git a/variable_set_test.go b/variable_set_test.go index 8af5b7652..c3fcd7d98 100644 --- a/variable_set_test.go +++ b/variable_set_test.go @@ -51,13 +51,67 @@ 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()) }) } +func TestVariableSetsListForWorkspace(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.VariableSets.ListForWorkspace(ctx, workspaceTest.ID, nil) + require.NoError(t, err) + require.Len(t, vsl.Items, 2) + + ids := []string{vsTest1.ID, vsTest2.ID} + for _, varset := range vsl.Items { + assert.Contains(t, ids, varset.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.VariableSets.ListForWorkspace(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.VariableSets.ListForWorkspace(ctx, badIdentifier, nil) + assert.Nil(t, vsl) + assert.EqualError(t, err, ErrInvalidWorkspaceID.Error()) + }) +} + func TestVariableSetsCreate(t *testing.T) { skipIfNotCINode(t) From 092d1b7327414e5131402c9339de168febd9cd90 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Mon, 17 Oct 2022 13:31:44 -0400 Subject: [PATCH 5/8] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5954d9db6..ab4b2d6f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # (Unreleased) ## Enhancements + * Adds `Query` and `Status` fields to `OrganizationMembershipListOptions` to allow filtering memberships by status or username by @sebasslash [#550](https://github.com/hashicorp/go-tfe/pull/550) +* Add `ListForWorkspace` method to `VariableSets` interface to enable fetching variable sets associated with a workspace. [#551](https://github.com/hashicorp/go-tfe/pull/551) # v1.10.0 From 5d4da4e3864517d759c57acd31d2381112fba463 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Mon, 17 Oct 2022 13:34:28 -0400 Subject: [PATCH 6/8] Regenerate mocks --- mocks/variable_set_mocks.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mocks/variable_set_mocks.go b/mocks/variable_set_mocks.go index 56d02c9ab..5ac9aa699 100644 --- a/mocks/variable_set_mocks.go +++ b/mocks/variable_set_mocks.go @@ -93,6 +93,21 @@ func (mr *MockVariableSetsMockRecorder) List(ctx, organization, options interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockVariableSets)(nil).List), ctx, organization, options) } +// ListForWorkspace mocks base method. +func (m *MockVariableSets) ListForWorkspace(ctx context.Context, workspaceID string, options *tfe.VariableSetListOptions) (*tfe.VariableSetList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListForWorkspace", ctx, workspaceID, options) + ret0, _ := ret[0].(*tfe.VariableSetList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListForWorkspace indicates an expected call of ListForWorkspace. +func (mr *MockVariableSetsMockRecorder) ListForWorkspace(ctx, workspaceID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListForWorkspace", reflect.TypeOf((*MockVariableSets)(nil).ListForWorkspace), ctx, workspaceID, options) +} + // Read mocks base method. func (m *MockVariableSets) Read(ctx context.Context, variableSetID string, options *tfe.VariableSetReadOptions) (*tfe.VariableSet, error) { m.ctrl.T.Helper() From 40e79dd36902c906112d9fc5456c26ddb870b6d2 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Mon, 17 Oct 2022 16:04:41 -0400 Subject: [PATCH 7/8] Rename TF_CLOUD_WORKSPACE to TF_WORKSPACE --- .github/workflows/nightly-tfe-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly-tfe-ci.yml b/.github/workflows/nightly-tfe-ci.yml index 85a249771..509cfa4fb 100644 --- a/.github/workflows/nightly-tfe-ci.yml +++ b/.github/workflows/nightly-tfe-ci.yml @@ -20,7 +20,7 @@ jobs: TFE_TOKEN: ${{ secrets.TF_WORKFLOW_TFLOCAL_CLOUD_TFC_TOKEN }} TF_CLOUD_ORGANIZATION: "hashicorp-v2" TF_CLOUD_HOSTNAME: "app.terraform.io" - TF_CLOUD_WORKSPACE: "tflocal-go-tfe-nightly" + TF_WORKSPACE: "tflocal-go-tfe-nightly" run: | cd tflocal/ terraform init From cd77d1d380c519bc6a3eb61be4fdaf8269d212aa Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Mon, 17 Oct 2022 16:34:28 -0400 Subject: [PATCH 8/8] Sync changelog with latest changes --- CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4b2d6f9..aa5747068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,12 @@ ## Enhancements -* Adds `Query` and `Status` fields to `OrganizationMembershipListOptions` to allow filtering memberships by status or username by @sebasslash [#550](https://github.com/hashicorp/go-tfe/pull/550) -* Add `ListForWorkspace` method to `VariableSets` interface to enable fetching variable sets associated with a workspace. [#551](https://github.com/hashicorp/go-tfe/pull/551) +* Add `Query` and `Status` fields to `OrganizationMembershipListOptions` to allow filtering memberships by status or username by @sebasslash [#550](https://github.com/hashicorp/go-tfe/pull/550) +* Add `ListForWorkspace` method to `VariableSets` interface to enable fetching variable sets associated with a workspace by @tstapler [#552](https://github.com/hashicorp/go-tfe/pull/552) +* Add `NotificationTriggerAssessmentDrifted` and `NotificationTriggerAssessmentFailed` notification trigger types by @lawliet89 [#542](https://github.com/hashicorp/go-tfe/pull/542) + +## Bug Fixes +* Fix marshalling of run variables in `RunCreateOptions`. The `Variables` field type in `Run` struct has changed from `[]*RunVariable` to `[]*RunVariableAttr` by @Uk1288 [#531](https://github.com/hashicorp/go-tfe/pull/531) # v1.10.0 @@ -14,7 +18,6 @@ ## Bug Fixes * Fixes null value returned in variable set relationship in `VariableSetVariable` by @sebasslash [#521](https://github.com/hashicorp/go-tfe/pull/521) -* Fix marshalling of run variables in `RunCreateOptions`. The `Variables` field type in `Run` struct has changed from `[]*RunVariable` to `[]*RunVariableAttr` by @Uk1288 [#531](https://github.com/hashicorp/go-tfe/pull/531) # v1.9.0