Stacks: Terraform Code Pre-processor

repository·main·Indexed 41 days ago

https://github.com/cisco-open/stacks

Stacks is a Terraform and OpenTofu code pre-processor designed to reduce boilerplate and minimize codebase size in monorepos. It uses Jinja templating, global code, and a hierarchical variable model (env.tfvars, stack.tfvars, and layer.tfvars) to generate standard Terraform output. Version 2.0.21 provides built-in Jinja filters to access data from other stacks, eliminating the need for manual remote_state data sources.

Tokens
16.1K
Snippets
66
Records
89
Agent score
51%

What's inside Stacks

  1. What is Stacks for Terraform?

    main

    Stacks is a code pre-processor for Terraform (and OpenTofu). It is designed to minimize the total Terraform codebase size while maintaining full coverage.

    As a pre-processor, Stacks operates on a simple input-output model: it receives "input code" (which may include Jinja templating, global code, and other Stacks-specific abstractions) and returns "output code" in standard Terraform format that can be consumed by Terraform or OpenTofu engines.

  2. Perform input validation using the `throw(msg)` filter

    main

    Stacks extends Jinja by providing a throw(msg) filter. When this filter is called during a template render, it performs a Python raise Exception(msg), which halts the execution of the Stacks render process. This is useful for enforcing constraints, such as verifying that specific variables are defined or that their values belong to an allowed set of options.

    Common use cases include:

    • Enforcing resource tagging policies.
    • Ensuring ownership tracking is accurate.
    • Validating inputs for cost analysis.
    # Example of using throw to validate a variable
    {% if 'owner' not in var %}{{ throw('var.owner is not defined') }}{% endif %}
    {% if var.owner not in var.owners %}{{ throw('var.owner not in var.owners') }}{% endif %}
  3. Understand the relationship between Stacks, Bases, and Layers

    main

    In Stacks, infrastructure is organized into a hierarchy of Bases and Layers:

    • Stack: The complete collection consisting of a single Base and its associated Layers.
    • Stack Base: The input code that translates directly into a Terraform root module. This defines the core logic and resources.
    • Stack Layer: A specific instance of a Stack Base mapped to a particular environment.

    This architecture allows you to define a single root module (the Base) and instantiate it multiple times (as Layers) across different environments without the overhead of re-initializing the module for every instance. To create new instances, you simply create new directories within your layers directory.

  4. Compare Stacks with CDK for Terraform and Pulumi

    main

    Stacks and imperative-based tools like CDK for Terraform (CDKTF) or Pulumi can achieve similar results but use different mental models:

    • CDKTF/Pulumi: Replaces HCL with imperative programming languages to provide limitless customizability through imperative logic.
    • Stacks: Adds a thin layer of Jinja templating on top of the HCL you already know, avoiding the complexities associated with imperative infrastructure-as-code frameworks.
  5. Determine if Stacks is suitable for your Terraform architecture

    main

    Stacks is optimized for Terraform monorepos, defined as a single version control repository that maps to two or more Terraform states (e.g., 1 repository = N states).

    While Stacks can function in a "1 repository = 1 state" setup, it is not recommended for simple, single-state repositories. If your infrastructure management does not follow a monorepo pattern, Stacks may not provide significant value.

  6. Use Stacks special variables in Jinja and Terraform

    main
    Stacks injects a set of read-only special variables that are available for use within Jinja templates and Terraform configurations. These variables allow you to dynamically reference the current stack's structure, environment hierarchy, and configuration settings without hardcoding paths or names.
  7. Understand HCL formatting limitations with Stacks

    main

    Because Stacks processes input code that is not always valid HCL (HashiCorp Configuration Language), standard HCL formatters like terraform fmt will not work on the source files.

    If your workflow strictly requires using terraform fmt to enforce code style on your source files, Stacks is not compatible with your requirements.

  8. Distinguish Stacks from Terraform Cloud Stacks

    main

    Do not confuse the Stacks project with Terraform Cloud Stacks (an HCP Terraform feature).

    • Terraform Cloud Stacks: An orchestration feature for deploying multiple interdependent root modules together.
    • Stacks: A templating-based approach to managing Terraform layers.

    Note: Stacks was released in March 2023, whereas Terraform Cloud Stacks was announced later in October 2023.

  9. Automatic variable initialization in Stacks

    main

    Stacks simplifies Terraform variable management by removing the requirement to explicitly define variables in variables.tf before assigning values in variables.tfvars.

    In standard Terraform workflows, you must define a variable's type, description, and default in a definition file, then assign its value in an assignment file. With Stacks, any variable used in your assignments that has not been explicitly defined is automatically initialized for you. You can use variables in your configuration without pre-defining them in a variables.tf file.

  10. Understand the Stacks directory structure

    main

    Stacks uses a specific directory hierarchy to manage environments, stacks, and layers. The structure is driven by several environment variables that define the location of core directories.

    Core Directory Hierarchy

    • Environments Directory (Default: environments/ via STACKS_ENVIRONMENTS_DIR): Contains environment-specific configurations. Each environment folder must contain exactly one env.tfvars file.
    • Stacks Directory (Default: stacks/ via STACKS_STACKS_DIR): Contains the stack definitions.
      • Global Files: global.tf and globals.tfvars are used for cross-stack reusability (e.g., providers, state backends).
      • Stack Folder (e.g., ec2/): Contains the stack's logic and variables.
        • Base Folder (Default: base/ via STACKS_BASE_DIR): Contains the Terraform root module. You can use Jinja on *.tf files here. Local modules in subfolders do not support Jinja unless added to the var.stacks_jinja_enabled_modules variable.
        • Layers Folder (Default: layers/ via STACKS_LAYERS_DIR): Contains environment-specific layers.
          • Layer Directory: Must be named after an existing environment (e.g., production/).
          • Layer Instances: To deploy a stack multiple times per environment, use a suffix: production_foo/.
          • Output Directory (Default: stacks.out/ via STACKS_OUTPUT_DIR): Stores generated output code. Exclude this from version control.
        • Stack Variables: stack.tfvars files define stack-specific variables.

    Naming Constraints

    • Environment/Stack/Layer Instance names: Must match the regex ^[a-zA-Z0-9-]{,254}$.
    |-- environments/               # STACKS_ENVIRONMENTS_DIR (default: "environments")
    |   `-- production/             # Regex: ^[a-zA-Z0-9-]{,254}$
    |       `-- env.tfvars          # Exactly one per environment
    `-- stacks/                     # STACKS_STACKS_DIR (default: "stacks")
       |-- ec2/                     # Stack name
       |   |-- base/                # STACKS_BASE_DIR (default: "base")
       |   |   |-- backend.tf       # Terraform root module (Jinja supported)
       |   |   `-- modules/...      # Local modules (Jinja requires var.stacks_jinja_enabled_modules)
       |   |-- layers/              # STACKS_LAYERS_DIR (default: "layers")
       |   |   |-- production/      # Must match an environment name
       |   |   |   |-- layer.tfvars # Multiple allowed, sorted alphabetically
       |   |   |   `-- stacks.out/  # STACKS_OUTPUT_DIR (Exclude from VCS)
       |   |   `-- production_foo/  # Layer instance (suffix with _string)
       |   `-- stack.tfvars         # Multiple allowed, sorted alphabetically
       |-- global.tf                # Cross-stack reusability
       `-- globals.tfvars           # Cross-stack reusability
  11. How environments and stacks work together

    main

    Stacks uses a hierarchical model to manage Terraform deployments across different contexts:

    1. Environments: A unique deployment context (e.g., development, production). Environments are subdirectories of environments/. Each must contain an env.tfvars file for shared variables like IAM roles or state bucket settings. Environment names must match ^[a-zA-Z0-9-]{,254}$.
    2. Stacks: A collection of a Base and its Layers.
      • Base: The input code located in stacks/<stack_name>/base/ which translates to a Terraform root module.
      • Layers: Instances of a stack mapped to a specific environment, located in stacks/<stack_name>/layers/<environment_name>/.
    3. Variable Scoping:
      • env.tfvars: Environment-wide variables.
      • stack.tfvars: Variables injected into all layers of a specific stack.
      • layer.tfvars: Variables used to override stack-level settings for a specific environment/layer.
    |-- environments/
    |   `-- development/
    |       `-- env.tfvars
    `-- stacks/
       `-- vpc/
           |-- base/
           |   `-- main.tf
           |-- layers/
           |   `-- development/
           |       `-- layer.tfvars
           `-- stack.tfvars
  12. Understand the difference between Stacks and Terraform workspaces

    main

    While both allow for deploying the same root module multiple times with different input variables, they serve different primary purposes:

    • Stacks: A Stacks stack is a Terraform root module that you can deploy as many times as you have layers. It is designed for long-lived parallel deployments.
    • Terraform CLI workspaces: Designed primarily to enable testing changes on a separate state before modifying production. HashiCorp explicitly recommends against using workspaces for long-lived parallel deployments of the same root module.

    Note: HCP Terraform workspaces are a different feature and do not compare with Stacks.