diff --git a/go.mod b/go.mod index a3bfda1d5..055788f44 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-slug v0.8.0 - github.com/hashicorp/go-tfe v1.0.0 + github.com/hashicorp/go-tfe v1.1.0 github.com/hashicorp/go-version v1.4.0 github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce github.com/hashicorp/hcl/v2 v2.10.0 // indirect diff --git a/go.sum b/go.sum index c9ac94ce8..35b7a5f69 100644 --- a/go.sum +++ b/go.sum @@ -212,22 +212,10 @@ github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiw github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-slug v0.7.0 h1:8HIi6oreWPtnhpYd8lIGQBgp4rXzDWQTOhfILZm+nok= -github.com/hashicorp/go-slug v0.7.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= github.com/hashicorp/go-slug v0.8.0 h1:h7AGtXVAI/cJ/Wwa/JQQaftQnWQmZbAzkzgZeZVVmLw= github.com/hashicorp/go-slug v0.8.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= -github.com/hashicorp/go-tfe v0.22.0 h1:FBK3LscU90EhQGS/p2NJJdJt2GzwiGNqHgex8SjZ+LM= -github.com/hashicorp/go-tfe v0.22.0/go.mod h1:gyXLXbpBVxA2F/6opah8XBsOkZJxHYQmghl0OWi8keI= -github.com/hashicorp/go-tfe v0.24.0 h1:7RyYTafFXGN6I6ayASJOpw6pARtKKSPdA9KRiovKQRM= -github.com/hashicorp/go-tfe v0.24.0/go.mod h1:gyXLXbpBVxA2F/6opah8XBsOkZJxHYQmghl0OWi8keI= -github.com/hashicorp/go-tfe v0.25.0 h1:eAqKG6hpxfjiw4KJheTeFhevov1avDPJFDDI0F/OAJU= -github.com/hashicorp/go-tfe v0.25.0/go.mod h1:gyXLXbpBVxA2F/6opah8XBsOkZJxHYQmghl0OWi8keI= -github.com/hashicorp/go-tfe v0.26.0 h1:6vQshg2NW5CkN4fkM64qhX+Z5Ua7ip74n8nAJRlhKKg= -github.com/hashicorp/go-tfe v0.26.0/go.mod h1:gyXLXbpBVxA2F/6opah8XBsOkZJxHYQmghl0OWi8keI= -github.com/hashicorp/go-tfe v1.0.0-rc1 h1:ZUAfDF5en/oayJJByxm3lQCsIRbCPNpf9RqBFhb3Crs= -github.com/hashicorp/go-tfe v1.0.0-rc1/go.mod h1:gyXLXbpBVxA2F/6opah8XBsOkZJxHYQmghl0OWi8keI= -github.com/hashicorp/go-tfe v1.0.0 h1:CmwoHrOs7WJfD/yEmVjJ65+dyKeVRrgvRHBLVSQQ6Ks= -github.com/hashicorp/go-tfe v1.0.0/go.mod h1:tJF/OlAXzVbmjiimAPLplSLgwg6kZDUOy0MzHuMwvF4= +github.com/hashicorp/go-tfe v1.1.0 h1:MGMdaQDdB9sWMTXAWLpdRmi5djLALR6qS5o5MC2u3bQ= +github.com/hashicorp/go-tfe v1.1.0/go.mod h1:tJF/OlAXzVbmjiimAPLplSLgwg6kZDUOy0MzHuMwvF4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= @@ -567,10 +555,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/tfe/data_source_variable_set.go b/tfe/data_source_variable_set.go new file mode 100644 index 000000000..d6d2f2d05 --- /dev/null +++ b/tfe/data_source_variable_set.go @@ -0,0 +1,116 @@ +package tfe + +import ( + "fmt" + + tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceTFEVariableSet() *schema.Resource { + return &schema.Resource{ + Read: dataSourceTFEVariableSetRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "organization": { + Type: schema.TypeString, + Required: true, + }, + + "description": { + Type: schema.TypeString, + Computed: true, + }, + + "global": { + Type: schema.TypeBool, + Computed: true, + }, + + "workspace_ids": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "variable_ids": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceTFEVariableSetRead(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get the name and organization. + name := d.Get("name").(string) + organization := d.Get("organization").(string) + + // Create an options struct. + options := tfe.VariableSetListOptions{} + + for { + // Variable Set relations, vars and workspaces, are omitted from the querying until + // we find the desired variable set. + l, err := tfeClient.VariableSets.List(ctx, organization, &options) + if err != nil { + if err == tfe.ErrResourceNotFound { + return fmt.Errorf("could not find variable set%s/%s", organization, name) + } + return fmt.Errorf("Error retrieving variable set: %v", err) + } + + for _, vs := range l.Items { + if vs.Name == name { + d.Set("name", vs.Name) + d.Set("description", vs.Description) + d.Set("global", vs.Global) + + //Only now include vars and workspaces to cut down on request load. + readOptions := tfe.VariableSetReadOptions{ + Include: &[]tfe.VariableSetIncludeOpt{tfe.VariableSetWorkspaces, tfe.VariableSetVars}, + } + + vs, err = tfeClient.VariableSets.Read(ctx, vs.ID, &readOptions) + if err != nil { + return fmt.Errorf("Error retrieving variable set relations: %v", err) + } + + var workspaces []interface{} + for _, workspace := range vs.Workspaces { + workspaces = append(workspaces, workspace.ID) + } + d.Set("workspace_ids", workspaces) + + var variables []interface{} + for _, variable := range vs.Variables { + variables = append(variables, variable.ID) + } + d.Set("variable_ids", variables) + + d.SetId(vs.ID) + return nil + } + } + + // Exit the loop when we've seen all pages. + if l.CurrentPage >= l.TotalPages { + break + } + + // Update the page number to get the next page. + options.PageNumber = l.NextPage + } + + return fmt.Errorf("Could not find variable set %s/%s", organization, name) +} diff --git a/tfe/data_source_variable_set_test.go b/tfe/data_source_variable_set_test.go new file mode 100644 index 000000000..d4e49facd --- /dev/null +++ b/tfe/data_source_variable_set_test.go @@ -0,0 +1,112 @@ +package tfe + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccTFEVariableSetsDataSource_basic(t *testing.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) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccTFEVariableSetsDataSourceConfig_basic(rInt), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("data.tfe_variable_set.foobar", "id"), + resource.TestCheckResourceAttr( + "data.tfe_variable_set.foobar", "name", fmt.Sprintf("varset-foo-%d", rInt)), + resource.TestCheckResourceAttr( + "data.tfe_variable_set.foobar", "description", "a description"), + resource.TestCheckResourceAttr( + "data.tfe_variable_set.foobar", "global", "false"), + resource.TestCheckResourceAttr( + "data.tfe_variable_set.foobar", "organization", orgName), + ), + }, + }, + }, + ) +} + +func TestAccTFEVariableSetsDataSource_full(t *testing.T) { + rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccTFEVariableSetsDataSourceConfig_full(rInt), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("data.tfe_variable_set.foobar", "id"), + resource.TestCheckResourceAttr( + "data.tfe_variable_set.foobar", "name", fmt.Sprintf("varset-foo-%d", rInt)), + resource.TestCheckResourceAttr( + "data.tfe_variable_set.foobar", "workspace_ids.#", "1"), + resource.TestCheckResourceAttr( + "data.tfe_variable_set.foobar", "variable_ids.#", "1"), + ), + }, + }, + }, + ) +} + +func testAccTFEVariableSetsDataSourceConfig_basic(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "org-%d" + email = "admin@company.com" +} + +resource "tfe_variable_set" "foobar" { + name = "varset-foo-%d" + description = "a description" + organization = tfe_organization.foobar.id +} + +data "tfe_variable_set" "foobar" { + name = tfe_variable_set.foobar.name + organization = tfe_variable_set.foobar.organization +}`, rInt, rInt) +} + +func testAccTFEVariableSetsDataSourceConfig_full(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "org-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-foo-%d" + organization = tfe_organization.foobar.id +} + +resource "tfe_variable_set" "foobar" { + name = "varset-foo-%d" + description = "a description" + organization = tfe_organization.foobar.id + workspace_ids = [tfe_workspace.foobar.id] +} + +resource "tfe_variable" "envfoo" { + key = "vfoo" + value = "bar" + category = "env" + variable_set_id = tfe_variable_set.foobar.id +} + +data "tfe_variable_set" "foobar" { + name = tfe_variable_set.foobar.name + organization = tfe_variable_set.foobar.organization +}`, rInt, rInt, rInt) +} diff --git a/tfe/data_source_variables.go b/tfe/data_source_variables.go index 9523ea72b..d0ba0d138 100644 --- a/tfe/data_source_variables.go +++ b/tfe/data_source_variables.go @@ -62,14 +62,26 @@ func dataSourceTFEWorkspaceVariables() *schema.Resource { }, }, "workspace_id": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{"workspace_id", "variable_set_id"}, + }, + "variable_set_id": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{"workspace_id", "variable_set_id"}, }, }, } } func dataSourceVariableRead(d *schema.ResourceData, meta interface{}) error { + //Switch to variable set variable logic + _, variableSetIdProvided := d.GetOk("variable_set_id") + if variableSetIdProvided { + return dataSourceVariableSetVariableRead(d, meta) + } + tfeClient := meta.(*tfe.Client) // Get the name and organization. @@ -122,3 +134,57 @@ func dataSourceVariableRead(d *schema.ResourceData, meta interface{}) error { d.Set("env", totalEnvVariables) return nil } + +func dataSourceVariableSetVariableRead(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get the id. + variableSetId := d.Get("variable_set_id").(string) + + log.Printf("[DEBUG] Read configuration of variable set: %s", variableSetId) + + totalEnvVariables := make([]interface{}, 0) + totalTerraformVariables := make([]interface{}, 0) + + options := tfe.VariableSetVariableListOptions{} + + for { + variableList, err := tfeClient.VariableSetVariables.List(ctx, variableSetId, &options) + if err != nil { + return fmt.Errorf("Error retrieving variable list: %w", err) + } + terraformVars := make([]interface{}, 0) + envVars := make([]interface{}, 0) + for _, variable := range variableList.Items { + result := make(map[string]interface{}) + result["id"] = variable.ID + result["category"] = variable.Category + result["hcl"] = variable.HCL + result["name"] = variable.Key + result["sensitive"] = variable.Sensitive + result["value"] = variable.Value + if variable.Category == "terraform" { + terraformVars = append(terraformVars, result) + } else if variable.Category == "env" { + envVars = append(envVars, result) + } + } + + totalEnvVariables = append(totalEnvVariables, envVars...) + totalTerraformVariables = append(totalTerraformVariables, terraformVars...) + + // Exit the loop when we've seen all pages. + if variableList.CurrentPage >= variableList.TotalPages { + break + } + + // Update the page number to get the next page. + options.PageNumber = variableList.NextPage + } + + d.SetId(fmt.Sprintf("variables/%v", variableSetId)) + d.Set("variables", append(totalTerraformVariables, totalEnvVariables...)) + d.Set("terraform", totalTerraformVariables) + d.Set("env", totalEnvVariables) + return nil +} diff --git a/tfe/data_source_variables_test.go b/tfe/data_source_variables_test.go index 2508df4a4..5302b8333 100644 --- a/tfe/data_source_variables_test.go +++ b/tfe/data_source_variables_test.go @@ -20,10 +20,14 @@ func TestAccTFEVariablesDataSource_basic(t *testing.T) { Config: testAccTFEVariablesDataSourceConfig_basic(rInt), Check: resource.ComposeAggregateTestCheckFunc( // variables attribute - resource.TestCheckResourceAttrSet("data.tfe_variables.foobar", "id"), - resource.TestCheckOutput("variables", "foo"), - resource.TestCheckOutput("env", "foo"), - resource.TestCheckOutput("terraform", "foo"), + resource.TestCheckResourceAttrSet("data.tfe_variables.workspace_foobar", "id"), + resource.TestCheckResourceAttrSet("data.tfe_variables.variable_set_foobar", "id"), + resource.TestCheckOutput("workspace_variables", "foo"), + resource.TestCheckOutput("workspace_env", "foo"), + resource.TestCheckOutput("workspace_terraform", "foo"), + resource.TestCheckOutput("variable_set_variables", "vfoo"), + resource.TestCheckOutput("variable_set_env", "vfoo"), + resource.TestCheckOutput("variable_set_terraform", "vfoo"), ), }, }, @@ -43,6 +47,11 @@ resource "tfe_workspace" "foobar" { organization = tfe_organization.foobar.id } +resource "tfe_variable_set" "foobar" { + name = "varset-foo-%d" + organization = tfe_organization.foobar.id +} + resource "tfe_variable" "terrbar" { key = "foo" value = "bar" @@ -57,23 +66,57 @@ resource "tfe_variable" "envbar" { workspace_id = tfe_workspace.foobar.id } -data "tfe_variables" "foobar" { +resource "tfe_variable" "terrfoo" { + key = "vfoo" + value = "bar" + category = "terraform" + variable_set_id = tfe_variable_set.foobar.id +} + +resource "tfe_variable" "envfoo" { + key = "vfoo" + value = "bar" + category = "env" + variable_set_id = tfe_variable_set.foobar.id +} + +data "tfe_variables" "workspace_foobar" { workspace_id = tfe_workspace.foobar.id depends_on = [ - tfe_variable.terrbar, + tfe_variable.terrbar, tfe_variable.envbar ] } -output "variables" { - value = data.tfe_variables.foobar.variables[0]["name"] +data "tfe_variables" "variable_set_foobar" { + variable_set_id = tfe_variable_set.foobar.id + depends_on = [ + tfe_variable.terrfoo, + tfe_variable.envfoo + ] +} + +output "workspace_variables" { + value = data.tfe_variables.workspace_foobar.variables[0]["name"] +} + +output "workspace_env" { + value = data.tfe_variables.workspace_foobar.env[0]["name"] +} + +output "workspace_terraform" { + value = data.tfe_variables.workspace_foobar.terraform[0]["name"] +} + +output "variable_set_variables" { + value = data.tfe_variables.variable_set_foobar.variables[0]["name"] } -output "env" { - value = data.tfe_variables.foobar.env[0]["name"] +output "variable_set_env" { + value = data.tfe_variables.variable_set_foobar.terraform[0]["name"] } -output "terraform" { - value = data.tfe_variables.foobar.terraform[0]["name"] -}`, rInt, rInt) +output "variable_set_terraform" { + value = data.tfe_variables.variable_set_foobar.env[0]["name"] +}`, rInt, rInt, rInt) } diff --git a/tfe/provider.go b/tfe/provider.go index 32343106a..5290960b8 100644 --- a/tfe/provider.go +++ b/tfe/provider.go @@ -90,6 +90,7 @@ func Provider() *schema.Provider { "tfe_workspace": dataSourceTFEWorkspace(), "tfe_workspace_ids": dataSourceTFEWorkspaceIDs(), "tfe_variables": dataSourceTFEWorkspaceVariables(), + "tfe_variable_set": dataSourceTFEVariableSet(), }, ResourcesMap: map[string]*schema.Resource{ @@ -116,6 +117,7 @@ func Provider() *schema.Provider { "tfe_terraform_version": resourceTFETerraformVersion(), "tfe_workspace": resourceTFEWorkspace(), "tfe_variable": resourceTFEVariable(), + "tfe_variable_set": resourceTFEVariableSet(), }, ConfigureFunc: providerConfigure, diff --git a/tfe/resource_tfe_variable.go b/tfe/resource_tfe_variable.go index 4a94dda7e..46f279741 100644 --- a/tfe/resource_tfe_variable.go +++ b/tfe/resource_tfe_variable.go @@ -74,14 +74,28 @@ func resourceTFEVariable() *schema.Resource { }, "workspace_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Required: false, + ForceNew: true, + ExactlyOneOf: []string{"workspace_id", "variable_set_id"}, ValidateFunc: validation.StringMatch( workspaceIdRegexp, "must be a valid workspace ID (ws-)", ), }, + + "variable_set_id": { + Type: schema.TypeString, + Optional: true, + Required: false, + ForceNew: true, + ExactlyOneOf: []string{"workspace_id", "variable_set_id"}, + ValidateFunc: validation.StringMatch( + variableSetIdRegexp, + "must be a valid variable set ID (varset-)", + ), + }, }, CustomizeDiff: forceRecreateResourceIf(), @@ -107,6 +121,12 @@ func forceRecreateResourceIf() schema.CustomizeDiffFunc { } func resourceTFEVariableCreate(d *schema.ResourceData, meta interface{}) error { + //Switch to variable set variable logic if we need to + _, variableSetIdProvided := d.GetOk("variable_set_id") + if variableSetIdProvided { + return resourceTFEVariableSetVariableCreate(d, meta) + } + tfeClient := meta.(*tfe.Client) // Get key and category. @@ -142,7 +162,49 @@ func resourceTFEVariableCreate(d *schema.ResourceData, meta interface{}) error { return resourceTFEVariableRead(d, meta) } +func resourceTFEVariableSetVariableCreate(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get key and category. + key := d.Get("key").(string) + category := d.Get("category").(string) + + // Get the variable set + variableSetID := d.Get("variable_set_id").(string) + vs, err := tfeClient.VariableSets.Read(ctx, variableSetID, nil) + if err != nil { + return fmt.Errorf( + "Error retrieving variable set %s: %v", variableSetID, err) + } + + // Create a new options struct. + options := tfe.VariableSetVariableCreateOptions{ + Key: tfe.String(key), + Value: tfe.String(d.Get("value").(string)), + Category: tfe.Category(tfe.CategoryType(category)), + HCL: tfe.Bool(d.Get("hcl").(bool)), + Sensitive: tfe.Bool(d.Get("sensitive").(bool)), + Description: tfe.String(d.Get("description").(string)), + } + + log.Printf("[DEBUG] Create %s variable: %s", category, key) + variable, err := tfeClient.VariableSetVariables.Create(ctx, vs.ID, &options) + if err != nil { + return fmt.Errorf("Error creating %s variable %s: %v", category, key, err) + } + + d.SetId(variable.ID) + + return resourceTFEVariableRead(d, meta) +} + func resourceTFEVariableRead(d *schema.ResourceData, meta interface{}) error { + //Switch to variable set variable logic if we need to + _, variableSetIdProvided := d.GetOk("variable_set_id") + if variableSetIdProvided { + return resourceTFEVariableSetVariableRead(d, meta) + } + tfeClient := meta.(*tfe.Client) // Get the workspace. @@ -184,7 +246,55 @@ func resourceTFEVariableRead(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceTFEVariableSetVariableRead(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get the variable set + variableSetID := d.Get("variable_set_id").(string) + vs, err := tfeClient.VariableSets.Read(ctx, variableSetID, nil) + if err != nil { + if err == tfe.ErrResourceNotFound { + log.Printf("[DEBUG] Variable set %s no longer exists", variableSetID) + d.SetId("") + return nil + } + return fmt.Errorf( + "Error retrieving variable set %s: %v", variableSetID, err) + } + + log.Printf("[DEBUG] Read variable: %s", d.Id()) + variable, err := tfeClient.VariableSetVariables.Read(ctx, vs.ID, d.Id()) + if err != nil { + if err == tfe.ErrResourceNotFound { + log.Printf("[DEBUG] Variable %s does no longer exist", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading variable %s: %v", d.Id(), err) + } + + // Update config. + d.Set("key", variable.Key) + d.Set("category", string(variable.Category)) + d.Set("description", string(variable.Description)) + d.Set("hcl", variable.HCL) + d.Set("sensitive", variable.Sensitive) + + // Only set the value if its not sensitive, as otherwise it will be empty. + if !variable.Sensitive { + d.Set("value", variable.Value) + } + + return nil +} + func resourceTFEVariableUpdate(d *schema.ResourceData, meta interface{}) error { + //Switch to variable set variable logic if we need to + _, variableSetIdProvided := d.GetOk("variable_set_id") + if variableSetIdProvided { + return resourceTFEVariableSetVariableUpdate(d, meta) + } + tfeClient := meta.(*tfe.Client) // Get the workspace. @@ -213,7 +323,42 @@ func resourceTFEVariableUpdate(d *schema.ResourceData, meta interface{}) error { return resourceTFEVariableRead(d, meta) } +func resourceTFEVariableSetVariableUpdate(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get the variable set. + variableSetID := d.Get("variable_set_id").(string) + vs, err := tfeClient.VariableSets.Read(ctx, variableSetID, nil) + if err != nil { + return fmt.Errorf( + "Error retrieving variable set %s: %v", variableSetID, err) + } + + // Create a new options struct. + options := tfe.VariableSetVariableUpdateOptions{ + Key: tfe.String(d.Get("key").(string)), + Value: tfe.String(d.Get("value").(string)), + HCL: tfe.Bool(d.Get("hcl").(bool)), + Sensitive: tfe.Bool(d.Get("sensitive").(bool)), + Description: tfe.String(d.Get("description").(string)), + } + + log.Printf("[DEBUG] Update variable: %s", d.Id()) + _, err = tfeClient.VariableSetVariables.Update(ctx, vs.ID, d.Id(), &options) + if err != nil { + return fmt.Errorf("Error updating variable %s: %v", d.Id(), err) + } + + return resourceTFEVariableRead(d, meta) +} + func resourceTFEVariableDelete(d *schema.ResourceData, meta interface{}) error { + //Switch to variable set variable logic if we need to + _, variableSetIdProvided := d.GetOk("variable_set_id") + if variableSetIdProvided { + return resourceTFEVariableSetVariableDelete(d, meta) + } + tfeClient := meta.(*tfe.Client) // Get the workspace. @@ -236,24 +381,53 @@ func resourceTFEVariableDelete(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceTFEVariableSetVariableDelete(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get the variable set. + variableSetID := d.Get("variable_set_id").(string) + vs, err := tfeClient.VariableSets.Read(ctx, variableSetID, nil) + if err != nil { + return fmt.Errorf( + "Error retrieving variable set %s: %v", variableSetID, err) + } + + log.Printf("[DEBUG] Delete variable: %s", d.Id()) + err = tfeClient.VariableSetVariables.Delete(ctx, vs.ID, d.Id()) + if err != nil { + if err == tfe.ErrResourceNotFound { + return nil + } + return fmt.Errorf("Error deleting variable%s: %v", d.Id(), err) + } + + return nil +} + func resourceTFEVariableImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { tfeClient := meta.(*tfe.Client) s := strings.SplitN(d.Id(), "/", 3) if len(s) != 3 { return nil, fmt.Errorf( - "invalid variable import format: %s (expected //)", + "invalid variable import format: %s (expected //)", d.Id(), ) } - // Set the fields that are part of the import ID. - workspace_id, err := fetchWorkspaceExternalID(s[0]+"/"+s[1], tfeClient) - if err != nil { - return nil, fmt.Errorf( - "error retrieving workspace %s from organization %s: %v", s[1], s[0], err) + varsetIdUsed := variableSetIdRegexp.MatchString(s[1]) + if varsetIdUsed { + d.Set("variable_set_id", s[1]) + } else { + // Set the fields that are part of the import ID. + workspace_id, err := fetchWorkspaceExternalID(s[0]+"/"+s[1], tfeClient) + if err != nil { + return nil, fmt.Errorf( + "error retrieving workspace %s from organization %s: %v", s[1], s[0], err) + } + d.Set("workspace_id", workspace_id) } - d.Set("workspace_id", workspace_id) + d.SetId(s[2]) return []*schema.ResourceData{d}, nil diff --git a/tfe/resource_tfe_variable_set.go b/tfe/resource_tfe_variable_set.go new file mode 100644 index 000000000..e500a91c0 --- /dev/null +++ b/tfe/resource_tfe_variable_set.go @@ -0,0 +1,181 @@ +package tfe + +import ( + "fmt" + "log" + "regexp" + + tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var variableSetIdRegexp = regexp.MustCompile("varset-[a-zA-Z0-9]{16}$") + +func resourceTFEVariableSet() *schema.Resource { + return &schema.Resource{ + Create: resourceTFEVariableSetCreate, + Read: resourceTFEVariableSetRead, + Update: resourceTFEVariableSetUpdate, + Delete: resourceTFEVariableSetDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "global": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ConflictsWith: []string{"workspace_ids"}, + }, + + "organization": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "workspace_ids": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceTFEVariableSetCreate(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + // Get the name and organization. + name := d.Get("name").(string) + organization := d.Get("organization").(string) + + // Create a new options struct. + options := tfe.VariableSetCreateOptions{ + Name: tfe.String(name), + Global: tfe.Bool(d.Get("global").(bool)), + } + + if description, descriptionSet := d.GetOk("description"); descriptionSet { + options.Description = tfe.String(description.(string)) + } + + variableSet, err := tfeClient.VariableSets.Create(ctx, organization, &options) + if err != nil { + return fmt.Errorf( + "Error creating variable set %s, for organization: %s: %v", name, organization, err) + } + + if workspaceIDs, workspacesSet := d.GetOk("workspace_ids"); !*options.Global && workspacesSet { + log.Printf("[DEBUG] Apply variable set %s to workspaces %v", name, workspaceIDs) + + applyOptions := tfe.VariableSetUpdateWorkspacesOptions{} + for _, workspaceID := range workspaceIDs.(*schema.Set).List() { + applyOptions.Workspaces = append(applyOptions.Workspaces, &tfe.Workspace{ID: workspaceID.(string)}) + } + + variableSet, err = tfeClient.VariableSets.UpdateWorkspaces(ctx, variableSet.ID, &applyOptions) + if err != nil { + return fmt.Errorf( + "Error applying variable set %s (%s) to given workspaces: %v", name, variableSet.ID, err) + } + } + + d.SetId(variableSet.ID) + + return resourceTFEVariableSetRead(d, meta) +} + +func resourceTFEVariableSetRead(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + log.Printf("[DEBUG] Read configuration of variable set: %s", d.Id()) + variableSet, err := tfeClient.VariableSets.Read(ctx, d.Id(), &tfe.VariableSetReadOptions{ + Include: &[]tfe.VariableSetIncludeOpt{tfe.VariableSetWorkspaces}, + }) + if err != nil { + if err == tfe.ErrResourceNotFound { + log.Printf("[DEBUG] Variable set %s no longer exists", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading configuration of variable set %s: %v", d.Id(), err) + } + + // Update the config. + d.Set("name", variableSet.Name) + d.Set("description", variableSet.Description) + d.Set("global", variableSet.Global) + d.Set("organization", variableSet.Organization.Name) + + var wids []interface{} + for _, workspace := range variableSet.Workspaces { + wids = append(wids, workspace.ID) + } + d.Set("workspace_ids", wids) + + return nil +} + +func resourceTFEVariableSetUpdate(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + if d.HasChange("name") || d.HasChange("description") || d.HasChange("global") { + options := tfe.VariableSetUpdateOptions{ + Name: tfe.String(d.Get("name").(string)), + Description: tfe.String(d.Get("description").(string)), + Global: tfe.Bool(d.Get("global").(bool)), + } + + log.Printf("[DEBUG] Update variable set: %s", d.Id()) + _, err := tfeClient.VariableSets.Update(ctx, d.Id(), &options) + if err != nil { + return fmt.Errorf("Error updateing variable %s: %v", d.Id(), err) + } + } + + if d.HasChanges("workspace_ids") { + workspaceIDs := d.Get("workspace_ids") + applyOptions := tfe.VariableSetUpdateWorkspacesOptions{} + applyOptions.Workspaces = []*tfe.Workspace{} + for _, workspaceID := range workspaceIDs.(*schema.Set).List() { + applyOptions.Workspaces = append(applyOptions.Workspaces, &tfe.Workspace{ID: workspaceID.(string)}) + } + + log.Printf("[DEBUG] Apply variable set %s to workspaces %v", d.Id(), workspaceIDs) + _, err := tfeClient.VariableSets.UpdateWorkspaces(ctx, d.Id(), &applyOptions) + if err != nil { + return fmt.Errorf( + "Error applying variable set %s to given workspaces: %v", d.Id(), err) + } + } + + return resourceTFEVariableSetRead(d, meta) +} + +func resourceTFEVariableSetDelete(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + log.Printf("[DEBUG] Delete variable set: %s", d.Id()) + err := tfeClient.VariableSets.Delete(ctx, d.Id()) + if err != nil { + if err == tfe.ErrResourceNotFound { + return nil + } + return fmt.Errorf("Error deleting variable set %s: %v", d.Id(), err) + } + + return nil +} diff --git a/tfe/resource_tfe_variable_set_test.go b/tfe/resource_tfe_variable_set_test.go new file mode 100644 index 000000000..afdf8d027 --- /dev/null +++ b/tfe/resource_tfe_variable_set_test.go @@ -0,0 +1,307 @@ +package tfe + +import ( + "fmt" + "math/rand" + "testing" + "time" + + tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccTFEVariableSet_basic(t *testing.T) { + variableSet := &tfe.VariableSet{} + 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_basic(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFEVariableSetExists( + "tfe_variable_set.foobar", variableSet), + testAccCheckTFEVariableSetAttributes(variableSet), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "name", "variable_set_test"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "description", "a test variable set"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "global", "false"), + ), + }, + }, + }) +} + +func TestAccTFEVariableSet_full(t *testing.T) { + variableSet := &tfe.VariableSet{} + 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_full(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFEVariableSetExists( + "tfe_variable_set.foobar", variableSet), + testAccCheckTFEVariableSetAttributes(variableSet), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "name", "variable_set_test"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "description", "a test variable set"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "global", "false"), + testAccCheckTFEVariableSetExists( + "tfe_variable_set.applied", variableSet), + testAccCheckTFEVariableSetApplication(variableSet), + ), + }, + }, + }) +} + +func TestAccTFEVariableSet_update(t *testing.T) { + variableSet := &tfe.VariableSet{} + 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_basic(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFEVariableSetExists( + "tfe_variable_set.foobar", variableSet), + testAccCheckTFEVariableSetAttributes(variableSet), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "name", "variable_set_test"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "description", "a test variable set"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "global", "false"), + testAccCheckTFEVariableSetApplicationUpdate(variableSet), + ), + }, + + { + Config: testAccTFEVariableSet_update(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFEVariableSetExists( + "tfe_variable_set.foobar", variableSet), + testAccCheckTFEVariableSetAttributesUpdate(variableSet), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "name", "variable_set_test_updated"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "description", "another description"), + resource.TestCheckResourceAttr( + "tfe_variable_set.foobar", "global", "true"), + ), + }, + }, + }) +} + +func TestAccTFEVariableSet_import(t *testing.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_basic(rInt), + }, + + { + ResourceName: "tfe_variable_set.foobar", + ImportState: true, + ImportStateIdPrefix: "", + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckTFEVariableSetExists( + n string, variableSet *tfe.VariableSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + vs, err := tfeClient.VariableSets.Read( + ctx, + rs.Primary.ID, + &tfe.VariableSetReadOptions{Include: &[]tfe.VariableSetIncludeOpt{tfe.VariableSetWorkspaces}}, + ) + if err != nil { + return err + } + + *variableSet = *vs + + return nil + } +} + +func testAccCheckTFEVariableSetAttributes( + variableSet *tfe.VariableSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + if variableSet.Name != "variable_set_test" { + return fmt.Errorf("Bad name: %s", variableSet.Name) + } + if variableSet.Description != "a test variable set" { + return fmt.Errorf("Bad description: %s", variableSet.Description) + } + if variableSet.Global != false { + return fmt.Errorf("Bad global: %t", variableSet.Global) + } + + return nil + } +} + +func testAccCheckTFEVariableSetAttributesUpdate( + variableSet *tfe.VariableSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + if variableSet.Name != "variable_set_test_updated" { + return fmt.Errorf("Bad name: %s", variableSet.Name) + } + if variableSet.Description != "another description" { + return fmt.Errorf("Bad description: %s", variableSet.Description) + } + if variableSet.Global != true { + return fmt.Errorf("Bad global: %t", variableSet.Global) + } + + return nil + } +} + +func testAccCheckTFEVariableSetApplication(variableSet *tfe.VariableSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(variableSet.Workspaces) != 1 { + return fmt.Errorf("Bad workspace apply: %v", variableSet.Workspaces) + } + + return nil + } +} + +func testAccCheckTFEVariableSetApplicationUpdate(variableSet *tfe.VariableSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(variableSet.Workspaces) != 0 { + return fmt.Errorf("Bad workspace apply: %v", variableSet.Workspaces) + } + + return nil + } +} + +func testAccCheckTFEVariableSetDestroy(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "tfe_variable_set" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + _, err := tfeClient.VariableSets.Read(ctx, rs.Primary.ID, nil) + if err == nil { + return fmt.Errorf("Variable Set %s still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccTFEVariableSet_basic(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_variable_set" "foobar" { + name = "variable_set_test" + description = "a test variable set" + global = false + organization = tfe_organization.foobar.id +}`, rInt) +} + +func testAccTFEVariableSet_full(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "foobar" + organization = tfe_organization.foobar.id +} + +resource "tfe_variable_set" "foobar" { + name = "variable_set_test" + description = "a test variable set" + global = false + organization = tfe_organization.foobar.id +} + +resource "tfe_variable_set" "applied" { + name = "variable_set_applied" + description = "a test variable set" + workspace_ids = [tfe_workspace.foobar.id] + organization = tfe_organization.foobar.id +}`, rInt) +} + +func testAccTFEVariableSet_update(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "foobar" + organization = tfe_organization.foobar.id +} + +resource "tfe_variable_set" "foobar" { + name = "variable_set_test_updated" + description = "another description" + global = true + organization = tfe_organization.foobar.id +} + +resource "tfe_variable_set" "applied" { + name = "variable_set_applied" + description = "a test variable set" + workspace_ids = [] + organization = tfe_organization.foobar.id +}`, rInt) +} diff --git a/tfe/resource_tfe_variable_test.go b/tfe/resource_tfe_variable_test.go index c9621734b..437313854 100644 --- a/tfe/resource_tfe_variable_test.go +++ b/tfe/resource_tfe_variable_test.go @@ -44,6 +44,39 @@ func TestAccTFEVariable_basic(t *testing.T) { }) } +func TestAccTFEVariable_basic_variable_set(t *testing.T) { + variable := &tfe.VariableSetVariable{} + rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckTFEVariableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTFEVariable_basic_variable_set(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFEVariableSetVariableExists( + "tfe_variable.foobar", variable), + testAccCheckTFEVariableiSetVariableAttributes(variable), + resource.TestCheckResourceAttr( + "tfe_variable.foobar", "key", "key_test"), + resource.TestCheckResourceAttr( + "tfe_variable.foobar", "value", "value_test"), + resource.TestCheckResourceAttr( + "tfe_variable.foobar", "description", "some description"), + resource.TestCheckResourceAttr( + "tfe_variable.foobar", "category", "env"), + resource.TestCheckResourceAttr( + "tfe_variable.foobar", "hcl", "false"), + resource.TestCheckResourceAttr( + "tfe_variable.foobar", "sensitive", "false"), + ), + }, + }, + }) +} + func TestAccTFEVariable_update(t *testing.T) { variable := &tfe.Variable{} rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() @@ -207,6 +240,38 @@ func testAccCheckTFEVariableExists( } } +func testAccCheckTFEVariableSetVariableExists( + n string, variable *tfe.VariableSetVariable) resource.TestCheckFunc { + return func(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + vsID := rs.Primary.Attributes["variable_set_id"] + vs, err := tfeClient.VariableSets.Read(ctx, vsID, nil) + if err != nil { + return fmt.Errorf( + "Error retrieving variable set %s: %v", vsID, err) + } + + v, err := tfeClient.VariableSetVariables.Read(ctx, vs.ID, rs.Primary.ID) + if err != nil { + return err + } + + *variable = *v + + return nil + } +} + func testAccCheckTFEVariableAttributes( variable *tfe.Variable) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -238,6 +303,37 @@ func testAccCheckTFEVariableAttributes( } } +func testAccCheckTFEVariableiSetVariableAttributes( + variable *tfe.VariableSetVariable) resource.TestCheckFunc { + return func(s *terraform.State) error { + if variable.Key != "key_test" { + return fmt.Errorf("Bad key: %s", variable.Key) + } + + if variable.Value != "value_test" { + return fmt.Errorf("Bad value: %s", variable.Value) + } + + if variable.Description != "some description" { + return fmt.Errorf("Bad description: %s", variable.Description) + } + + if variable.Category != tfe.CategoryEnv { + return fmt.Errorf("Bad category: %s", variable.Category) + } + + if variable.HCL != false { + return fmt.Errorf("Bad HCL: %t", variable.HCL) + } + + if variable.Sensitive != false { + return fmt.Errorf("Bad sensitive: %t", variable.Sensitive) + } + + return nil + } +} + func testAccCheckTFEVariableAttributesUpdate( variable *tfe.Variable) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -353,6 +449,27 @@ resource "tfe_variable" "foobar" { }`, rInt) } +func testAccTFEVariable_basic_variable_set(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_variable_set" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_variable" "foobar" { + key = "key_test" + value = "value_test" + description = "some description" + category = "env" + variable_set_id = tfe_variable_set.foobar.id +}`, rInt) +} + func testAccTFEVariable_update(rInt int) string { return fmt.Sprintf(` resource "tfe_organization" "foobar" { diff --git a/website/docs/d/variable_sets.html.markdown b/website/docs/d/variable_sets.html.markdown new file mode 100644 index 000000000..b0ecfca9f --- /dev/null +++ b/website/docs/d/variable_sets.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "tfe" +page_title: "Terraform Enterprise: tfe_variable_set" +sidebar_current: "docs-datasource-tfe-variable-set" +description: |- + Get information on organization variable sets. +--- + +# Data Source: tfe_variable_set + +This data source is used to retrieve all variables defined in a specified workspace. + +## Example Usage + +For workspace variables: + +```hcl +data "tfe_variable_set" "test" { + name = "my-variable-set-name" + organization = "my-org-name" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Name of the workspace. +* `organization` - (Required) Name of the organization. + +## Attributes Reference + +* `id` - The ID of the variable. +* `organization` - Name of the organization. +* `name` - Name of the variable set. +* `description` - Description of the variable set. +* `global` - Whether or not the variable set applies to all workspaces in the organization. +* `workspace_ids` - IDs of the workspaces that use the variable set. +* `variable_ids` - IDs of the variables attached to the variable set. diff --git a/website/docs/d/variables.html.markdown b/website/docs/d/variables.html.markdown index cf4940a40..880c46db9 100644 --- a/website/docs/d/variables.html.markdown +++ b/website/docs/d/variables.html.markdown @@ -12,6 +12,8 @@ This data source is used to retrieve all variables defined in a specified worksp ## Example Usage +For workspace variables: + ```hcl data "tfe_workspace" "test" { name = "my-workspace-name" @@ -23,11 +25,25 @@ data "tfe_variables" "test" { } ``` +For variable set variables: + +```hcl +data "tfe_variable_set" "test" { + name = "my-variable-set-name" + organization = "my-org-name" +} + +data "tfe_variables" "test" { + variable_set_id = data.tfe_variable_set.test.id +} +``` + ## Argument Reference -The following arguments are supported: +One of following arguments are required: -* `workspace_id` - (Required) ID of the workspace. +* `workspace_id` - ID of the workspace. +* `variable_set_id` - ID of the workspace. ## Attributes Reference diff --git a/website/docs/r/variable.html.markdown b/website/docs/r/variable.html.markdown index b502a87e0..28634cfa1 100644 --- a/website/docs/r/variable.html.markdown +++ b/website/docs/r/variable.html.markdown @@ -12,7 +12,7 @@ Creates, updates and destroys variables. ## Example Usage -Basic usage: +Basic usage for workspaces: ```hcl resource "tfe_organization" "test" { @@ -34,6 +34,38 @@ resource "tfe_variable" "test" { } ``` +Basic usage for variable sets: + +```hcl +resource "tfe_organization" "test" { + name = "my-org-name" + email = "admin@company.com" +} + +resource "tfe_variable_set" "test" { + name = "Test Varset" + description = "Some description." + global = false + organization = tfe_organization.test.id +} + +resource "tfe_variable" "test" { + key = "seperate_variable" + value = "my_value_name" + category = "terraform" + description = "a useful description" + variable_set_id = tfe_variable_set.test.id +} + +resource "tfe_variable" "test" { + key = "another_variable" + value = "my_value_name" + category = "env" + description = "an environment variable" + variable_set_id = tfe_variable_set.test.id +} +``` + ## Argument Reference The following arguments are supported: @@ -46,8 +78,10 @@ The following arguments are supported: * `hcl` - (Optional) Whether to evaluate the value of the variable as a string of HCL code. Has no effect for environment variables. Defaults to `false`. * `sensitive` - (Optional) Whether the value is sensitive. If true then the - variable is written once and not visible thereafter. Defaults to `false`. -* `workspace_id` - (Required) ID of the workspace that owns the variable. +variable is written once and not visible thereafter. Defaults to `false`. + * One of the following (Required) + * `workspace_id` - ID of the workspace that owns the variable. + * `variable_set_id` - ID of the variable set that owns the variable. ## Attributes Reference diff --git a/website/docs/r/variable_set.html.markdown b/website/docs/r/variable_set.html.markdown new file mode 100644 index 000000000..105060886 --- /dev/null +++ b/website/docs/r/variable_set.html.markdown @@ -0,0 +1,118 @@ +--- +layout: "tfe" +page_title: "Terraform Enterprise: tfe_variable_set" +sidebar_current: "docs-resource-tfe-variable-set" +description: |- + Manages variable sets. +--- + +# tfe_variable_set + +Creates, updates and destroys variable sets. + +## Example Usage + +Basic usage: + +```hcl +resource "tfe_organization" "test" { + name = "my-org-name" + email = "admin@company.com" +} + +resource "tfe_workspace" "test" { + name = "my-workspace-name" + organization = tfe_organization.test.id +} + +resource "tfe_variable_set" "test" { + name = "Test Varset" + description = "Some description." + global = false + organization = tfe_organization.test.id + workspaces = [tfe_workspace.test.id] +} + +resource "tfe_variable" "test" { + key = "seperate_variable" + value = "my_value_name" + category = "terraform" + description = "a useful description" + variable_set = tfe_variable_set.test.id +} + +resource "tfe_variable" "test" { + key = "another_variable" + value = "my_value_name" + category = "env" + description = "an environment variable" + variable_set = tfe_variable_set.test.id +} +``` + +Creating a global variable set: + +```hcl +resource "tfe_organization" "test" { + name = "my-org-name" + email = "admin@company.com" +} + +resource "tfe_variable_set" "test" { + name = "Global Varset" + description = "Variable set applied to all workspaces." + global = true + organization = tfe_organization.test.id +} + +resource "tfe_variable" "test" { + key = "seperate_variable" + value = "my_value_name" + category = "terraform" + description = "a useful description" + variable_set_id = tfe_variable_set.test.id +} + +resource "tfe_variable" "test" { + key = "another_variable" + value = "my_value_name" + category = "env" + description = "an environment variable" + variable_set_id = tfe_variable_set.test.id +} +``` + +Creating a variable set that is applied based on workspace tags: + +```hcl +resource "tfe_organization" "test" { + name = "my-org-name" + email = "admin@company.com" +} + +data "tfe_workspace_ids" "prod-apps" { + tag_names = ["prod", "app", "aws"] + organization = tfe_organization.test.id +} + +resource "tfe_variable_set" "test" { + name = "Tag Based Varset" + description = "Variable set applied to workspaces based on tag." + organization = tfe_organization.test.id + workspaces = tfe_workspace_ids.prod-apps.ids +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Name of the variable set. +* `description` - (Optional) Description of the variable set. +* `global` - (Optional) Whether or not the variable set applies to all workspaces in the organization. Defaults to `false`. +* `organization` - (Required) Name of the organization. +* `workspaces` - (Optional) IDs of the workspaces that use the variable set. + +## Attributes Reference + +* `id` - The ID of the variable.