How can I use Terraform to manage my GitHub repositories?

Nikita Barskov,5 min read

This article is highly inspired and overlaps with "Using Terraform to Manage Git Repositories" by Chris Wahl. You can also read the original (opens in a new tab) text.

Motivation

I want to learn and share my vision of working with Infrastructure as Code tooling like Terraform. Having my repositories managed in this manner is a good outcome.

Prerequisites

Before you start, you need

Structure of the repository

At the moment of writing, I already had a repository for all my personal development: this blog, my previous attempts to configure my private GitHub with Terraform, etc.

I tried to structure and figure out the way I should structure my management repository.

After several iterations, I realised that the most convenient for me layout should look like this:

.
├── README.MD
└── infrastructure
    └── terraform

The infrastructure folder has all the required and necessary code to manage my personal infrastructure: integration with Cloud Providers, management of my version control, etc.

terraform is a tool-specific subfolder for infrastructure. I might want to test Pulumi or other IaC tooling in the future. This project structure allows me to try it flexibly.

If you want to follow the suggested structure, then run in the root of your repository:

mkdir -p infrastructure/terraform

Creating a boilerplate for our Terraform code

To configure Terraform to work with GitHub, we need to:

Configure Terraform backend and Terraform version

For simplicity, I started with the local (opens in a new tab) backend. It has its own downsides, but I can improve it later.

Create backend.tf file in infrastructure/terraform folder with the following content:

terraform {
  backend "local" {}
}

To configure preferred Terraform version I created versions.tf

terraform {
  required_version = ">= 1.0"
}

Configure Terraform providers

We need to configure GitHub Integration (opens in a new tab) Terraform provider.

First, let's add the provider. To do this, change the content of versions.tf to:

terraform {
  required_version = ">= 1.0"
 
  required_providers {
    github = {
      source  = "integrations/github"
      version = "~> 5.9"
    }
  }
}

GitHub Integration Terraform provider also requires authentication (opens in a new tab). In this article, I am going to use token based authentication.

Create a file providers.tf and put these lines of code there

provider "github" {
  owner = "<change-me-name-of-github-profile>"
  token = var.github_oauth_token
}

Instead of <change-me-name-of-github-profile> put your GitHub profile user name. In my case it is nikitabarskov.

As you can see, I use Terraform variables (opens in a new tab) to cofigure provider. Before I used to configure them in a separate file variables.tf.

variable "github_oauth_token" {
  type      = string
  sensitive = true
}

Run Terraform

Run a command

terraform init

and then

terraform plan

and you should be able to see the expected output:

var.github_oauth_token
Enter a value:

You need to submit a PAT for GitHub. You can follow the instruction "Creating a personal access token" (opens in a new tab) from GitHub. I will just list a set of permission I used, when I was creating one: admin:gpg_key, admin:org, admin:public_key, admin:repo_hook, admin:ssh_signing_key, delete_repo, repo, user.

Once you created a PAT, run terraform plan again, and submit the PAT as an input.

Great job!

Import the repository

Let's bring your empty repository you are working on now to Terraform.

In my case, I have a repository dev with some ClickOps changes I made before.

First, I am trying to structure the things I am doing. There are several ways how you can structure your Terraform code.

For me, the most suitable one is to keep all Cloud resources related to the actual resource I am using together.

In the case of GitHub repositories, I use a file called github_repositories.tf.

So, to import the repository to Terraform, I make this list of actions:

  1. I put these lines in github_repositories.tf

    resource "github_repository" "dev" {
      name = "dev"
    }
  2. I import dev repository to Terraform state

    terraform import github_repository.dev dev

    It will ask you to submit your GitHub PAT again. The successfull output of terraform import command should look like that

    github_repository.dev: Importing from ID "dev"...
    github_repository.dev: Import prepared!
    Prepared github_repository for import
    github_repository.dev: Refreshing state... [id=dev]
     
    Import successful!
     
    The resources that were imported are shown above. These resources are now in
    your Terraform state and will henceforth be managed by Terraform.
  3. I run terraform plan again to see the differences between the actual configuration and the configuration I described in my Terraform code.

    terraform plan

    And the output for me looks like

    github_repository.dev: Refreshing state... [id=dev]
     
    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
      ~ update in-place
     
    Terraform will perform the following actions:
     
      # github_repository.dev will be updated in-place
      ~ resource "github_repository" "dev" {
          ~ allow_auto_merge            = true -> false
          ~ allow_merge_commit          = false -> true
          ~ delete_branch_on_merge      = true -> false
            id                          = "dev"
            name                        = "dev"
            ~ topics                      = [
                - "github",
                - "infrastructure",
                - "terraform",
            ]
            - vulnerability_alerts        = true -> null
            # (26 unchanged attributes hidden)
        }
     
     Plan: 0 to add, 1 to change, 0 to destroy.
  4. I fix my Terraform code to aligh with the actual configuration

    resource "github_repository" "dev" {
      name = "dev"
     
      allow_auto_merge   = true
      allow_merge_commit = false
      allow_rebase_merge = true
     
      delete_branch_on_merge = true
     
      topics = [
        "github",
        "infrastructure",
        "terraform",
      ]
     
      vulnerability_alerts = true
    }
  5. Run terraform plan again and paste your PAT again. The output should be close to the example below.

    github_repository.dev: Refreshing state... [id=dev]
     
    No changes. Your infrastructure matches the configuration.
     
    Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
  6. Optionally: run terraform apply and check the output.

    Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
     
    Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
  7. Commit your changes and push them to GitHub!

Conclusion

Managing infrastructure with Terraform is not hard at all, and even you can use it for your personal projects.

I learned how to use Terraform with GitHub and how to structure Terraform code.

In the following articles, I will describe the usage of Terraform Cloud and how to configure CI for your Terraform code.

Happy Terraforming!

Links

The changes you can find in my PR: nikitbarskov/dev#217 (opens in a new tab).