Using Terraform to push files to Git Repo for GitOps

Daniel German Rivera - Jul 11 - - Dev Community

Note: This post was updated because I incorrectly stated that the GitHub Terraform provider didn't delete files in the remote repository when they were removed from the Terraform code. The issue arose because I was using GitHub Actions to run the plan and apply steps, but the GitHub token was not propagating correctly during the plan step. This caused Terraform to fail to delete the files in the remote repository and resulted in multiple commits with each execution. I apologize for any confusion this may have caused and have edited the article to provide accurate information.

I have been working on a personal project named Smart-cash to improve some skills and learn new ones.

In this article, I will share my thoughts about using Terraform in the GitOps process, specifically to create the manifest and push it to the Git repo.

The basics

GitOps relies on a Git repository as the single source of truth. New commits imply infrastructure and application updates.

simple image

Imagine a Git repository where you push all the manifests of the Kubernetes resources you want to create in your cluster. These are pulled by a tool or script that runs a "kubectl apply", creates the resources, and checks the Git repo for new changes to apply. This, at a high level, is GitOps.

Setting up the scenario

For this case, the K8 cluster will run in AWS EKS, and Terraform is being used as an IaC tool.

A basic cluster can be created using Terraform. You can check an example here.

FluxCD installation can be done using the official documentation or you can check this.

I will not explain some Flux concepts like sources and Kustomizations; you can check that in the links shared previously.

Creating the YAML files

Let's say that we want to create a namespace for the development environment, we can use the following YAML:

apiVersion: v1
kind: Namespace
metadata:
  name: develop
  labels:
    test: true
Enter fullscreen mode Exit fullscreen mode

We can push this file to GitHub and wait for FluxCD to do the magic.

Now let's say that we want to create a service account and associate it with an AWS IAM role, the YAML can be:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-test-develop
  annotations:  
    eks.amazonaws.com/role-arn: arn:aws:iam::12345678910:role/TEST
Enter fullscreen mode Exit fullscreen mode

This looks easy but what happens if we have multiple environments or if We don't yet know the ARN of the role because this is part of our IaC?

help-me

Here is where Terraform gives us a hand.

You can create something like a template for the manifest and some variables that you can specify with Terraform. The two manifests would look like:

apiVersion: v1
kind: Namespace
metadata:
  name: ${ENVIRONMENT}
  labels:
    test: true
Enter fullscreen mode Exit fullscreen mode
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-test-${ENVIRONMENT}
  annotations:  
    eks.amazonaws.com/role-arn: ${ROLE_ARN}
Enter fullscreen mode Exit fullscreen mode

Notice the ${ENVIRONMENT} and ${ROLE_ARN} variables added.

We can use the Terraform GitHub provider to push the file to the repository. Let's check the following code to push the service account:

resource "github_repository_file" "sa-test" {
  repository          = data.github_repository.flux-gitops.name
  branch              = main
  file                = "./manifest/sa-manifest.yaml"
  content = templatefile(
    "sa-manifest.yaml",
    {
      ENVIRONMENT = var.environment
      ROLE_ARN = aws_iam_role.arn
    }
  )
  commit_message      = "Terraform"
  commit_author       = "terraform"
  commit_email        = "example@example"
  overwrite_on_create = true
}
Enter fullscreen mode Exit fullscreen mode

The arguments repository and branch allow us to specify the remote repo and the branch where we want to push the file. The file argument is the location in the remote repository where we want to put the file.

The content argument is where we pass the values to the variables created in the template, in this case ENVIRONMENT and ROLE_ARN, the values are a terraform variable and the reference to a Terraform resource that creates the role.

overwrite_on_create argument is needed because if you run Terraform again, it will show an error because the file already exists in the repo.

Pros

  1. Pushing the manifests using Terraform avoids the manual tasks of committing and pushing them, allowing us to automate more steps.
  2. We can integrate this process into our pipeline, so a full environment can be ready when the pipeline finishes.
  3. Terraform count can be used when there are many manifests to push, avoiding repetitive code.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .