Skip to content

Commit 0fd8c7b

Browse files
Merge pull request #1822 from hashicorp/tylerwolf/project-agent-pools
Add default agent pool and execution mode support to projects
2 parents 7e26414 + c4b85bb commit 0fd8c7b

15 files changed

+1850
-0
lines changed

CHANGELOG.md

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

3+
FEATURES:
4+
* `d/tfe_agent_pool`: Adds the `allowed_project_ids` and `excluded_workspace_ids` attributes, by @tylerworlf [#1822](https://github.com/hashicorp/terraform-provider-tfe/pull/1822)
5+
* `r/tfe_agent_pool_allowed_projects`: Adds support for scoping agent pools to projects, by @tylerworlf [#1822](https://github.com/hashicorp/terraform-provider-tfe/pull/1822)
6+
* `r/tfe_agent_pool_excluded_workspaces`: Adds support for excluding workspaces from the scope of agent pools, by @tylerworlf [#1822](https://github.com/hashicorp/terraform-provider-tfe/pull/1822)
7+
* `r/tfe_project_settings`: Adds support for managing project settings. This initially supports setting a `default_execution_mode` and `default_agent_pool_id` which override the organization defaults. When not specified in the configuration, the organization defaults will be used and can be read from the resource. by @JarrettSpiker [#1822](Thttps://github.com/hashicorp/terraform-provider-tfe/pull/1822)
8+
39
BUG FIXES:
410
* `r/tfe_workspace_settings`: Prevent unintended clearing of workspace-level tags on the first apply when tags is unset by making tag updates sparse. By @shwetamurali [#1851](https://github.com/hashicorp/terraform-provider-tfe/pull/1851)
511

internal/provider/data_source_agent_pool.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ func dataSourceTFEAgentPool() *schema.Resource {
3737
Computed: true,
3838
Elem: &schema.Schema{Type: schema.TypeString},
3939
},
40+
41+
"allowed_project_ids": {
42+
Type: schema.TypeSet,
43+
Computed: true,
44+
Elem: &schema.Schema{Type: schema.TypeString},
45+
},
46+
47+
"excluded_workspace_ids": {
48+
Type: schema.TypeSet,
49+
Computed: true,
50+
Elem: &schema.Schema{Type: schema.TypeString},
51+
},
4052
},
4153
}
4254
}
@@ -59,11 +71,23 @@ func dataSourceTFEAgentPoolRead(d *schema.ResourceData, meta interface{}) error
5971
d.SetId(pool.ID)
6072
d.Set("organization_scoped", pool.OrganizationScoped)
6173

74+
var allowedProjectIDs []string
75+
for _, allowedProjectID := range pool.AllowedProjects {
76+
allowedProjectIDs = append(allowedProjectIDs, allowedProjectID.ID)
77+
}
78+
d.Set("allowed_project_ids", allowedProjectIDs)
79+
6280
var allowedWorkspaceIDs []string
6381
for _, allowedWorkspaceID := range pool.AllowedWorkspaces {
6482
allowedWorkspaceIDs = append(allowedWorkspaceIDs, allowedWorkspaceID.ID)
6583
}
6684
d.Set("allowed_workspace_ids", allowedWorkspaceIDs)
6785

86+
var excludedWorkspaceIDs []string
87+
for _, excludedWorkspaceID := range pool.ExcludedWorkspaces {
88+
excludedWorkspaceIDs = append(excludedWorkspaceIDs, excludedWorkspaceID.ID)
89+
}
90+
d.Set("excluded_workspace_ids", excludedWorkspaceIDs)
91+
6892
return nil
6993
}

internal/provider/data_source_agent_pool_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,90 @@ func TestAccTFEAgentPoolDataSource_allowed_workspaces(t *testing.T) {
8989
})
9090
}
9191

92+
func TestAccTFEAgentPoolDataSource_allowed_projects(t *testing.T) {
93+
skipIfEnterprise(t)
94+
95+
tfeClient, err := getClientUsingEnv()
96+
if err != nil {
97+
t.Fatal(err)
98+
}
99+
100+
org, orgCleanup := createBusinessOrganization(t, tfeClient)
101+
t.Cleanup(orgCleanup)
102+
103+
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
104+
105+
ws, err := tfeClient.Projects.Create(ctx, org.Name, tfe.ProjectCreateOptions{
106+
Name: fmt.Sprintf("tst-proj-test-%d", rInt),
107+
})
108+
if err != nil {
109+
t.Fatal(err)
110+
}
111+
112+
resource.Test(t, resource.TestCase{
113+
PreCheck: func() { testAccPreCheck(t) },
114+
ProtoV6ProviderFactories: testAccMuxedProviders,
115+
Steps: []resource.TestStep{
116+
{
117+
Config: testAccTFEAgentPoolDataSourceAllowedProjectsConfig(org.Name, rInt, ws.ID),
118+
Check: resource.ComposeAggregateTestCheckFunc(
119+
resource.TestCheckResourceAttrSet("data.tfe_agent_pool.foobar", "id"),
120+
resource.TestCheckResourceAttr(
121+
"data.tfe_agent_pool.foobar", "name", fmt.Sprintf("agent-pool-test-%d", rInt)),
122+
resource.TestCheckResourceAttr(
123+
"data.tfe_agent_pool.foobar", "organization", org.Name),
124+
resource.TestCheckResourceAttr(
125+
"data.tfe_agent_pool.foobar", "organization_scoped", "false"),
126+
resource.TestCheckResourceAttr(
127+
"data.tfe_agent_pool.foobar", "allowed_project_ids.0", ws.ID),
128+
),
129+
},
130+
},
131+
})
132+
}
133+
134+
func TestAccTFEAgentPoolDataSource_excluded_workspaces(t *testing.T) {
135+
skipIfEnterprise(t)
136+
137+
tfeClient, err := getClientUsingEnv()
138+
if err != nil {
139+
t.Fatal(err)
140+
}
141+
142+
org, orgCleanup := createBusinessOrganization(t, tfeClient)
143+
t.Cleanup(orgCleanup)
144+
145+
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
146+
147+
ws, err := tfeClient.Workspaces.Create(ctx, org.Name, tfe.WorkspaceCreateOptions{
148+
Name: tfe.String(fmt.Sprintf("tst-workspace-test-%d", rInt)),
149+
})
150+
if err != nil {
151+
t.Fatal(err)
152+
}
153+
154+
resource.Test(t, resource.TestCase{
155+
PreCheck: func() { testAccPreCheck(t) },
156+
ProtoV6ProviderFactories: testAccMuxedProviders,
157+
Steps: []resource.TestStep{
158+
{
159+
Config: testAccTFEAgentPoolDataSourceExcludedWorkspacesConfig(org.Name, rInt, ws.ID),
160+
Check: resource.ComposeAggregateTestCheckFunc(
161+
resource.TestCheckResourceAttrSet("data.tfe_agent_pool.foobar", "id"),
162+
resource.TestCheckResourceAttr(
163+
"data.tfe_agent_pool.foobar", "name", fmt.Sprintf("agent-pool-test-%d", rInt)),
164+
resource.TestCheckResourceAttr(
165+
"data.tfe_agent_pool.foobar", "organization", org.Name),
166+
resource.TestCheckResourceAttr(
167+
"data.tfe_agent_pool.foobar", "organization_scoped", "false"),
168+
resource.TestCheckResourceAttr(
169+
"data.tfe_agent_pool.foobar", "excluded_workspace_ids.0", ws.ID),
170+
),
171+
},
172+
},
173+
})
174+
}
175+
92176
func testAccTFEAgentPoolDataSourceConfig(organization string, rInt int) string {
93177
return fmt.Sprintf(`
94178
resource "tfe_agent_pool" "foobar" {
@@ -121,3 +205,43 @@ data "tfe_agent_pool" "foobar" {
121205
depends_on = [ tfe_agent_pool_allowed_workspaces.foobar ]
122206
}`, rInt, organization, workspaceID, organization)
123207
}
208+
209+
func testAccTFEAgentPoolDataSourceAllowedProjectsConfig(organization string, rInt int, projectID string) string {
210+
return fmt.Sprintf(`
211+
resource "tfe_agent_pool" "foobar" {
212+
name = "agent-pool-test-%d"
213+
organization = "%s"
214+
organization_scoped = false
215+
}
216+
217+
resource "tfe_agent_pool_allowed_projects" "foobar" {
218+
agent_pool_id = tfe_agent_pool.foobar.id
219+
allowed_project_ids = ["%s"]
220+
}
221+
222+
data "tfe_agent_pool" "foobar" {
223+
name = tfe_agent_pool.foobar.name
224+
organization = "%s"
225+
depends_on = [ tfe_agent_pool_allowed_projects.foobar ]
226+
}`, rInt, organization, projectID, organization)
227+
}
228+
229+
func testAccTFEAgentPoolDataSourceExcludedWorkspacesConfig(organization string, rInt int, workspaceID string) string {
230+
return fmt.Sprintf(`
231+
resource "tfe_agent_pool" "foobar" {
232+
name = "agent-pool-test-%d"
233+
organization = "%s"
234+
organization_scoped = false
235+
}
236+
237+
resource "tfe_agent_pool_excluded_workspaces" "foobar" {
238+
agent_pool_id = tfe_agent_pool.foobar.id
239+
excluded_workspace_ids = ["%s"]
240+
}
241+
242+
data "tfe_agent_pool" "foobar" {
243+
name = tfe_agent_pool.foobar.name
244+
organization = "%s"
245+
depends_on = [ tfe_agent_pool_excluded_workspaces.foobar ]
246+
}`, rInt, organization, workspaceID, organization)
247+
}

internal/provider/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ func Provider() *schema.Provider {
107107
ResourcesMap: map[string]*schema.Resource{
108108
"tfe_admin_organization_settings": resourceTFEAdminOrganizationSettings(),
109109
"tfe_agent_pool": resourceTFEAgentPool(),
110+
"tfe_agent_pool_allowed_projects": resourceTFEAgentPoolAllowedProjects(),
110111
"tfe_agent_pool_allowed_workspaces": resourceTFEAgentPoolAllowedWorkspaces(),
112+
"tfe_agent_pool_excluded_workspaces": resourceTFEAgentPoolExcludedWorkspaces(),
111113
"tfe_agent_token": resourceTFEAgentToken(),
112114
"tfe_oauth_client": resourceTFEOAuthClient(),
113115
"tfe_organization": resourceTFEOrganization(),

internal/provider/provider_next.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Res
168168
NewWorkspaceRunTaskResource,
169169
NewNotificationConfigurationResource,
170170
NewTeamTokenResource,
171+
NewProjectSettingsResource,
171172
NewTerraformVersionResource,
172173
NewOPAVersionResource,
173174
NewsentinelVersionResource,
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
// NOTE: This is a legacy resource and should be migrated to the Plugin
5+
// Framework if substantial modifications are planned. See
6+
// docs/new-resources.md if planning to use this code as boilerplate for
7+
// a new resource.
8+
9+
package provider
10+
11+
import (
12+
"errors"
13+
"fmt"
14+
"log"
15+
16+
"github.com/hashicorp/go-tfe"
17+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
18+
)
19+
20+
func resourceTFEAgentPoolAllowedProjects() *schema.Resource {
21+
return &schema.Resource{
22+
Create: resourceTFEAgentPoolAllowedProjectsCreate,
23+
Read: resourceTFEAgentPoolAllowedProjectsRead,
24+
Update: resourceTFEAgentPoolAllowedProjectsUpdate,
25+
Delete: resourceTFEAgentPoolAllowedProjectsDelete,
26+
Importer: &schema.ResourceImporter{
27+
StateContext: schema.ImportStatePassthroughContext,
28+
},
29+
30+
Schema: map[string]*schema.Schema{
31+
"agent_pool_id": {
32+
Type: schema.TypeString,
33+
Required: true,
34+
ForceNew: true,
35+
},
36+
37+
"allowed_project_ids": {
38+
Type: schema.TypeSet,
39+
Required: true,
40+
Elem: &schema.Schema{Type: schema.TypeString},
41+
},
42+
},
43+
}
44+
}
45+
46+
func resourceTFEAgentPoolAllowedProjectsCreate(d *schema.ResourceData, meta interface{}) error {
47+
config := meta.(ConfiguredClient)
48+
49+
apID := d.Get("agent_pool_id").(string)
50+
51+
// Create a new options struct.
52+
options := tfe.AgentPoolAllowedProjectsUpdateOptions{}
53+
54+
if allowedProjectIDs, allowedProjectSet := d.GetOk("allowed_project_ids"); allowedProjectSet {
55+
options.AllowedProjects = []*tfe.Project{}
56+
for _, projectID := range allowedProjectIDs.(*schema.Set).List() {
57+
if val, ok := projectID.(string); ok {
58+
options.AllowedProjects = append(options.AllowedProjects, &tfe.Project{ID: val})
59+
}
60+
}
61+
}
62+
63+
log.Printf("[DEBUG] Update agent pool: %s", apID)
64+
_, err := config.Client.AgentPools.UpdateAllowedProjects(ctx, apID, options)
65+
if err != nil {
66+
return fmt.Errorf("Error updating agent pool %s: %w", apID, err)
67+
}
68+
69+
d.SetId(apID)
70+
71+
return nil
72+
}
73+
74+
func resourceTFEAgentPoolAllowedProjectsRead(d *schema.ResourceData, meta interface{}) error {
75+
config := meta.(ConfiguredClient)
76+
77+
agentPool, err := config.Client.AgentPools.Read(ctx, d.Id())
78+
if err != nil {
79+
if errors.Is(err, tfe.ErrResourceNotFound) {
80+
log.Printf("[DEBUG] agent pool %s no longer exists", d.Id())
81+
d.SetId("")
82+
return nil
83+
}
84+
return fmt.Errorf("Error reading configuration of agent pool %s: %w", d.Id(), err)
85+
}
86+
87+
var allowedProjectIDs []string
88+
for _, project := range agentPool.AllowedProjects {
89+
allowedProjectIDs = append(allowedProjectIDs, project.ID)
90+
}
91+
d.Set("allowed_project_ids", allowedProjectIDs)
92+
d.Set("agent_pool_id", agentPool.ID)
93+
94+
return nil
95+
}
96+
97+
func resourceTFEAgentPoolAllowedProjectsUpdate(d *schema.ResourceData, meta interface{}) error {
98+
config := meta.(ConfiguredClient)
99+
100+
apID := d.Get("agent_pool_id").(string)
101+
102+
// Create a new options struct.
103+
options := tfe.AgentPoolAllowedProjectsUpdateOptions{
104+
AllowedProjects: []*tfe.Project{},
105+
}
106+
107+
if allowedProjectIDs, allowedProjectSet := d.GetOk("allowed_project_ids"); allowedProjectSet {
108+
options.AllowedProjects = []*tfe.Project{}
109+
for _, projectID := range allowedProjectIDs.(*schema.Set).List() {
110+
if val, ok := projectID.(string); ok {
111+
options.AllowedProjects = append(options.AllowedProjects, &tfe.Project{ID: val})
112+
}
113+
}
114+
}
115+
116+
log.Printf("[DEBUG] Update agent pool: %s", apID)
117+
_, err := config.Client.AgentPools.UpdateAllowedProjects(ctx, apID, options)
118+
if err != nil {
119+
return fmt.Errorf("Error updating agent pool %s: %w", apID, err)
120+
}
121+
122+
d.SetId(apID)
123+
124+
return nil
125+
}
126+
127+
func resourceTFEAgentPoolAllowedProjectsDelete(d *schema.ResourceData, meta interface{}) error {
128+
config := meta.(ConfiguredClient)
129+
130+
apID := d.Get("agent_pool_id").(string)
131+
132+
// Create a new options struct.
133+
options := tfe.AgentPoolAllowedProjectsUpdateOptions{
134+
AllowedProjects: []*tfe.Project{},
135+
}
136+
137+
log.Printf("[DEBUG] Update agent pool: %s", apID)
138+
_, err := config.Client.AgentPools.UpdateAllowedProjects(ctx, apID, options)
139+
if err != nil {
140+
return fmt.Errorf("Error updating agent pool %s: %w", apID, err)
141+
}
142+
143+
return nil
144+
}

0 commit comments

Comments
 (0)