Git Product home page Git Product logo

tfgen's Introduction

tfgen - Terraform boilerplate generator

Maintenance GitHub go.mod Go version of a Go module GitHub stars

Terragrunt alternative to keep your Terraform code consistent and DRY

Overview

What is tfgen

tfgen is useful for maintaining and scaling a Terraform Monorepo, in which you provision resources in a multi-environment/account setup. It is designed to create consistent Terraform definitions, like backend (with dynamic key), provider, and variables for each environment/account, as defined in a set of YAML configuration files.

Why tfgen

Terragrunt - a thin wrapper for Terraform that provides extra tools for working with multiple Terraform modules - is a great tool and inspired me a lot to create tfgen, but instead of being a wrapper for the Terraform binary, tfgen just creates Terraform files from templates and doesn't interact with Terraform at all. Terraform will be used independently on your local environment or in your CI system to deploy the resources.

  • This is not just a tool, it's a way of doing things
  • Keep your Terraform configuration consistent across the environments
  • Reduce the risk of making mistakes while copying+pasting your backend, provider, and other common Terraform definitions
  • Increase your productivity
  • Scale your mono repo following the same pattern across the modules

Features

  • Builtin functionality to provide the remote state key dynamically
  • YAML file configuration
  • Templates are parsed using Go templates

Getting Started

Prereqs

  • Docker or Go

Installation

git clone --depth 1 [email protected]:0xDones/tfgen.git
cd tfgen

# Using Docker
docker run --rm -v $PWD:/src -w /src -e GOOS=darwin -e GOARCH=amd64 golang:alpine go build -o bin/tfgen

# Using Go
go build -o bin/tfgen

mv bin/tfgen /usr/local/bin

Note: when building using Docker, change GOOS=darwin to GOOS=linux or GOOS=windows based on your system

Usage

Basic Usage

$ tfgen help
tfgen is a devtool to keep your Terraform code consistent and DRY

Usage:
  tfgen [command]

Available Commands:
  clean       clean templates from the target directory
  completion  Generate the autocompletion script for the specified shell
  exec        Execute the templates in the given target directory
  help        Help about any command

Flags:
  -h, --help      help for tfgen
  -v, --verbose   verbose output

Use "tfgen [command] --help" for more information about a command.

Configuration files

The configuration files are written in YAML and have the following structure:

---
root_file: bool
vars:
  var1: value1
  var2: value2
template_files:
  template1.tf: |
    template content
  template2.tf: |
    template content

How config files are parsed

tfgen will recursively look for all .tfgen.yaml files from the target directory up to the parent directories until it finds the root config file, if it doesn't find the file it will exit with an error. All the other files found on the way up are merged into the root config file, and the inner config file has precedence over the outer.

We have two types of configuration files:

  1. Root config
  2. Environment specific config

Root config

In the root config file, you can set variables and templates that can be reused across all environments. You need at least 1 root config file.

# infra-live/.tfgen.yaml
---
root_file: true
vars:
  company: acme
template_files:
  _backend.tf: |
    terraform {
      backend "s3" {
        bucket         = "my-state-bucket"
        dynamodb_table = "my-lock-table"
        encrypt        = true
        key            = "{{ .Vars.tfgen_state_key }}/terraform.tfstate"
        region         = "{{ .Vars.aws_region }}"
        role_arn       = "arn:aws:iam::{{ .Vars.aws_account_id }}:role/terraformRole"
      }
    }
  _provider.tf: |
    provider "aws" {
      region = "{{ .Vars.aws_region }}"
      allowed_account_ids = [
        "{{ .Vars.aws_account_id }}"
      ]
    }
  _vars.tf: |
    variable "env" {
      type    = string
      default = "{{ .Vars.env }}"
    }

Note that aws_region, aws_account, and env are variables that you need to provide in the environment-specific config. tfgen_state_key is provided by the tfgen, it will be explained below.

Environment specific config

In the environment-specific config file (non-root), you can pass additional configuration, or override configuration from the root config file. You can have multiple specific config files, all of them will be merged into the root one.

# infra-live/dev/.tfgen.yaml
---
root_file: false
vars:
  aws_account_id: 111111111111
  aws_region: us-east-1
  env: dev

# infra-live/prod/.tfgen.yaml
---
root_file: false
vars:
  aws_account_id: 222222222222
  aws_region: us-east-2
  env: prod
template_files:
  additional.tf: |
    # I'll just be created on modules inside the prod folder

Provided Variables

These variables are automatically injected into the templates:

  • tfgen_state_key: The path from the root config file to the target directory

Practical Example

Repository Structure

The terraform-monorepo-example repository can be used as an example of how to structure your repository to leverage tfgen and also follow Terraform best practices.

.
├── infra-live
│   ├── dev
│   │   ├── networking
│   │   ├── s3
│   │   ├── security
│   │   ├── stacks
│   │   └── .tfgen.yaml     # Environment specific config
│   ├── prod
│   │   ├── networking
│   │   ├── s3
│   │   ├── security
│   │   ├── stacks
│   │   └── .tfgen.yaml     # Environment specific config
│   └── .tfgen.yaml         # Root config file
└── modules
    └── my-custom-module

Inside our infra-live folder, we have two environments, dev and prod. They are deployed in different aws accounts, and each one has a different role that needs to be assumed in the provider configuration. Instead of copying the files back and forth every time we need to create a new module, we'll let tfgen create it for us based on our configuration defined on the .tfgen.yaml config files.

Running the exec command

Let's create the common files to start writing our Terraform module

# If you didn't clone the example repo yet
git clone [email protected]:0xDones/terraform-monorepo-example.git
cd terraform-monorepo-example

# Create a folder for our new module
mkdir -p infra-live/dev/s3/dev-tfgen-bucket
cd infra-live/dev/s3/dev-tfgen-bucket

# Generate the files
tfgen exec .

# Checking the result (See Output section)
cat _backend.tf _provider.tf _vars.tf

This execution will create all the files inside the working directory, executing the templates and passing in all the variables declared in the config files.

Output

This will be the content of the files created by tfgen:

_backend.tf

terraform {
  backend "s3" {
    bucket         = "my-state-bucket"
    dynamodb_table = "my-lock-table"
    encrypt        = true
    key            = "dev/s3/dev-tfgen-bucket/terraform.tfstate"
    region         = "us-east-1"
    role_arn       = "arn:aws:iam::111111111111:role/terraformRole"
  }
}

_provider.tf

provider "aws" {
  region = "us-east-1"
  allowed_account_ids = [
    "111111111111"
  ]
}

_vars.tf

variable "env" {
  type    = string
  default = "dev"
}

Next steps

After creating the common Terraform files, you'll probably start writing your main.tf file. So at this point, you already know what to do.

terraform init

terraform plan -out tf.out

terraform apply tf.out

Related

Have fun!

tfgen's People

Contributors

0xdones avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

tfgen's Issues

[FEATURE] Traverse Sub Directories

We use a large mono repo, and there are times when it would be nice to be able to update multiple states with the new templates in one go.

Currently we have to get into each dir, or use some bash-isms to loop through. Would be great, if you could tell tfgen to run in all subdirs, that contains a .tf file or something

[FEATURE] Add a target to root relative path variable

This would allow you to consistently reference the path to a directory as a variable. For instance, being able to reference a modules directory.

---
root_file: true
vars:
  tenant: default
  aws_region: us-west-1
template_files:
  _backend.tf: |
    terraform {
      backend "s3" {
        bucket         = "my-state-bucket"
        dynamodb_table = "my-lock-table"
        encrypt        = true
        key            = "{{ .Vars.tfgen_state_key }}/terraform.tfstate"
        region         = "{{ .Vars.aws_region }}"
        role_arn       = "arn:aws:iam::{{ .Vars.aws_account_id }}:role/terraformRole"
      }
    }
  _provider.tf: |
    provider "aws" {
      region = "{{ .Vars.aws_region }}"
      allowed_account_ids = [
        "{{ .Vars.aws_account_id }}"
      ]
    }
  _vars.tf: |
    variable "env" {
      type    = string
      default = "{{ .Vars.env }}"
    }
    variable "modules-directory" {
      type = string
      default = "{{ .Vars.target_to_root_relative_path }}/modules"
    }

Unable to Template when building from Latest

❯ git clone --depth 1 [email protected]:refl3ction/tfgen.git
Cloning into 'tfgen'...
remote: Enumerating objects: 17, done.
remote: Counting objects: 100% (17/17), done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 17 (delta 1), reused 16 (delta 1), pack-reused 0
Receiving objects: 100% (17/17), 22.55 KiB | 1.50 MiB/s, done.
Resolving deltas: 100% (1/1), done.
  ~/repos                                                                                                                                                                                                                      02:37:47 PM
❯ cd tfgen
  ~/repos/tfgen   main                                                                                                                                                                                                       02:37:51 PM
❯ docker run --rm -v $PWD:/src -w /src -e GOOS=linux -e GOARCH=amd64 golang:alpine go build -o bin/tfgen

Same error if I clone, and just build directly with go as well

Leads to these errors, with a already working setup.

] ConfigFileDir:/home/justin/company/company_beta_infra}
2:43PM DBG writing template file: /home/justin/company/company_beta_infra/dev/deploys/jls/_remote.tf
2:43PM ERR failed to execute templates error="template: /home/justin/company/company_beta_infra/dev/deploys/jls/_remote.tf:4:51: executing \"/home/justin/company/company_beta_infra/dev/deploys/jls/_remote.tf\" at <.aws_account_id>: can't evaluate field aws_account_id in type tfgen.TemplateContext"
2:43PM DBG writing template file: /home/justin/company/company_beta_infra/dev/deploys/jls/_remote_dmz.tf
2:43PM ERR failed to execute templates error="template: /home/justin/company/company_beta_infra/dev/deploys/jls/_remote_dmz.tf:4:51: executing \"/home/justin/company/company_beta_infra/dev/deploys/jls/_remote_dmz.tf\" at <.aws_account_id>: can't evaluate field aws_account_id in type tfgen.TemplateContext"
2:43PM DBG writing template file: /home/justin/company/company_beta_infra/dev/deploys/jls/_versions.tf
2:43PM DBG writing template file: /home/justin/company/company_beta_infra/dev/deploys/jls/_backend.tf
2:43PM ERR failed to execute templates error="template: /home/justin/company/company_beta_infra/dev/deploys/jls/_backend.tf:2:25: executing \"/home/justin/company/company_beta_infra/dev/deploys/jls/_backend.tf\" at <.tf_version>: can't evaluate field tf_version in type tfgen.TemplateContext"
2:43PM DBG writing template file: /home/justin/company/company_beta_infra/dev/deploys/jls/_data.tf
2:43PM ERR failed to execute templates error="template: /home/justin/company/company_beta_infra/dev/deploys/jls/_data.tf:8:33: executing \"/home/justin/company/company_beta_infra/dev/deploys/jls/_data.tf\" at <.env>: can't evaluate field env in type tfgen.TemplateContext"
2:43PM DBG writing template file: /home/justin/company/company_beta_infra/dev/deploys/jls/_common.tf
2:43PM ERR failed to execute templates error="template: /home/justin/company/company_beta_infra/dev/deploys/jls/_common.tf:2:21: executing \"/home/justin/company/company_beta_infra/dev/deploys/jls/_common.tf\" at <.env>: can't evaluate field env in type tfgen.TemplateContext"
2:43PM DBG writing template file: /home/justin/company/company_beta_infra/dev/deploys/jls/_provider.tf
2:43PM ERR failed to execute templates error="template: /home/justin/company/company_beta_infra/dev/deploys/jls/_provider.tf:2:15: executing \"/home/justin/company/company_beta_infra/dev/deploys/jls/_provider.tf\" at <.aws_region>: can't evaluate field aws_region in type tfgen.TemplateContext"
2:43PM DBG deleting file: /home/justin/company/company_beta_infra/dev/deploys/jls/_versions.tf
2:43PM DBG deleting file: /home/justin/company/company_beta_infra/dev/deploys/jls/_backend.tf
2:43PM DBG deleting file: /home/justin/company/company_beta_infra/dev/deploys/jls/_data.tf
2:43PM DBG deleting file: /home/justin/company/company_beta_infra/dev/deploys/jls/_common.tf
2:43PM DBG deleting file: /home/justin/company/company_beta_infra/dev/deploys/jls/_provider.tf
2:43PM DBG deleting file: /home/justin/company/company_beta_infra/dev/deploys/jls/_remote.tf
2:43PM DBG deleting file: /home/justin/company/company_beta_infra/dev/deploys/jls/_remote_dmz.tf
2:43PM FTL error="failed to generate one or more templates, please check your configuration"

If i put back my old bin, it works fine.

[FEATURE] - Check Mode

It would be great, if it was possible to add a check mode to this, which would just output the diff between the current templates, and what would be created/updated if tfgen exec . was ran.

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.