Skip to content

feature proposal: Manage multiple env vars in one resource #779

@alexjurkiewicz

Description

@alexjurkiewicz

Image

☝️ Here's a stack in our Spacelift account. Typical plans take ~10mins. Slooowwww.

I want to reduce the number of environment variable resources in our state.

We have several static env vars per stack, corresponding to inventory allocation tags.

resource "spacelift_stack" "myinfra1" {
  # ...
}

resource "spacelift_environment_variable" "myinfra1_base_tags" {
  for_each = { business_unit = "foo", product = "bar", service = "baz", environment = "quux" }
  stack_id = spacelift.stack.myinfra1.id
  name = each.key
  value = each.value
}

Can we enhance the provider so that we can manage N environment variables with <N Terrafom resources?

I have written two proposals below. I am willing to implement this resource myself, but want to get approval on the design / API before proceeding 🙏 Please let me know your thoughts.

  • Constraints:
  • Preserve the clarity and simplicity of the provider. It's a thin wrapper over the API and doesn't try to add many "convenience" functions
  • Don't break the declarative nature of Terraform / don't orphan anything. If you CRUD an env var from state, it MUST be CRUDed in Spacelift

Option 0: environment_variable blocks on spacelift_stack

resource "spacelift_stack" "myinfra1" {
  # ...

  environment_variable {
    name  = "business_unit"
    value = "foo"
  }

  dynamic "environment_variable" {
    for_each = { product = "bar", service = "baz", environment = "quux" }
    content {
      name  = environment_variable.key
      value = environment_variable.value
    }
  }

  # Secrets supported too
  environment_variable {
    name             = "API_KEY"
    value_wo         = var.api_key
    value_wo_version = 1
    write_only       = true
    description      = "Upstream API key"
  }
}

Pros:

  • Fewest total resources — stack and its env vars coexist in one block
  • Supports full attribute set: value, value_wo/value_wo_version, write_only, description
  • Declarative by default — removing a block removes the variable from Spacelift

Cons:

  • Adds complexity to spacelift_stack, which is already the largest resource in the provider
  • dynamic blocks are syntactically heavier than for_each on a dedicated resource
  • Can't be shared across stacks (unlike a context); if ten stacks share the same vars, each stack block repeats them
  • Mixing environment_variable blocks with spacelift_environment_variable resources on the same stack risks confusion about which takes precedence

Option 1: spacelift_environment_variables

resource "spacelift_stack" "myinfra1" {
  # ...
}

resource "spacelift_environment_variables" "myinfra1_base" {
  stack_id = spacelift_stack.myinfra1.id

  variable {
    name = "business_unit"
    value = "foo"
    # All attributes are supported: value_wo, value_wo_version, write_only, description
  }
  dynamic "variable" {
    for_each = { product = "bar", service = "baz", environment = "quux" }
    content {
      name = variable.key
      value = variable.value
    }
  }
}

Pros:

  • Simplest approach conceptually
  • Adding many env vars to a stack is (IMO) a common occurrence, and this streamlines the process
  • Still full support for adding/modifying/removing env vars without Terraform forgetting/orphaning env vars

Cons:

  • Mixing spacelift_environment_variable and spacelift_environment_variables resource blocks could be confusing and redundant (spacelift_environment_variable basically becomes a legacy resource since it's a subset of spacelift_environment_variables)
  • Doesn't map 1:1 to the API

Option 2: spacelift_context_config_exclusive

I think there's some prior art to consider: aws_iam_role_policy_attachments_exclusive: https://registry.terraform.io/providers/hashicorp/awS/latest/docs/resources/iam_role_policy_attachments_exclusive.

Managing many underlying resources in one Terraform resource can be awkward, because it's unclear what you do in the "some of the underlying resources are modified/deleted" scenarios. Making the Terraform resource explicitly "exclusive" resolves these issues: Terraform IaC exactly matches the underlying configuration. No external management is allowed.

Example:

resource "spacelift_context" "myinfra1_base" {
  name        = "myinfra1-base"
}

resource "spacelift_context_config_exclusive" "myinfra1_base" {
  context_id = spacelift_context.myinfra1_base.id
  
  environment_variable {
    name  = "business_unit"
    value = "foo"
    # All attributes are supported: value_wo, value_wo_version, write_only, description
  }

  dynamic "environment_variable" {
    for_each = { product = "bar", service = "baz", environment = "quux" }
    content {
      name = environment_variable.key
      value = environment_variable.value
    }
  }

  # mounted files are also supported
  mounted_file {
    relative_path      = ".npmrc"
    content_base64_wo  = var.npmrc_base64
    content_wo_version = 1
    write_only         = true
    description        = "Private registry config"
  }
}

resource "spacelift_context_attachment" "myinfra1_base" {
  context_id = spacelift_context.myinfra1_base.id
  stack_id   = spacelift_stack.myinfra1.id
}

In short, we add the ability to manage contexts items exclusively with a single resource. Then we attach the context to our stack.

Pros:

  • Exclusive management, easy to understand

Cons:

  • Doesn't map 1:1 to the API
  • Needs three resources (context, config, attachment) rather than one (spacelift_environment_variables). Doesn't save any resources until you have >3 env vars to manage. In our case, we have ~6 env vars per stack, so this is only a moderately effective solution

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions