Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for project-owned variable sets #1522

Merged
merged 5 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Unreleased

FEATURES:
* `r/tfe_variable_set`: Add `parent_project_id` attribute, by @mkam [#1522](https://github.com/hashicorp/terraform-provider-tfe/pull/1522)

## v0.61.0

DEPRECATIONS:
Expand Down
10 changes: 10 additions & 0 deletions internal/provider/data_source_variable_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ func dataSourceTFEVariableSet() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"parent_project_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
Expand Down Expand Up @@ -100,6 +106,10 @@ func dataSourceTFEVariableSetRead(d *schema.ResourceData, meta interface{}) erro
d.Set("global", vs.Global)
d.Set("priority", vs.Priority)

if vs.Parent != nil && vs.Parent.Project != nil {
d.Set("parent_project_id", vs.Parent.Project.ID)
}

// Only now include vars and workspaces to cut down on request load.
readOptions := tfe.VariableSetReadOptions{
Include: &[]tfe.VariableSetIncludeOpt{tfe.VariableSetWorkspaces, tfe.VariableSetVars},
Expand Down
49 changes: 49 additions & 0 deletions internal/provider/data_source_variable_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,31 @@ func TestAccTFEVariableSetsDataSource_full(t *testing.T) {
)
}

func TestAccTFEVariableSetsDataSource_ProjectOwned(t *testing.T) {
skipUnlessBeta(t)

rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
orgName := fmt.Sprintf("org-%d", rInt)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: testAccMuxedProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEVariableSetsDataSourceConfig_ProjectOwned(rInt),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.tfe_variable_set.project_owned", "id"),
resource.TestCheckResourceAttr(
"data.tfe_variable_set.project_owned", "organization", orgName),
resource.TestCheckResourceAttrPair(
"data.tfe_variable_set.project_owned", "parent_project_id", "tfe_project.foobar", "id"),
),
},
},
},
)
}

func testAccTFEVariableSetsDataSourceConfig_basic(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
Expand Down Expand Up @@ -130,3 +155,27 @@ func testAccTFEVariableSetsDataSourceConfig_full(rInt int) string {
depends_on = [tfe_variable.envfoo, tfe_project_variable_set.foobar]
}`, rInt, rInt, rInt, rInt)
}

func testAccTFEVariableSetsDataSourceConfig_ProjectOwned(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
name = "org-%d"
email = "[email protected]"
}
resource "tfe_project" "foobar" {
organization = tfe_organization.foobar.id
name = "project-%d"
}

resource "tfe_variable_set" "project_owned" {
name = "project_owned_variable_set_test"
organization = tfe_organization.foobar.id
parent_project_id = tfe_project.foobar.id
}

data "tfe_variable_set" "project_owned" {
name = tfe_variable_set.project_owned.name
organization = tfe_variable_set.project_owned.organization
}
`, rInt, rInt)
}
49 changes: 47 additions & 2 deletions internal/provider/resource_tfe_variable_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package provider

import (
"context"
"fmt"
"log"
"regexp"
Expand All @@ -29,7 +30,16 @@ func resourceTFEVariableSet() *schema.Resource {
StateContext: schema.ImportStatePassthroughContext,
},

CustomizeDiff: customizeDiffIfProviderDefaultOrganizationChanged,
CustomizeDiff: func(c context.Context, d *schema.ResourceDiff, meta interface{}) error {
if err := customizeDiffIfProviderDefaultOrganizationChanged(c, d, meta); err != nil {
return err
}

if err := validateParentProjectID(d); err != nil {
return err
}
return nil
},

Schema: map[string]*schema.Schema{
"name": {
Expand Down Expand Up @@ -68,6 +78,13 @@ func resourceTFEVariableSet() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"parent_project_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
}
}
Expand All @@ -89,6 +106,14 @@ func resourceTFEVariableSetCreate(d *schema.ResourceData, meta interface{}) erro
Priority: tfe.Bool(d.Get("priority").(bool)),
}

if parentProject, ok := d.GetOk("parent_project_id"); ok {
options.Parent = &tfe.Parent{
Project: &tfe.Project{
ID: parentProject.(string),
},
}
}

if description, descriptionSet := d.GetOk("description"); descriptionSet {
options.Description = tfe.String(description.(string))
}
Expand Down Expand Up @@ -151,6 +176,10 @@ func resourceTFEVariableSetRead(d *schema.ResourceData, meta interface{}) error
}
d.Set("workspace_ids", wids)

if variableSet.Parent != nil && variableSet.Parent.Project != nil {
d.Set("parent_project_id", variableSet.Parent.Project.ID)
}

return nil
}

Expand All @@ -168,7 +197,7 @@ func resourceTFEVariableSetUpdate(d *schema.ResourceData, meta interface{}) erro
log.Printf("[DEBUG] Update variable set: %s", d.Id())
_, err := config.Client.VariableSets.Update(ctx, d.Id(), &options)
if err != nil {
return fmt.Errorf("Error updateing variable %s: %w", d.Id(), err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

return fmt.Errorf("Error updating variable %s: %w", d.Id(), err)
}
}

Expand Down Expand Up @@ -212,3 +241,19 @@ func resourceTFEVariableSetDelete(d *schema.ResourceData, meta interface{}) erro
func warnWorkspaceIdsDeprecation() {
log.Printf("[WARN] The workspace_ids field of tfe_variable_set is deprecated as of release 0.33.0 and may be removed in a future version. The preferred method of associating a variable set to a workspace is by using the tfe_workspace_variable_set resource.")
}

func validateParentProjectID(d *schema.ResourceDiff) error {
_, ok := d.GetOk("parent_project_id")
if !ok {
return nil
}

// If parent_project_id is set, global must be false
if global, ok := d.GetOk("global"); ok {
if global.(bool) {
return fmt.Errorf("global must be 'false' when setting parent_project_id")
}
}

return nil
}
74 changes: 74 additions & 0 deletions internal/provider/resource_tfe_variable_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,34 @@ func TestAccTFEVariableSet_import(t *testing.T) {
})
}

func TestAccTFEVariableSet_project_owned(t *testing.T) {
skipUnlessBeta(t)
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckTFEVariableSetDestroy,
Steps: []resource.TestStep{
{
Config: testACCTFEVariableSet_ProjectOwned(rInt),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(
"tfe_variable_set.project_owned", "parent_project_id", "tfe_project.foobar", "id"),
),
},

{
Config: testACCTFEVariableSet_UpdateProjectOwned(rInt),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(
"tfe_variable_set.project_owned", "parent_project_id", "tfe_project.updated", "id"),
),
},
},
})
}

func testAccCheckTFEVariableSetExists(
n string, variableSet *tfe.VariableSet) resource.TestCheckFunc {
return func(s *terraform.State) error {
Expand Down Expand Up @@ -325,3 +353,49 @@ func testAccTFEVariableSet_update(rInt int) string {
organization = tfe_organization.foobar.id
}`, rInt)
}

func testACCTFEVariableSet_ProjectOwned(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
name = "tst-terraform-%d"
email = "[email protected]"
}

resource "tfe_project" "foobar" {
organization = tfe_organization.foobar.id
name = "tst-terraform-%d"
}

resource "tfe_variable_set" "project_owned" {
name = "project_owned_variable_set_test"
description = "a project-owned test variable set"
organization = tfe_organization.foobar.id
parent_project_id = tfe_project.foobar.id
}`, rInt, rInt)
}

func testACCTFEVariableSet_UpdateProjectOwned(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
name = "tst-terraform-%d"
email = "[email protected]"
}

resource "tfe_project" "foobar" {
organization = tfe_organization.foobar.id
name = "tst-terraform-%d"
}

resource "tfe_project" "updated" {
organization = tfe_organization.foobar.id
name = "updated-%d"
}

resource "tfe_variable_set" "project_owned" {
name = "project_owned_variable_set_test"
description = "a project-owned test variable set"
organization = tfe_organization.foobar.id
global = false
parent_project_id = tfe_project.updated.id
}`, rInt, rInt, rInt)
}
1 change: 1 addition & 0 deletions website/docs/d/variable_set.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ The following arguments are supported:
* `workspace_ids` - IDs of the workspaces that use the variable set.
* `variable_ids` - IDs of the variables attached to the variable set.
* `project_ids` - IDs of the projects that use the variable set.
* `parent_project_id` - ID of the project that owns the variable set.
6 changes: 5 additions & 1 deletion website/docs/r/project_variable_set.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ description: |-

# tfe_project_variable_set

Adds and removes variable sets from a project
Adds and removes a project from a variable set's scope.

-> **Note:** This resource controls whether a project has access to a variable set, not whether
a project owns the variable set. Ownership is specified by setting the `parent_project_id` on the
`tfe_variable_set` resource.

## Example Usage

Expand Down
60 changes: 60 additions & 0 deletions website/docs/r/variable_set.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,64 @@ resource "tfe_variable" "test-b" {
}
```

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if adding both makes the example section too long, but I thought it would be valuable to make it clear that a project-owned varset doesn't necessarily/automatically apply itself to the project and that it can be scoped to workspaces.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, great idea to make that behavior clear.

Creating a project-owned variable set that is applied to all workspaces in the project:

```hcl
resource "tfe_organization" "test" {
name = "my-org-name"
email = "[email protected]"
}

resource "tfe_project" "test" {
organization = tfe_organization.test.name
name = "projectname"
}

resource "tfe_variable_set" "test" {
name = "Project-owned Varset"
description = "Varset that is owned and managed by a project."
organization = tfe_organization.test.name
parent_project_id = tfe_project.test.id
}

resource "tfe_project_variable_set" "test" {
project_id = tfe_project.test.id
variable_set_id = tfe_variable_set.test.id
}
```

Creating a project-owned variable set that is applied to specific workspaces:

```hcl
resource "tfe_organization" "test" {
name = "my-org-name"
email = "[email protected]"
}

resource "tfe_project" "test" {
organization = tfe_organization.test.name
name = "projectname"
}

resource "tfe_workspace" "test" {
name = "my-workspace-name"
organization = tfe_organization.test.name
project_id = tfe_project.test.id
}

resource "tfe_variable_set" "test" {
name = "Project-owned Varset"
description = "Varset that is owned and managed by a project."
organization = tfe_organization.test.name
parent_project_id = tfe_project.test.id
}

resource "tfe_workspace_variable_set" "test" {
workspace_id = tfe_workspace.test.id
variable_set_id = tfe_variable_set.test.id
}
```

## Argument Reference

The following arguments are supported:
Expand All @@ -139,6 +197,8 @@ The following arguments are supported:
Must not be set if `global` is set. This argument is mutually exclusive with using the resource
[tfe_workspace_variable_set](workspace_variable_set.html) which is the preferred method of associating a workspace
with a variable set.
* `parent_project_id` - (Optional) ID of the project that should own the variable set. If set, than the value of `global` must be `false`.
To assign whether a variable set should be applied to a project, use the [`tfe_project_variable_set`](project_variable_set.html) resource.

## Attributes Reference

Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/workspace_variable_set.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: |-

# tfe_workspace_variable_set

Adds and removes variable sets from a workspace
Adds and removes a workspace from a variable set's scope.
Copy link
Contributor Author

@mkam mkam Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed for consistency with tfe_project_variable_set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, especially because making it consistent on the project side to read "Adds and removes variable sets from a project" could be ambiguous.


-> **Note:** `tfe_variable_set` has a deprecated argument `workspace_ids` that should not be used alongside this resource. They attempt to manage the same attachments and are mutually exclusive.

Expand Down
Loading