Resourcely.yaml

Configuring Resourcely.yaml

This guide will walk you through configuring .resourcely.yaml in your Terraform repository.

What is .resourcely.yaml?

.resourcely.yaml is a config file that provides Resourcely with metadata about the structure of a repository's Terraform:

  • paths to the Config Roots in the Repo. These are where Resourcely can emit Terraform.

  • for config roots that are instantiated in multiple environments, what these environments are and where the environment-specific values (*.tfvars) are stored.

Resourcely Create will use this metadata to help developers

  • place their Terraform config in the correct paths

  • provide values for all environments

  • without them needing to be aware of the repository or Terraform structure themselves.

Where to place .resourcely.yaml?

The .resourcely.yaml files may be placed anywhere within a repo. Multiple such files are allowed, primarily to support large mono-repos where subdirectories are owned by different teams.

The only restriction is that .resourcely.yaml files cannot be nested. No subdirectories below a .resourcely.yaml may contain a .resourcely.yaml.

.resourcely.yaml Structure

There are a variety of ways that Terraform config can be structured, including:

  • No-envs: One config root, no environments, one state file.

  • Multiple-envs: One config root, one or more *.tfvars files per environment, one state file per environment.

  • Module: One config root that contains reusable Terraform code to be invoked by other config roots. (coming soon)

Resourcely automatically detects which config roots are modules. The contents of .resourcely.yaml specify whether a config root has environments or not.

version: "2"

# Each .resourcely.yaml file manages all config roots in or below its directory.
#
# - A subdirectory of a .resourcely.yaml directory may not contain its own
#   .resourcely.yaml file.
#
# - All the paths and files referenced in a .resourcely.yaml must be in or below
#   its directory. References cannot "escape" that directory.

# List of directories that contain Terraform configuration.
#
# In the Resourcely Pull Request UI, a developer will pick one of these
# locations to put their new resources in.
terraform_config_roots:

  - # Name that will be shown in the Resourcely Pull Request UI
    # dropdown menu.
    #
    # Example: "Live Site Infra"
    name: <string>

    # (optional)
    #
    # Longer description for the config root. If developers may have trouble
    # selecting the correct config root, use this text to help guide them.
    description: <string>

    # (optional)
    #
    # Instructs Resourcely to ignore this config root. It will not be
    # shown in the Resourcely Pull Request UI dropdown menu.
    skip: true | false

    # Path to the config root, the directory containing the *.tf
    # files.
    #
    # Relative to the location of this .resourcely.yaml file
    #
    # If the config root is the same directory, specify
    #     path: .
    #
    # Example: live_site
    path: <string>

    # (optional, conflicts with option_groups)
    # 
    # Name of the file in the `path` directory in which to place
    # new resources by default.
    #
    # Developers may pick a different file, but will be defaulted to this one.
    #
    # Example: main.tf
    default_file: <string>

    # (optional, conflicts with option_groups)
    #
    # Name of the file in the `path` directory in which to place new
    # variable declarations for new environment-specific values.
    #
    # For `module` configs, these variables are the module's inputs.
    #
    # Example: variables.tf
    var_file: <string>
    
    # (optional, conflicts with default_file and var_file)
    #
    # Option group allow you to specify different conventions for producing
    # Terraform into this config root's directory. When creating a new pull
    # request, users will choose an option group, rather than the "base"
    # config root.
    #
    # Use option_groups when a single directory has multiple conventions,
    # such as placing new resources in "frontend.tf" vs "backend.tf".
    #
    # If option_groups is specified, it is illegal to specify default_file
    # or var_file. If either default_file or var_file is specified, it is
    # illegal to specify option_groups.
    option_groups:
    
      - # (required, must be unique within the config_root)
        # 
        # A non-user-visible identifier for the option group. Change the id
        # causes unwanted behavior when users edit old pull requests through
        # Resourcely.
        # 
        # Example: 'frontend' or 'backend'
        id: <string>
        
        # (required, must be unique within the config_root)
        # 
        # The user-visible label for this option group. Changing the label
        # is safe. 
        # 
        # Example: 'Frontend' or 'Backend'
        # 
        # Using the example from config_root.name and the two examples above,
        # users would see:
        # - Live Site Infra (Frontend)
        # - Live Site Infra (Backend)
        label: <string>
      
        # (optional)
        # 
        # Name of the file in the `path` directory in which to place
        # new resources by default. Similar to config_root.default_file.
        # 
        # Example: frontend.tf or backend.tf
        default_file: <string>
        
        # (optional)
        # 
        # Name of the file in the `path` directory in which to place new
        # variable declarations for new environment-specific values. Similar
        # to config_root.var_file.
        # 
        # Example: frontend_variables.tf, backend_variables.tf
        var_file: <string>

    # (optional)
    #
    # Name of the file in which to place the variables definitions
    # for this config root.
    #
    # Mutually exclusive with the `environments` option below. Use this if
    # your config root does not have multiple environments, but does use
    # tfvars as way of defining "constant" values.
    #
    # The config root name will be used for `context.environment` in
    # guardrails and blueprints.
    #
    # Relative to the config root `path` directory.
    #
    # Example: constants.tfvars
    tfvars_file: <string>

    # (optional)
    #
    # Additional files that contain existing variable declariations.
    # If the same variable appears in more than one of these files (or in tfvars_file and one of these files),
    # Resourcely will use the value in tfvars_file or the one that comes last in this list.
    #
    # Allowed, but not required, when tfvars_file is set.
    #
    # Relative to the config root `path` directory.
    #
    # Example: ../global.tfvars
    extra_tfvars_files: <list of strings>

    # (optional)
    # 
    # Context from the config root that will be used when evaluating context-aware guardrails, such as
    # `WHEN aws_s3_bucket AND CONTEXT data = "PII"`.
    # The key of the map might match an existing Global or Local context question.
    # If there is a conflict between a map key and a context question, Resourcely will use the value from the config root.
    # The value of the map entry should be one of the options for a single/multi-select question
    # or match the format for a text question.
    # Example:
    # context:
    #   data: "PII"
    #   owner: "example@resourcely.io"
    context: <map of strings>


    # (optional)
    #
    # List of the environments that this config root supports.
    #
    # Mutually exclusive with the `tfvars_file` option above. Use this if
    # you run `terraform apply` in this config root multiple times with
    # different values for the vars.
    #
    # If this list is missing or empty and `tfvars_file` above is not set,
    # developers will not be allowed to use Terraform variables.
    #
    # If non-empty, a developers will be allowed to use Terraform variables.
    # For any new variable, they will have to supply a value for each
    # environment in this list.
    environments:

      - # Name of the environment.
        #
        # This name is used for two purposes.
        # 1. It is shown in the UI to the developers.
        # 2. It is used as the value for `context.environment` in guardrails
        #    and blueprints.
        name: <string>

        # Name of the file in which to place the variables definitions
        # for this environment.
        #
        # Relative to the config root `path` directory.
        #
        # Example: envs/dev.tfvars
        tfvars_file: <string>

        # (optional)
        #
        # Additional files that contain existing variable declariations.
        # If the same variable appears in more than one of these files (or in tfvars_file and one of these files),
        # Resourcely will use the value in tfvars_file or the one that comes last in this list.
        #
        # Allowed, but not required when tfvars_file is set.
        #
        # Relative to the config root `path` directory.
        #
        # Example: ../global.tfvars
        extra_tfvars_files: <list of strings>


        # (optional)
        # 
        # Context from this environment that will be used when evaluating guardrails.
        # For example, `WHEN aws_s3_bucket AND CONTEXT tier = "1"`
        # The key of the map might match an existing Global or Local context question.
        # If there is a conflict between a map key and a context question or a config-root-level value, Resourcely will use the value from the environment.
        # The value of the map entry should be one of the options for a single/multi-select question
        # or match the format for a text question.
        #
        # These answers do not need to be defined for every environment.
        # Example:
        # context:
        #   tier: "1"

        context: <map of strings>

Examples

# Repo with one very simple config root
#
# Assume the repo is stuctured like
#
# network.tf
# main.tf
# providers.tf
# terraform.tf

version: "2"

terraform_config_roots:
  - name: "Project A"
    path: .
    default_file: main.tf
# Repo with one config root that uses tfvars
# to hold constant values, but not for multiple environments.
#
# Assume the repo is stuctured like
#
# network.tf
# main.tf
# providers.tf
# terraform.tf
# variables.tf
# constants.tfvars

version: "2"

terraform_config_roots:
  - name: "Project B"
    path: .
    default_file: main.tf
    var_files: variables.tf
    tfvars_file: constants.tfvars
# Repo with one Terraform root module that uses tfvars
# to instantiante in multiple environments.
#
# Assume the repo is stuctured like
#
# network.tf
# main.tf
# providers.tf
# terraform.tf
# variables.tf
# global.tfvars
# envs/
#   | dev.tfvars
#   | stage.tfvars
#   | envs/prod.tfvars

version: "2"

terraform_config_roots:
  - name: "Project C"
    path: .
    default_file: main.tf
    var_file: variables.tf
    environments:
      - name: dev
        tfvars_file: envs/dev.tfvars
        extra_tfvars_files: ["global.tfvars"]
      - name: stage
        tfvars_file: envs/stage.tfvars
        extra_tfvars_files: ["global.tfvars"]
      - name: prod
        tfvars_file: envs/prod.tfvars
        extra_tfvars_files: ["global.tfvars"]

# Repo with multiple Terraform root modules.
#
# Assume the repo is stuctured like
#
# projectA/
#   | network.tf
#   | main.tf
#   | providers.tf
#   | terraform.tf
# projectB/
#   | network.tf
#   | main.tf
#   | providers.tf
#   | terraform.tf
#   | variables.tf
#   | envs/
#   |   | dev.tfvars
#   |   | stage.tfvars
#   |   | prod.tfvars

version: "2"

terraform_config_roots:
  - name: "Project A"
    path: projectA
    default_file: main.tf

  - name: "Project B"
    path: projectB
    default_file: main.tf
    var_file: variables.tf
    environments:
      - name: dev
        tfvars_file: envs/dev.tfvars
      - name: stage
        tfvars_file: envs/stage.tfvars
      - name: prod
        tfvars_file: envs/prod.tfvars

How to create resourcely.yaml

You can use the scaffolding we created to get started with the basic resourcely.yaml file.

If you have added the Resourcely.yaml file and still cannot see the config root in the pull request creation form, you need to trigger a fresh scan. To do this, click on Settings -> Update Settings -> Queue Scan, and wait 1-2 minutes for the scan to detect the new Resourcely.yaml file.

Context in resourcely.yaml

If you want guardrails to behave differently in different config roots or environments, you can specify metadata in .resourcely.yaml. Guardrails can access this metadata with syntax like this:

GUARDRAIL "Production buckets must have suffix -prod"
    WHEN aws_s3_bucket AND CONTEXT production = "true"
        REQUIRE bucket ENDS WITH "-prod"

.resourcely.yaml can provide metadata for each config root or environment. Guardrails will load the correct metadata based on resourcely-cli arguments - see Advanced resourcely-cli usage.

You can specify metadata like this:

version: "2"

terraform_config_roots:
  - name: "Project C"
    path: .
    context:
      tier: "0"
      production: "false"
    environments:
      - name: dev
        tfvars_file: envs/dev.tfvars
      - name: prod
        tfvars_file: envs/prod.tfvars
        context:
          production: "true"

With this yaml, guardrails would use the following metadata, depending on which environment they are evaluating:

// When evaluating plans for 'dev':
tier = "0"
production = "false"
environment = "dev"

// When evaluating plans for 'prod':
tier = "0"
production = "true"
environment = "prod"

Resourcely automatically provides some metadata out of the box:

  • environment is the name of the environment being evaluated.

When running resourcely-cli in your pipeline, please ensure you include the --config_root_path and --environment options. This allows the CLI to properly load the context from the specified configuration root and environment.

Example: resourcely-cli --config_root_path {config_root_name} --environment {environment_name}

Collisions

.resourcely.yaml can specify multiple values for the same key. It can also specify values for keys that match Global Context questions. When a collision occurs, Resourcely picks a value based on the following rules:

  • Values from environment.context in .resourcely.yaml have the highest priority.

  • Values from terraform_config_roots.context in .resourcely.yaml have the next-highest priority.

  • Values collected from the user through Global Context questions have the lowest priority.

Creating resourcely.yaml via the API

The following endpoint sets the Terraform configuration for a repo used with Resourcely. It can be used in conjunction with .resourcely.yaml files. If a config root is present in both an API config and a .resourcely.yaml config, the API config will take precedence.

Each POST request must include the configurations for ALL config roots that are to be set via the API. For example, if configuration roots foo, bar, and baz are set via the API in one call, and a subsequent call specifies only foo and bar, the configuration for baz will no longer be managed through the API.

Endpoint

POST /api/v1/terraform/config

Request

repo_url (string, required): Git repository URL. Resourcely will scan this repo asynchronously to determine existing resources within config roots

version (string, required): Version of config structure

Supported values: ["1", "2"]

provider (string, required): Git provider

Supported values: ["REMOTE_GIT_GITHUB", "REMOTE_GIT_GITLAB"]

terraform_configs(list, required): List of Terraform configs. See .resourcely.yaml documentation for field descriptions

An empty list will delete all existing Terraform configs set by the API. Configuration will fall back to .resourcely.yaml files (if they exist).

Example

Assume a repo is structured as follows:

# <https://github.com/ResourcelyTest/terraform>

# projectA/
#   | network.tf
#   | main.tf
#   | providers.tf
#   | terraform.tf
# projectB/
#   | network.tf
#   | main.tf
#   | providers.tf
#   | terraform.tf
#   | variables.tf
#   | envs/
#   |   | dev.tfvars
#   |   | stage.tfvars
#   |   | prod.tfvars
{
  "repo_url": "<https://github.com/ResourcelyTest/terraform>",
  "version": "2",
  "provider": "REMOTE_GIT_GITHUB",
  "terraform_configs": [
    {
      "name": "Project A",
      "path": "projectA",
      "default_file": "main.tf",
      "skip": false
    },
    {
      "name": "Project B",
      "default_file": "main.tf",
      "path": "projectB",
      "skip": false,
      "var_file": "variables.tf",
      "environments": [
        {
          "name": "dev",
          "tfvars_file": "dev.tfvars"
        },
        {
          "name": "stage",
          "tfvars_file": "stage.tfvars"
        },
        {
          "name": "prod",
          "tfvars_file": "prod.tfvars"
        }
      ]
    }
  ]
}

Response

On success the following is expected

204 No Content

Getting Terraform configurations via the API

The following endpoint gets the Terraform configuration for a repo used with Resourcely. It can be used to get configurations set with .resourcely.yaml files as well those set by API.

Endpoint

GET /api/v1/terraform/config

Query Params

repo_url (string, required): Git repository URL.

source (string, optional): If provided, filters Terraform configs to those set by API or by scanning .resourcely.yaml.

Supported values: ["TERRAFORM_CONFIG_SOURCE_SCAN", "TERRAFORM_CONFIG_SOURCE_API"]

config_root_paths (list(string), optional): Config root paths relative to the repository root.

Response

On success the following response type is expected

200 OK
{
  "repo_url": "https://github.com/ResourcelyTest/terraform",
  "terraform_configs": [
    {
      "terraform_config": {
        "name": "Project A",
        "description": "",
        "type": "simple",
        "path": "projectA",
        "skip": false,
        "default_file": "main.tf",
        "var_file": "",
        "extra_tfvars_files": [],
        "context": null,
        "environments": [],
        "module_invocations": []
      },
      "version": "2",
      "source": "TERRAFORM_CONFIG_SOURCE_API"
    },
    {
      "terraform_config": {
        "name": "Project B",
        "description": "",
        "type": "simple",
        "path": "projectB",
        "skip": false,
        "default_file": "main.tf",
        "var_file": "variables.tf",
        "extra_tfvars_files": [],
        "context": null,
        "environments": [
          {
            "name": "dev",
            "tfvars_file": "dev.tfvars",
            "extra_tfvars_files": [],
            "context": null
          },
          {
            "name": "stage",
            "tfvars_file": "stage.tfvars",
            "extra_tfvars_files": [],
            "context": null
          }
        ],
        "module_invocations": []
      },
      "version": "2",
      "source": "TERRAFORM_CONFIG_SOURCE_API"
    }
  ]
}

Validate resourcely.yaml via CLI

To validate one or more .resourcely.yaml terraform configurations run the following:

  1. Generate Resourcely API Token from Settings -> Generate API token

  2. Set the token environment variable Export RESOURCELY_API_TOKEN=<TOKEN>

  3. Run the validate command

resourcely-cli validate repoconfig --file /path/to/.resourcely.yaml --file /path/to/another/.resourcely.yaml

Must be run from the git repository's root and file paths must also be relative to the git repository's root

Last updated