Giter Club home page Giter Club logo

example-terraform-workspace-config's Introduction

The suggested best practices for organizing configuration for multiple workspaces/environments is to call Terraform with -var-file=$env to include a specific tfvars file.

Of course this works. But it seems to be error prone if you allow to trigger an apply against any workspace with just any configuration:

terraform workspace select production
terraform apply -var-file=config/staging.tfvars

Furthermore this cannot be used in Terraform Cloud, where you have to specify workspace related vars in the workspace configuration itself:

Terraform Cloud Variable configuration

There is no option for including tfvars per workspace.

Select config automatically based on the workspace

Unfortunately there is no functionality to automatically include a tfvars file based on the workspace name nor is there support for conditionally including tfvars files.

So you got to build something yourself. You can access the current workspace name via terraform.workspace. There are a couple of things you can do with this value.

Below are 3 solutions which all have the same exact outcome. The defined config is stored in local.config and can be access via local.config.ec2_instance_type etc.

All 3 solutions support default values, so you're not required to define every config option in every environment.

All examples are available in this repository.

1. Inline expressions to select correct config from a map

All config is held is a single file config.tf:

locals {
  configs = {

    _defaults = {
      ec2_instance_type = "t2.nano"
      regions           = ["us-east-1"]
    }

    dev = {} // use config from _defaults ^

    staging = {
      ec2_instance_type = "t2.medium"
      regions = [
        "us-east-1",
        "eu-central-1",
      ]
    }

    production = {
      ec2_instance_type = "t2.xlarge"
      regions = [
        "us-east-1",
        "us-west-2",
        "eu-central-1",
        "ap-east-1",
      ]
    }

  }

  config = merge(
    lookup(local.configs, "_defaults"),
    lookup(local.configs, terraform.workspace)
  )
}

Pros:

  • Straight forward

Cons:

  • Cannot split config into separate files. Therefore could quickly get hard to maintain and compare environment config.

2. Load config from YAML files

Directory structure:

.
├── config
│   ├── _defaults.yml
│   ├── dev.yml
│   ├── production.yml
│   └── staging.yml
├── config.tf
├── main.tf

Example content of config/production.yml:

---
ec2_instance_type: t2.xlarge

regions:
  - us-east-1
  - us-west-2
  - eu-central-1
  - ap-east-1

The config is loaded in config.yml:

data "local_file" "defaults" {
  filename = "${path.module}/config/_defaults.yml"
}

data "local_file" "config" {
  filename = "${path.module}/config/${terraform.workspace}.yml"
}

locals {
  config = merge(
    yamldecode(data.local_file.defaults.content),
    yamldecode(data.local_file.config.content)
  )
}

Pros:

  • Straight forward
  • Config for every environment resides in its own file

Cons:

  • No HCL expressions are possible in the config itself

3. Create a module per environment and return the config as an output

Directory structure:

.
├── config
│   ├── _defaults
│   │   └── outputs.tf
│   ├── dev
│   │   └── outputs.tf
│   ├── main.tf
│   ├── production
│   │   └── outputs.tf
│   └── staging
│       └── outputs.tf
├── main.tf

In every config/$env/outputs.tf a single output is defined like this:

config/_defaults/outputs.tf:

output "data" {
  value = {
    ec2_instance_type = "t2.nano"
    regions = [
      "us-east-1",
    ]
  }
}

config/dev/outputs.tf:

output "data" {
  value = {} // use config from _defaults
}

config/staging/outputs.tf:

output "data" {
  value = {
    ec2_instance_type = "t2.medium"
    regions = [
      "us-east-1",
      "eu-central-1",
    ]
  }
}

config/production/outputs.tf:

output "data" {
  value = {
    ec2_instance_type = "t2.xlarge"
    regions = [
      "us-east-1",
      "us-west-2",
      "eu-central-1",
      "ap-east-1",
    ]
  }
}

Since you cannot use variables in a module source parameter all 4 modules have to be defined in every environment. Furthermore you cannot directly access a module by name when the name is not hardcoded, so you need to additionally create a mapping like so:

config/main.tf:

module "_defaults" {
  source = "./_defaults"
}

module "dev" {
  source = "./dev"
}

module "staging" {
  source = "./staging"
}

module "production" {
  source = "./production"
}

locals {
  data_map = {
    dev        = module.dev.data,
    staging    = module.staging.data,
    production = module.production.data,
  }
}

output "data" {
  value = merge(
    module._defaults.data,
    lookup(local.data_map, terraform.workspace)
  )
}

In the main.tf then the module needs to be loaded and for convenience the output gets registered as a local value:

module "config" {
  source = "./config"
}

locals {
  config = module.config.data
}

Pros:

  • Config for every environment resides in its own file

Cons:

  • Complex setup

Conclusion

Using modules as config provider seems to be the best solution, as you can split the configuration into separate files which supports HCL expressions. The setup though is complex and requires some additional boilerplate code for every additional environment.

If you have no need for HCL expressions, the YAML solution seems to be nice as it is easy to setup and IMHO is very readable to humans.

example-terraform-workspace-config's People

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.