Eight things I learned managing GitHub infrastructure using Terraform

Recently I've been using Terraform for managing Infrastructure as Code. It's a great tool but the learning curve can be a little steep at times, here are some of the things I've learned and my tips for creating infrastructure as efficiently as possible.

1. Alias the terraform command

terraform is a long word. Alias it to tf by adding alias tf=terraform to your bash profile and save yourself some typing.

2. Set up a remote backend

Avoid deleting your state by configuring an S3 and DynamoDB remote backend. This will also lock the state ensuring that multiple developers can collaborate safely.

3. Use a variable definition file

Easily create infrastructure for different environments b1y using a variable definition file such as dev.tfvars and passing it to terraform using the -var-file command line argument.

terraform plan -var-file="dev.tfvars"

4. You'll need to use a GitHub Personal Access token

When creating build pipelines using CodePipeline, AWS recommends that GitHub sources are managed using CodeStar but this option isn't available when using the Terraform GitHub provider Instead you'll need to create a GitHub Personal Access token but whatever you do make sure that you store the token in an environment variable and not in your repository.

provider "github" {
  token = var.GITHUB_TOKEN
}

5. Configure providers at the top level

Providers must be declared and configured at the top level

terraform {
  required_version = ">= 1.1.2"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.27"
    }

    github = {
      source  = "integrations/github"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  profile = var.aws_profile
  region  = var.aws_region
}

provider "github" {
  token = var.GITHUB_TOKEN
}

When using modules declare the required providers.

modules/github-repository/repo.tf

terraform {
  required_providers {
    github = {
      source  = "integrations/github"
      version = "~> 4.0"
    }
  }
}

6. You can't use count and for_each in the same module

If you want to create multiple similar resources when a condition is met, you will need to use the count and for_each Meta-Arguments, but you cannot use both of these in the same module. Instead create a child module to manage the resources and use count in the module definition.

modules/github-repository/repo.tf

module "github-project" {
  count  = local.has_projects ? 1 : 0
  source = "../github-project"

  repo_name = github_repository.repo.name
  projects  = var.config.projects
}

modules/github-project/project.tf

resource "github_repository_project" "project" {
  for_each   = var.projects
  name       = each.key
  repository = var.repo_name
  body       = each.value
}

7. Use outputs

Aggregate useful information such as repository urls and output it to the console.

repo_clone_urls = {
  "repo-one" = "git@github.com:LucasAmos/repo-one.git"
  "repo-two" = "git@github.com:LucasAmos/repo-two.git"
}

main.tf

output "repo_clone_urls" {
  value = { for repo in keys(var.repos) : repo => module.github-repository[repo].repo_url }
}

8. Use the lifecycle Meta-Argument to ignore resources that may change

Ordinarily you will want Terraform to track all infrastructure changes but this will not always be the case. Unless the lifecycle Meta-Argument is used when initialising the repository with a default .gitignore file its content will be restored to its initial state any time terraform apply is executed.

main.tf

resource "github_repository_file" "pr_template" {
  count          = local.gitignore ? 1 : 0
  repository     = github_repository.repo.name
  branch         = local.default_branch
  file           = ".gitignore"
  content        = "**/*.tfstate"
  commit_message = "Gitignore file"
  commit_author  = "Lucas Amos"
  commit_email   = "lucas@lucasamos.dev"

  depends_on = [
    github_branch.default
  ]

  lifecycle {
    ignore_changes = [
      content
    ]
  }
}

View all of the terraform code here

← Back to home