Skip to content
Open
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ DEPRECATIONS:
* `r/tfe_opa_version`: The `url` and `sha` attributes are deprecated and will be removed in a future version. Use the `archs` attribute to specify architecture-specific binaries going forward, by @kelsi-hoyle [1762](https://github.com/hashicorp/terraform-provider-tfe/pull/1762)
* `r/tfe_sentinel_version`: The `url` and `sha` attributes are deprecated and will be removed in a future version. Use the `archs` attribute to specify architecture-specific binaries going forward, by @kelsi-hoyle [1762](https://github.com/hashicorp/terraform-provider-tfe/pull/1762)
* `r/tfe_oauth_client`: The `oauth_token` attribute no longer triggers resource replacement unless combined with other replacement-triggering attributes. Use `terraform apply -replace` to force replacement. By @lilincmu [#1825](https://github.com/hashicorp/terraform-provider-tfe/pull/1825)
* `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)
* `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)
* `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)
* `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)
* `r/tfe_test_variable`: Add missing argument reference and attributes documentation ([#1625](https://github.com/hashicorp/terraform-provider-tfe/issues/1625))

## v0.68.3

BUG FIXES:

* `r/tfe_notification_configuration`: update url attribute to be sensitive, by @jillirami [#1799](https://github.com/hashicorp/terraform-provider-tfe/pull/1799)
Expand Down
24 changes: 24 additions & 0 deletions internal/provider/data_source_agent_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ func dataSourceTFEAgentPool() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"allowed_project_ids": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"excluded_workspace_ids": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
Expand All @@ -59,11 +71,23 @@ func dataSourceTFEAgentPoolRead(d *schema.ResourceData, meta interface{}) error
d.SetId(pool.ID)
d.Set("organization_scoped", pool.OrganizationScoped)

var allowedProjectIDs []string
for _, allowedProjectID := range pool.AllowedProjects {
allowedProjectIDs = append(allowedProjectIDs, allowedProjectID.ID)
}
d.Set("allowed_project_ids", allowedProjectIDs)

var allowedWorkspaceIDs []string
for _, allowedWorkspaceID := range pool.AllowedWorkspaces {
allowedWorkspaceIDs = append(allowedWorkspaceIDs, allowedWorkspaceID.ID)
}
d.Set("allowed_workspace_ids", allowedWorkspaceIDs)

var excludedWorkspaceIDs []string
for _, excludedWorkspaceID := range pool.ExcludedWorkspaces {
excludedWorkspaceIDs = append(excludedWorkspaceIDs, excludedWorkspaceID.ID)
}
d.Set("excluded_workspace_ids", excludedWorkspaceIDs)

return nil
}
124 changes: 124 additions & 0 deletions internal/provider/data_source_agent_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,90 @@ func TestAccTFEAgentPoolDataSource_allowed_workspaces(t *testing.T) {
})
}

func TestAccTFEAgentPoolDataSource_allowed_projects(t *testing.T) {
skipIfEnterprise(t)

tfeClient, err := getClientUsingEnv()
if err != nil {
t.Fatal(err)
}

org, orgCleanup := createBusinessOrganization(t, tfeClient)
t.Cleanup(orgCleanup)

rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

ws, err := tfeClient.Projects.Create(ctx, org.Name, tfe.ProjectCreateOptions{
Name: fmt.Sprintf("tst-proj-test-%d", rInt),
})
if err != nil {
t.Fatal(err)
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccMuxedProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEAgentPoolDataSourceAllowedProjectsConfig(org.Name, rInt, ws.ID),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.tfe_agent_pool.foobar", "id"),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "name", fmt.Sprintf("agent-pool-test-%d", rInt)),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "organization", org.Name),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "organization_scoped", "false"),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "allowed_project_ids.0", ws.ID),
),
},
},
})
}

func TestAccTFEAgentPoolDataSource_excluded_workspaces(t *testing.T) {
skipIfEnterprise(t)

tfeClient, err := getClientUsingEnv()
if err != nil {
t.Fatal(err)
}

org, orgCleanup := createBusinessOrganization(t, tfeClient)
t.Cleanup(orgCleanup)

rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

ws, err := tfeClient.Workspaces.Create(ctx, org.Name, tfe.WorkspaceCreateOptions{
Name: tfe.String(fmt.Sprintf("tst-workspace-test-%d", rInt)),
})
if err != nil {
t.Fatal(err)
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccMuxedProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEAgentPoolDataSourceExcludedWorkspacesConfig(org.Name, rInt, ws.ID),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.tfe_agent_pool.foobar", "id"),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "name", fmt.Sprintf("agent-pool-test-%d", rInt)),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "organization", org.Name),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "organization_scoped", "false"),
resource.TestCheckResourceAttr(
"data.tfe_agent_pool.foobar", "excluded_workspace_ids.0", ws.ID),
),
},
},
})
}

func testAccTFEAgentPoolDataSourceConfig(organization string, rInt int) string {
return fmt.Sprintf(`
resource "tfe_agent_pool" "foobar" {
Expand Down Expand Up @@ -121,3 +205,43 @@ data "tfe_agent_pool" "foobar" {
depends_on = [ tfe_agent_pool_allowed_workspaces.foobar ]
}`, rInt, organization, workspaceID, organization)
}

func testAccTFEAgentPoolDataSourceAllowedProjectsConfig(organization string, rInt int, projectID string) string {
return fmt.Sprintf(`
resource "tfe_agent_pool" "foobar" {
name = "agent-pool-test-%d"
organization = "%s"
organization_scoped = false
}

resource "tfe_agent_pool_allowed_projects" "foobar" {
agent_pool_id = tfe_agent_pool.foobar.id
allowed_project_ids = ["%s"]
}

data "tfe_agent_pool" "foobar" {
name = tfe_agent_pool.foobar.name
organization = "%s"
depends_on = [ tfe_agent_pool_allowed_projects.foobar ]
}`, rInt, organization, projectID, organization)
}

func testAccTFEAgentPoolDataSourceExcludedWorkspacesConfig(organization string, rInt int, workspaceID string) string {
return fmt.Sprintf(`
resource "tfe_agent_pool" "foobar" {
name = "agent-pool-test-%d"
organization = "%s"
organization_scoped = false
}

resource "tfe_agent_pool_excluded_workspaces" "foobar" {
agent_pool_id = tfe_agent_pool.foobar.id
excluded_workspace_ids = ["%s"]
}

data "tfe_agent_pool" "foobar" {
name = tfe_agent_pool.foobar.name
organization = "%s"
depends_on = [ tfe_agent_pool_excluded_workspaces.foobar ]
}`, rInt, organization, workspaceID, organization)
}
2 changes: 2 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ func Provider() *schema.Provider {
ResourcesMap: map[string]*schema.Resource{
"tfe_admin_organization_settings": resourceTFEAdminOrganizationSettings(),
"tfe_agent_pool": resourceTFEAgentPool(),
"tfe_agent_pool_allowed_projects": resourceTFEAgentPoolAllowedProjects(),
"tfe_agent_pool_allowed_workspaces": resourceTFEAgentPoolAllowedWorkspaces(),
"tfe_agent_pool_excluded_workspaces": resourceTFEAgentPoolExcludedWorkspaces(),
"tfe_agent_token": resourceTFEAgentToken(),
"tfe_oauth_client": resourceTFEOAuthClient(),
"tfe_organization": resourceTFEOrganization(),
Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider_next.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Res
NewWorkspaceRunTaskResource,
NewNotificationConfigurationResource,
NewTeamTokenResource,
NewProjectSettingsResource,
NewTerraformVersionResource,
NewOPAVersionResource,
NewsentinelVersionResource,
Expand Down
144 changes: 144 additions & 0 deletions internal/provider/resource_tfe_agent_pool_allowed_projects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

// NOTE: This is a legacy resource and should be migrated to the Plugin
// Framework if substantial modifications are planned. See
// docs/new-resources.md if planning to use this code as boilerplate for
// a new resource.

package provider

import (
"errors"
"fmt"
"log"

"github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceTFEAgentPoolAllowedProjects() *schema.Resource {
return &schema.Resource{
Create: resourceTFEAgentPoolAllowedProjectsCreate,
Read: resourceTFEAgentPoolAllowedProjectsRead,
Update: resourceTFEAgentPoolAllowedProjectsUpdate,
Delete: resourceTFEAgentPoolAllowedProjectsDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"agent_pool_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"allowed_project_ids": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}

func resourceTFEAgentPoolAllowedProjectsCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(ConfiguredClient)

apID := d.Get("agent_pool_id").(string)

// Create a new options struct.
options := tfe.AgentPoolAllowedProjectsUpdateOptions{}

if allowedProjectIDs, allowedProjectSet := d.GetOk("allowed_project_ids"); allowedProjectSet {
options.AllowedProjects = []*tfe.Project{}
for _, projectID := range allowedProjectIDs.(*schema.Set).List() {
if val, ok := projectID.(string); ok {
options.AllowedProjects = append(options.AllowedProjects, &tfe.Project{ID: val})
}
}
}

log.Printf("[DEBUG] Update agent pool: %s", apID)
_, err := config.Client.AgentPools.UpdateAllowedProjects(ctx, apID, options)
if err != nil {
return fmt.Errorf("Error updating agent pool %s: %w", apID, err)
}

d.SetId(apID)

return nil
}

func resourceTFEAgentPoolAllowedProjectsRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(ConfiguredClient)

agentPool, err := config.Client.AgentPools.Read(ctx, d.Id())
if err != nil {
if errors.Is(err, tfe.ErrResourceNotFound) {
log.Printf("[DEBUG] agent pool %s no longer exists", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error reading configuration of agent pool %s: %w", d.Id(), err)
}

var allowedProjectIDs []string
for _, project := range agentPool.AllowedProjects {
allowedProjectIDs = append(allowedProjectIDs, project.ID)
}
d.Set("allowed_project_ids", allowedProjectIDs)
d.Set("agent_pool_id", agentPool.ID)

return nil
}

func resourceTFEAgentPoolAllowedProjectsUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(ConfiguredClient)

apID := d.Get("agent_pool_id").(string)

// Create a new options struct.
options := tfe.AgentPoolAllowedProjectsUpdateOptions{
AllowedProjects: []*tfe.Project{},
}

if allowedProjectIDs, allowedProjectSet := d.GetOk("allowed_project_ids"); allowedProjectSet {
options.AllowedProjects = []*tfe.Project{}
for _, projectID := range allowedProjectIDs.(*schema.Set).List() {
if val, ok := projectID.(string); ok {
options.AllowedProjects = append(options.AllowedProjects, &tfe.Project{ID: val})
}
}
}

log.Printf("[DEBUG] Update agent pool: %s", apID)
_, err := config.Client.AgentPools.UpdateAllowedProjects(ctx, apID, options)
if err != nil {
return fmt.Errorf("Error updating agent pool %s: %w", apID, err)
}

d.SetId(apID)

return nil
}

func resourceTFEAgentPoolAllowedProjectsDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(ConfiguredClient)

apID := d.Get("agent_pool_id").(string)

// Create a new options struct.
options := tfe.AgentPoolAllowedProjectsUpdateOptions{
AllowedProjects: []*tfe.Project{},
}

log.Printf("[DEBUG] Update agent pool: %s", apID)
_, err := config.Client.AgentPools.UpdateAllowedProjects(ctx, apID, options)
if err != nil {
return fmt.Errorf("Error updating agent pool %s: %w", apID, err)
}

return nil
}
Loading
Loading