diff --git a/CHANGELOG.md b/CHANGELOG.md index e1fd672fd..14d314079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## Enhancements * Adds the `ProviderType` field to `AdminSAMLSetting` and `AdminSAMLSettingsUpdateOptions` to support the `provider-type` SAML setting. * Adds `AdminSCIMSetting` to support managing site-level SCIM settings by @skj-skj [#1307](https://github.com/hashicorp/go-tfe/pull/1307) +* Adds BETA support for delegating policy overrides on teams by @jbonhag [#1301](https://github.com/hashicorp/go-tfe/pull/1301) * Adds `AdminSCIMToken` to support managing site-level SCIM tokens by @skj-skj [#1310](https://github.com/hashicorp/go-tfe/pull/1310) * Add support for trigger patterns and working directories to stacks by @aaabdelgany [#1305](https://github.com/hashicorp/go-tfe/pull/1305) diff --git a/helper_test.go b/helper_test.go index 4e36481d6..d06d02bbc 100644 --- a/helper_test.go +++ b/helper_test.go @@ -2425,10 +2425,11 @@ func createTeam(t *testing.T, client *Client, org *Organization) (*Team, func()) tm, err := client.Teams.Create(ctx, org.Name, TeamCreateOptions{ Name: String(randomString(t)), OrganizationAccess: &OrganizationAccessOptions{ - ManagePolicies: Bool(true), - ManagePolicyOverrides: Bool(true), - ManageProviders: Bool(true), - ManageModules: Bool(true), + ManagePolicies: Bool(true), + ManagePolicyOverrides: Bool(true), + DelegatePolicyOverrides: Bool(true), + ManageProviders: Bool(true), + ManageModules: Bool(true), }, }) if err != nil { diff --git a/team.go b/team.go index 846e8bbf1..9f9d340ba 100644 --- a/team.go +++ b/team.go @@ -64,8 +64,10 @@ type Team struct { // OrganizationAccess represents the team's permissions on its organization type OrganizationAccess struct { - ManagePolicies bool `jsonapi:"attr,manage-policies"` - ManagePolicyOverrides bool `jsonapi:"attr,manage-policy-overrides"` + ManagePolicies bool `jsonapi:"attr,manage-policies"` + ManagePolicyOverrides bool `jsonapi:"attr,manage-policy-overrides"` + // **Note: This API is still in BETA and subject to change.** + DelegatePolicyOverrides bool `jsonapi:"attr,delegate-policy-overrides"` ManageWorkspaces bool `jsonapi:"attr,manage-workspaces"` ManageVCSSettings bool `jsonapi:"attr,manage-vcs-settings"` ManageProviders bool `jsonapi:"attr,manage-providers"` @@ -160,8 +162,10 @@ type TeamUpdateOptions struct { // OrganizationAccessOptions represents the organization access options of a team. type OrganizationAccessOptions struct { - ManagePolicies *bool `json:"manage-policies,omitempty"` - ManagePolicyOverrides *bool `json:"manage-policy-overrides,omitempty"` + ManagePolicies *bool `json:"manage-policies,omitempty"` + ManagePolicyOverrides *bool `json:"manage-policy-overrides,omitempty"` + // **Note: This API is still in BETA and subject to change.** + DelegatePolicyOverrides *bool `json:"delegate-policy-overrides,omitempty"` ManageWorkspaces *bool `json:"manage-workspaces,omitempty"` ManageVCSSettings *bool `json:"manage-vcs-settings,omitempty"` ManageProviders *bool `json:"manage-providers,omitempty"` diff --git a/team_access.go b/team_access.go index 76a7e1e4d..2965214c3 100644 --- a/team_access.go +++ b/team_access.go @@ -102,6 +102,8 @@ type TeamAccess struct { SentinelMocks SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks"` WorkspaceLocking bool `jsonapi:"attr,workspace-locking"` RunTasks bool `jsonapi:"attr,run-tasks"` + // **Note: This API is still in BETA and subject to change.** + PolicyOverrides bool `jsonapi:"attr,policy-overrides"` // Relations Team *Team `jsonapi:"relation,team"` @@ -133,6 +135,8 @@ type TeamAccessAddOptions struct { SentinelMocks *SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks,omitempty"` WorkspaceLocking *bool `jsonapi:"attr,workspace-locking,omitempty"` RunTasks *bool `jsonapi:"attr,run-tasks,omitempty"` + // **Note: This API is still in BETA and subject to change.** + PolicyOverrides *bool `jsonapi:"attr,policy-overrides,omitempty"` // The team to add to the workspace Team *Team `jsonapi:"relation,team"` @@ -160,6 +164,8 @@ type TeamAccessUpdateOptions struct { SentinelMocks *SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks,omitempty"` WorkspaceLocking *bool `jsonapi:"attr,workspace-locking,omitempty"` RunTasks *bool `jsonapi:"attr,run-tasks,omitempty"` + // **Note: This API is still in BETA and subject to change.** + PolicyOverrides *bool `jsonapi:"attr,policy-overrides,omitempty"` } // List all the team accesses for a given workspace. diff --git a/team_access_integration_test.go b/team_access_integration_test.go index eca959c1e..32f8bf404 100644 --- a/team_access_integration_test.go +++ b/team_access_integration_test.go @@ -397,3 +397,51 @@ func TestTeamAccessesUpdateRunTasks(t *testing.T) { assert.Equal(t, newAccess, ta.RunTasks) }) } + +func TestTeamAccessesAddPolicyOverrides(t *testing.T) { + skipUnlessBeta(t) + t.Parallel() + + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + defer orgTestCleanup() + + wTest, wTestCleanup := createWorkspace(t, client, orgTest) + defer wTestCleanup() + + tmTest, tmTestCleanup := createTeam(t, client, orgTest) + defer tmTestCleanup() + + t.Run("with valid custom options", func(t *testing.T) { + options := TeamAccessAddOptions{ + Access: Access(AccessCustom), + PolicyOverrides: Bool(true), + Team: tmTest, + Workspace: wTest, + } + + ta, err := client.TeamAccess.Add(ctx, options) + defer func() { + err := client.TeamAccess.Remove(ctx, ta.ID) + if err != nil { + t.Logf("error removing team access (%s): %s", ta.ID, err) + } + }() + + require.NoError(t, err) + + refreshed, err := client.TeamAccess.Read(ctx, ta.ID) + require.NoError(t, err) + + for _, item := range []*TeamAccess{ + ta, + refreshed, + } { + assert.NotEmpty(t, item.ID) + assert.Equal(t, *options.Access, item.Access) + assert.Equal(t, true, item.PolicyOverrides) + } + }) +} diff --git a/team_integration_test.go b/team_integration_test.go index ce118add0..ca01ec224 100644 --- a/team_integration_test.go +++ b/team_integration_test.go @@ -116,16 +116,45 @@ func TestTeamsCreate(t *testing.T) { } }) + t.Run("with beta delegate-policy-overrides", func(t *testing.T) { + skipUnlessBeta(t) + + options := TeamCreateOptions{ + Name: String("delegate-policy-overrides"), + OrganizationAccess: &OrganizationAccessOptions{ + DelegatePolicyOverrides: Bool(true), + }, + } + + team, err := client.Teams.Create(ctx, orgTest.Name, options) + require.NoError(t, err) + defer func() { + err := client.Teams.Delete(ctx, team.ID) + require.NoError(t, err) + }() + + refreshed, err := client.Teams.Read(ctx, team.ID) + require.NoError(t, err) + + for _, item := range []*Team{ + team, + refreshed, + } { + assert.NotEmpty(t, item.ID) + assert.Equal(t, *options.Name, item.Name) + assert.Equal(t, *options.OrganizationAccess.DelegatePolicyOverrides, item.OrganizationAccess.DelegatePolicyOverrides) + } + }) + t.Run("with sso-team-id", func(t *testing.T) { options := TeamCreateOptions{ Name: String("rockettes"), SSOTeamID: String("7dddb675-73e0-4858-a8ad-0e597064301b"), } team, err := client.Teams.Create(ctx, orgTest.Name, options) + require.NoError(t, err) - assert.Nil(t, err) assert.Equal(t, *options.Name, team.Name) - assert.NotNil(t, team.SSOTeamID) assert.Equal(t, *options.SSOTeamID, team.SSOTeamID) }) @@ -174,7 +203,8 @@ func TestTeamsRead(t *testing.T) { t.Run("when the team exists", func(t *testing.T) { tm, err := client.Teams.Read(ctx, tmTest.ID) require.NoError(t, err) - assert.Equal(t, tmTest, tm) + assert.Equal(t, tmTest.ID, tm.ID) + assert.Equal(t, tmTest.Name, tm.Name) t.Run("visibility is returned", func(t *testing.T) { assert.Equal(t, "secret", tm.Visibility) @@ -278,6 +308,32 @@ func TestTeamsUpdate(t *testing.T) { } }) + t.Run("with beta delegate policy overrides", func(t *testing.T) { + skipUnlessBeta(t) + + team, err := client.Teams.Create(ctx, orgTest.Name, TeamCreateOptions{ + Name: String(randomString(t)), + }) + require.NoError(t, err) + defer func() { + err := client.Teams.Delete(ctx, team.ID) + require.NoError(t, err) + }() + + updated, err := client.Teams.Update(ctx, team.ID, TeamUpdateOptions{ + OrganizationAccess: &OrganizationAccessOptions{ + DelegatePolicyOverrides: Bool(true), + }, + }) + require.NoError(t, err) + + refreshed, err := client.Teams.Read(ctx, team.ID) + require.NoError(t, err) + + assert.True(t, updated.OrganizationAccess.DelegatePolicyOverrides) + assert.True(t, refreshed.OrganizationAccess.DelegatePolicyOverrides) + }) + t.Run("when the team does not exist", func(t *testing.T) { tm, err := client.Teams.Update(ctx, "nonexisting", TeamUpdateOptions{ Name: String("foo bar"), diff --git a/team_project_access.go b/team_project_access.go index b8e78807f..a6e947bf8 100644 --- a/team_project_access.go +++ b/team_project_access.go @@ -86,6 +86,8 @@ type TeamProjectAccessWorkspacePermissions struct { WorkspaceMovePermission bool `jsonapi:"attr,move"` WorkspaceDeletePermission bool `jsonapi:"attr,delete"` WorkspaceRunTasksPermission bool `jsonapi:"attr,run-tasks"` + // **Note: This API is still in BETA and subject to change.** + WorkspacePolicyOverridesPermission bool `jsonapi:"attr,policy-overrides"` } // ProjectSettingsPermissionType represents the permissiontype to a project's settings @@ -167,6 +169,8 @@ type TeamProjectAccessWorkspacePermissionsOptions struct { Move *bool `json:"move,omitempty"` Delete *bool `json:"delete,omitempty"` RunTasks *bool `json:"run-tasks,omitempty"` + // **Note: This API is still in BETA and subject to change.** + PolicyOverrides *bool `json:"policy-overrides,omitempty"` } // TeamProjectAccessListOptions represents the options for listing team project accesses diff --git a/team_project_access_integration_test.go b/team_project_access_integration_test.go index 0a0829b01..237c847e3 100644 --- a/team_project_access_integration_test.go +++ b/team_project_access_integration_test.go @@ -351,6 +351,44 @@ func TestTeamProjectAccessesAdd(t *testing.T) { } }) + t.Run("with valid options for custom workspace policy overrides permission", func(t *testing.T) { + skipUnlessBeta(t) + + options := TeamProjectAccessAddOptions{ + Access: *ProjectAccess(TeamProjectAccessCustom), + Team: tmTest, + Project: pTest, + WorkspaceAccess: &TeamProjectAccessWorkspacePermissionsOptions{ + Runs: WorkspaceRunsPermission(WorkspaceRunsPermissionApply), + PolicyOverrides: Bool(true), + }, + } + + tpa, err := client.TeamProjectAccess.Add(ctx, options) + t.Cleanup(func() { + err := client.TeamProjectAccess.Remove(ctx, tpa.ID) + if err != nil { + t.Logf("error removing team access (%s): %s", tpa.ID, err) + } + }) + + require.NoError(t, err) + + // Get a refreshed view from the API. + refreshed, err := client.TeamProjectAccess.Read(ctx, tpa.ID) + require.NoError(t, err) + + for _, item := range []*TeamProjectAccess{ + tpa, + refreshed, + } { + assert.NotEmpty(t, item.ID) + assert.Equal(t, options.Access, item.Access) + assert.Equal(t, *options.WorkspaceAccess.Runs, item.WorkspaceAccess.WorkspaceRunsPermission) + assert.Equal(t, true, item.WorkspaceAccess.WorkspacePolicyOverridesPermission) + } + }) + t.Run("when the team already has access to the project", func(t *testing.T) { _, tpaTestCleanup := createTeamProjectAccess(t, client, tmTest, pTest, nil) defer tpaTestCleanup()