Skip to content

Commit cd7fe71

Browse files
authored
Merge pull request #1033 from hashicorp/Netra2104/TF-7897-add-policy-set-workspace-exclusions-resource
Add a new workspace_policy_set_exclusion resource
2 parents 23a944c + a772eff commit cd7fe71

File tree

5 files changed

+431
-37
lines changed

5 files changed

+431
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ FEATURES:
88
* `d/tfe_organization_membership`: Add `organization_membership_id` attribute, by @laurenolivia [997](https://github.com/hashicorp/terraform-provider-tfe/pull/997)
99
* `d/tfe_variable_set`: Add `project_ids` attribute, by @Netra2104 [994](https://github.com/hashicorp/terraform-provider-tfe/pull/994)
1010
* **New Data Source**: `d/tfe_teams` is a new data source to return names and IDs of Teams in an Organization, by @isaacmcollins [992](https://github.com/hashicorp/terraform-provider-tfe/pull/992)
11+
* **New Resource**: `r/tfe_workspace_policy_set_exclusion` is a new resource allowing the exclusion of one or more existing workspaces from an existing `policy set`, by @Netra2104 [1033](https://github.com/hashicorp/terraform-provider-tfe/pull/1033)
1112

1213
## v0.48.0 (August 7, 2023)
1314

internal/provider/provider.go

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -106,43 +106,44 @@ func Provider() *schema.Provider {
106106
},
107107

108108
ResourcesMap: map[string]*schema.Resource{
109-
"tfe_admin_organization_settings": resourceTFEAdminOrganizationSettings(),
110-
"tfe_agent_pool": resourceTFEAgentPool(),
111-
"tfe_agent_pool_allowed_workspaces": resourceTFEAgentPoolAllowedWorkspaces(),
112-
"tfe_agent_token": resourceTFEAgentToken(),
113-
"tfe_notification_configuration": resourceTFENotificationConfiguration(),
114-
"tfe_oauth_client": resourceTFEOAuthClient(),
115-
"tfe_organization": resourceTFEOrganization(),
116-
"tfe_organization_membership": resourceTFEOrganizationMembership(),
117-
"tfe_organization_module_sharing": resourceTFEOrganizationModuleSharing(),
118-
"tfe_organization_run_task": resourceTFEOrganizationRunTask(),
119-
"tfe_organization_token": resourceTFEOrganizationToken(),
120-
"tfe_policy": resourceTFEPolicy(),
121-
"tfe_policy_set": resourceTFEPolicySet(),
122-
"tfe_policy_set_parameter": resourceTFEPolicySetParameter(),
123-
"tfe_project": resourceTFEProject(),
124-
"tfe_project_policy_set": resourceTFEProjectPolicySet(),
125-
"tfe_project_variable_set": resourceTFEProjectVariableSet(),
126-
"tfe_registry_module": resourceTFERegistryModule(),
127-
"tfe_no_code_module": resourceTFENoCodeModule(),
128-
"tfe_run_trigger": resourceTFERunTrigger(),
129-
"tfe_sentinel_policy": resourceTFESentinelPolicy(),
130-
"tfe_ssh_key": resourceTFESSHKey(),
131-
"tfe_team": resourceTFETeam(),
132-
"tfe_team_access": resourceTFETeamAccess(),
133-
"tfe_team_organization_member": resourceTFETeamOrganizationMember(),
134-
"tfe_team_organization_members": resourceTFETeamOrganizationMembers(),
135-
"tfe_team_project_access": resourceTFETeamProjectAccess(),
136-
"tfe_team_member": resourceTFETeamMember(),
137-
"tfe_team_members": resourceTFETeamMembers(),
138-
"tfe_team_token": resourceTFETeamToken(),
139-
"tfe_terraform_version": resourceTFETerraformVersion(),
140-
"tfe_workspace": resourceTFEWorkspace(),
141-
"tfe_workspace_run_task": resourceTFEWorkspaceRunTask(),
142-
"tfe_variable_set": resourceTFEVariableSet(),
143-
"tfe_workspace_variable_set": resourceTFEWorkspaceVariableSet(),
144-
"tfe_workspace_policy_set": resourceTFEWorkspacePolicySet(),
145-
"tfe_workspace_run": resourceTFEWorkspaceRun(),
109+
"tfe_admin_organization_settings": resourceTFEAdminOrganizationSettings(),
110+
"tfe_agent_pool": resourceTFEAgentPool(),
111+
"tfe_agent_pool_allowed_workspaces": resourceTFEAgentPoolAllowedWorkspaces(),
112+
"tfe_agent_token": resourceTFEAgentToken(),
113+
"tfe_notification_configuration": resourceTFENotificationConfiguration(),
114+
"tfe_oauth_client": resourceTFEOAuthClient(),
115+
"tfe_organization": resourceTFEOrganization(),
116+
"tfe_organization_membership": resourceTFEOrganizationMembership(),
117+
"tfe_organization_module_sharing": resourceTFEOrganizationModuleSharing(),
118+
"tfe_organization_run_task": resourceTFEOrganizationRunTask(),
119+
"tfe_organization_token": resourceTFEOrganizationToken(),
120+
"tfe_policy": resourceTFEPolicy(),
121+
"tfe_policy_set": resourceTFEPolicySet(),
122+
"tfe_policy_set_parameter": resourceTFEPolicySetParameter(),
123+
"tfe_project": resourceTFEProject(),
124+
"tfe_project_policy_set": resourceTFEProjectPolicySet(),
125+
"tfe_project_variable_set": resourceTFEProjectVariableSet(),
126+
"tfe_registry_module": resourceTFERegistryModule(),
127+
"tfe_no_code_module": resourceTFENoCodeModule(),
128+
"tfe_run_trigger": resourceTFERunTrigger(),
129+
"tfe_sentinel_policy": resourceTFESentinelPolicy(),
130+
"tfe_ssh_key": resourceTFESSHKey(),
131+
"tfe_team": resourceTFETeam(),
132+
"tfe_team_access": resourceTFETeamAccess(),
133+
"tfe_team_organization_member": resourceTFETeamOrganizationMember(),
134+
"tfe_team_organization_members": resourceTFETeamOrganizationMembers(),
135+
"tfe_team_project_access": resourceTFETeamProjectAccess(),
136+
"tfe_team_member": resourceTFETeamMember(),
137+
"tfe_team_members": resourceTFETeamMembers(),
138+
"tfe_team_token": resourceTFETeamToken(),
139+
"tfe_terraform_version": resourceTFETerraformVersion(),
140+
"tfe_workspace": resourceTFEWorkspace(),
141+
"tfe_workspace_run_task": resourceTFEWorkspaceRunTask(),
142+
"tfe_variable_set": resourceTFEVariableSet(),
143+
"tfe_workspace_policy_set": resourceTFEWorkspacePolicySet(),
144+
"tfe_workspace_policy_set_exclusion": resourceTFEWorkspacePolicySetExclusion(),
145+
"tfe_workspace_run": resourceTFEWorkspaceRun(),
146+
"tfe_workspace_variable_set": resourceTFEWorkspaceVariableSet(),
146147
},
147148
ConfigureContextFunc: configure(),
148149
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"log"
11+
"strings"
12+
13+
tfe "github.com/hashicorp/go-tfe"
14+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
15+
)
16+
17+
func resourceTFEWorkspacePolicySetExclusion() *schema.Resource {
18+
return &schema.Resource{
19+
Create: resourceTFEWorkspacePolicySetExclusionCreate,
20+
Read: resourceTFEWorkspacePolicySetExclusionRead,
21+
Delete: resourceTFEWorkspacePolicySetExclusionDelete,
22+
Importer: &schema.ResourceImporter{
23+
StateContext: resourceTFEWorkspacePolicySetExclusionImporter,
24+
},
25+
26+
Schema: map[string]*schema.Schema{
27+
"policy_set_id": {
28+
Type: schema.TypeString,
29+
Required: true,
30+
ForceNew: true,
31+
},
32+
33+
"workspace_id": {
34+
Type: schema.TypeString,
35+
Required: true,
36+
ForceNew: true,
37+
},
38+
},
39+
}
40+
}
41+
42+
func resourceTFEWorkspacePolicySetExclusionCreate(d *schema.ResourceData, meta interface{}) error {
43+
config := meta.(ConfiguredClient)
44+
45+
policySetID := d.Get("policy_set_id").(string)
46+
workspaceExclusionID := d.Get("workspace_id").(string)
47+
48+
policySetAddWorkspaceExclusionsOptions := tfe.PolicySetAddWorkspaceExclusionsOptions{}
49+
policySetAddWorkspaceExclusionsOptions.WorkspaceExclusions = append(policySetAddWorkspaceExclusionsOptions.WorkspaceExclusions, &tfe.Workspace{ID: workspaceExclusionID})
50+
51+
err := config.Client.PolicySets.AddWorkspaceExclusions(ctx, policySetID, policySetAddWorkspaceExclusionsOptions)
52+
if err != nil {
53+
return fmt.Errorf(
54+
"error adding workspace exclusion %s to policy set id %s: %w", workspaceExclusionID, policySetID, err)
55+
}
56+
57+
d.SetId(fmt.Sprintf("%s_%s", workspaceExclusionID, policySetID))
58+
59+
return resourceTFEWorkspacePolicySetExclusionRead(d, meta)
60+
}
61+
62+
func resourceTFEWorkspacePolicySetExclusionRead(d *schema.ResourceData, meta interface{}) error {
63+
config := meta.(ConfiguredClient)
64+
65+
policySetID := d.Get("policy_set_id").(string)
66+
workspaceExclusionsID := d.Get("workspace_id").(string)
67+
68+
log.Printf("[DEBUG] Read configuration of excluded workspace policy set: %s", policySetID)
69+
policySet, err := config.Client.PolicySets.ReadWithOptions(ctx, policySetID, &tfe.PolicySetReadOptions{
70+
Include: []tfe.PolicySetIncludeOpt{tfe.PolicySetWorkspaceExclusions},
71+
})
72+
if err != nil {
73+
if errors.Is(err, tfe.ErrResourceNotFound) {
74+
log.Printf("[DEBUG] Policy set %s no longer exists", policySetID)
75+
d.SetId("")
76+
return nil
77+
}
78+
return fmt.Errorf("error reading configuration of policy set %s: %w", policySetID, err)
79+
}
80+
81+
isWorkspaceExclusionsAttached := false
82+
for _, excludedWorkspace := range policySet.WorkspaceExclusions {
83+
if excludedWorkspace.ID == workspaceExclusionsID {
84+
isWorkspaceExclusionsAttached = true
85+
d.Set("workspace_id", workspaceExclusionsID)
86+
break
87+
}
88+
}
89+
90+
if !isWorkspaceExclusionsAttached {
91+
log.Printf("[DEBUG] Excluded workspace %s not attached to policy set %s. Removing from state.", workspaceExclusionsID, policySetID)
92+
d.SetId("")
93+
return nil
94+
}
95+
96+
d.Set("policy_set_id", policySetID)
97+
return nil
98+
}
99+
100+
func resourceTFEWorkspacePolicySetExclusionDelete(d *schema.ResourceData, meta interface{}) error {
101+
config := meta.(ConfiguredClient)
102+
103+
policySetID := d.Get("policy_set_id").(string)
104+
workspaceExclusionsID := d.Get("workspace_id").(string)
105+
106+
log.Printf("[DEBUG] Removing excluded workspace (%s) from policy set (%s)", workspaceExclusionsID, policySetID)
107+
policySetRemoveWorkspaceExclusionsOptions := tfe.PolicySetRemoveWorkspaceExclusionsOptions{}
108+
policySetRemoveWorkspaceExclusionsOptions.WorkspaceExclusions = append(policySetRemoveWorkspaceExclusionsOptions.WorkspaceExclusions, &tfe.Workspace{ID: workspaceExclusionsID})
109+
110+
err := config.Client.PolicySets.RemoveWorkspaceExclusions(ctx, policySetID, policySetRemoveWorkspaceExclusionsOptions)
111+
if err != nil {
112+
return fmt.Errorf(
113+
"error removing excluded workspace %s from policy set %s: %w", workspaceExclusionsID, policySetID, err)
114+
}
115+
116+
return nil
117+
}
118+
119+
func resourceTFEWorkspacePolicySetExclusionImporter(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
120+
// The format of the import ID is <ORGANIZATION/WORKSPACE NAME/POLICYSET NAME>
121+
splitID := strings.SplitN(d.Id(), "/", 3)
122+
if len(splitID) != 3 {
123+
return nil, fmt.Errorf(
124+
"invalid excluded workspace policy set input format: %s (expected <ORGANIZATION>/<WORKSPACE NAME>/<POLICYSET NAME>)",
125+
splitID,
126+
)
127+
}
128+
129+
organization, wsName, pSName := splitID[0], splitID[1], splitID[2]
130+
131+
config := meta.(ConfiguredClient)
132+
133+
// Ensure the named workspace exists before fetching all the policy sets in the org
134+
_, err := config.Client.Workspaces.Read(ctx, organization, wsName)
135+
if err != nil {
136+
return nil, fmt.Errorf("error reading configuration of the workspace to exclude %s in organization %s: %w", wsName, organization, err)
137+
}
138+
139+
options := &tfe.PolicySetListOptions{Include: []tfe.PolicySetIncludeOpt{tfe.PolicySetWorkspaceExclusions}}
140+
for {
141+
list, err := config.Client.PolicySets.List(ctx, organization, options)
142+
if err != nil {
143+
return nil, fmt.Errorf("error retrieving policy sets: %w", err)
144+
}
145+
for _, policySet := range list.Items {
146+
if policySet.Name != pSName {
147+
continue
148+
}
149+
150+
for _, ws := range policySet.WorkspaceExclusions {
151+
if ws.Name != wsName {
152+
continue
153+
}
154+
155+
d.Set("workspace_id", ws.ID)
156+
d.Set("policy_set_id", policySet.ID)
157+
d.SetId(fmt.Sprintf("%s_%s", ws.ID, policySet.ID))
158+
159+
return []*schema.ResourceData{d}, nil
160+
}
161+
}
162+
163+
// Exit the loop when we've seen all pages.
164+
if list.CurrentPage >= list.TotalPages {
165+
break
166+
}
167+
168+
// Update the page number to get the next page.
169+
options.PageNumber = list.NextPage
170+
}
171+
172+
return nil, fmt.Errorf("excluded workspace %s has not been added to policy set %s", wsName, pSName)
173+
}

0 commit comments

Comments
 (0)