diff --git a/docs/resources/environment_variable.md b/docs/resources/environment_variable.md index d7124373..6646524b 100644 --- a/docs/resources/environment_variable.md +++ b/docs/resources/environment_variable.md @@ -55,6 +55,7 @@ resource "spacelift_environment_variable" "core-kubeconfig" { - `module_id` (String) ID of the module on which the environment variable is defined - `stack_id` (String) ID of the stack on which the environment variable is defined - `value` (String, Sensitive) Value of the environment variable. Defaults to an empty string. +- `value_nonsensitive` (String) Value of the environment variable. Defaults to an empty string. - `write_only` (Boolean) Indicates whether the value is secret or not. Defaults to `true`. ### Read-Only diff --git a/spacelift/resource_environment_variable.go b/spacelift/resource_environment_variable.go index fa5aee9a..49312c90 100644 --- a/spacelift/resource_environment_variable.go +++ b/spacelift/resource_environment_variable.go @@ -74,6 +74,16 @@ func resourceEnvironmentVariable() *schema.Resource { Optional: true, Default: "", ForceNew: true, + ConflictsWith: []string{"value_nonsensitive"}, + }, + "value_nonsensitive": { + Type: schema.TypeString, + Description: "Value of the environment variable. Defaults to an empty string.", + Sensitive: false, + Optional: true, + Default: "", + ForceNew: true, + ConflictsWith: []string{"value"}, }, "write_only": { Type: schema.TypeBool, @@ -93,12 +103,19 @@ func resourceEnvironmentVariable() *schema.Resource { } func resourceEnvironmentVariableCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + writeOnly := d.Get("write_only").(bool) + + value, sensitive := resourceEnvironmentVariableValue(d) + if !sensitive && writeOnly { + return diag.Errorf("a non-sensitive environment variable cannot be write-only") + } + variables := map[string]interface{}{ "config": structs.ConfigInput{ ID: toID(d.Get("name")), Type: structs.ConfigType("ENVIRONMENT_VARIABLE"), - Value: toString(d.Get("value")), - WriteOnly: graphql.Boolean(d.Get("write_only").(bool)), + Value: value, + WriteOnly: graphql.Boolean(writeOnly), Description: toOptionalString(d.Get("description")), }, } @@ -127,6 +144,13 @@ func resourceEnvironmentVariableCreate(ctx context.Context, d *schema.ResourceDa return resourceEnvironmentVariableCreateModule(ctx, d, meta.(*internal.Client), variables) } +func resourceEnvironmentVariableValue(d *schema.ResourceData) (graphql.String, bool) { + if v, ok := d.GetOk("value_nonsensitive"); ok { + return toString(v), false + } + return toString(d.Get("value")), true +} + func resourceEnvironmentVariableCreateContext(ctx context.Context, d *schema.ResourceData, client *internal.Client, variables map[string]interface{}) diag.Diagnostics { var mutation struct { AddContextConfig structs.ConfigElement `graphql:"contextConfigAdd(context: $context, config: $config)"` @@ -206,7 +230,11 @@ func resourceEnvironmentVariableRead(ctx context.Context, d *schema.ResourceData d.Set("write_only", element.WriteOnly) if value := element.Value; value != nil { - d.Set("value", *value) + if _, ok := d.GetOk("value_nonsensitive"); ok { + d.Set("value_nonsensitive", *value) + } else { + d.Set("value", *value) + } } else { d.Set("value", element.Checksum) } diff --git a/spacelift/resource_environment_variable_test.go b/spacelift/resource_environment_variable_test.go index 3313c3b1..37e4331a 100644 --- a/spacelift/resource_environment_variable_test.go +++ b/spacelift/resource_environment_variable_test.go @@ -2,6 +2,7 @@ package spacelift import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -141,3 +142,139 @@ func TestEnvironmentVariableResource(t *testing.T) { }) }) } + +func TestEnvironmentVariableResourceNonsensitiveValue(t *testing.T) { + const resourceName = "spacelift_environment_variable.test" + + t.Run("with a context", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + config := func(description string) string { + return fmt.Sprintf(` + resource "spacelift_context" "test" { + name = "My first context %s" + } + + resource "spacelift_environment_variable" "test" { + context_id = spacelift_context.test.id + name = "BACON" + value_nonsensitive = "is tasty" + write_only = false + description = %s + } + `, randomID, description) + } + + testSteps(t, []resource.TestStep{ + { + Config: config(`"Bacon is tasty"`), + Check: Resource( + resourceName, + Attribute("id", IsNotEmpty()), + Attribute("checksum", Equals("4d5d01ea427b10dd483e8fce5b5149fb5a9814e9ee614176b756ca4a65c8f154")), + Attribute("context_id", Contains(randomID)), + Attribute("name", Equals("BACON")), + Attribute("value_nonsensitive", Equals("is tasty")), + Attribute("write_only", Equals("false")), + Attribute("description", Equals("Bacon is tasty")), + AttributeNotPresent("value"), + AttributeNotPresent("module_id"), + AttributeNotPresent("stack_id"), + ), + }, + }) + }) + + t.Run("with a module", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + testSteps(t, []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "spacelift_module" "test" { + name = "test-module-%s" + branch = "master" + repository = "terraform-bacon-tasty" + } + + resource "spacelift_environment_variable" "test" { + module_id = spacelift_module.test.id + name = "BACON" + value_nonsensitive = "is tasty" + write_only = false + description = "Bacon is tasty" + } + `, randomID), + Check: Resource( + resourceName, + Attribute("module_id", Equals(fmt.Sprintf("terraform-default-test-module-%s", randomID))), + Attribute("value_nonsensitive", Equals("is tasty")), + Attribute("write_only", Equals("false")), + Attribute("description", Equals("Bacon is tasty")), + AttributeNotPresent("value"), + AttributeNotPresent("context_id"), + AttributeNotPresent("stack_id"), + ), + }, + }) + }) + + t.Run("with a stack", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + testSteps(t, []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "spacelift_stack" "test" { + branch = "master" + repository = "demo" + name = "Test stack %s" + } + + resource "spacelift_environment_variable" "test" { + stack_id = spacelift_stack.test.id + value_nonsensitive = "is tasty" + write_only = false + name = "BACON" + description = "Bacon is tasty" + } + `, randomID), + Check: Resource( + resourceName, + Attribute("stack_id", StartsWith("test-stack-")), + Attribute("stack_id", Contains(randomID)), + Attribute("value_nonsensitive", Equals("is tasty")), + Attribute("description", Equals("Bacon is tasty")), + AttributeNotPresent("value"), + AttributeNotPresent("context_id"), + AttributeNotPresent("module_id"), + ), + }, + }) + }) + + t.Run("write only is not allowed", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + testSteps(t, []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "spacelift_stack" "test" { + branch = "master" + repository = "demo" + name = "Test stack %s" + } + + resource "spacelift_environment_variable" "test" { + stack_id = spacelift_stack.test.id + value_nonsensitive = "is tasty" + write_only = true + name = "BACON" + description = "Bacon is tasty" + } + `, randomID), + ExpectError: regexp.MustCompile("a non-sensitive environment variable cannot be write-only"), + }, + }) + }) +}