Building a Pipeline to Deploy Terraform Updates
Tags: aws, terraform, devops
Published: 2023-09-10 14:43:41

This is the last part of the series for **Infrastructure as Code: Deploying a Demo App on EC2 with AWS Image Builder and Terraform**. On this part, I will focus more on GitHub Actions and how to utilize this CI/CD platform to deploy updates to your Infrastructure code.

## Start with a Pre-defined Workflow File

GitHub Actions already provides a sample template to start. Simply search for the sample template in Actions tab, and click on the **Configure** button.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eykp3zspvttd9ac04a0q.png)

This will create a terraform.yml file with all the required steps for a Terraform deployment.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/knzwhhaz2aw21dzpwcsu.png)

Let's walk through each one.
**1. The Trigger**
This part indicates different GitHub actions to trigger the workflow. The default actions triggers it with a _push_ and _pull request_ action. Since I want to trigger the workflow manually, I added a _workflow dispatch_ action.
```yml
name: 'Terraform'

on:
  push:
    branches: [ "main" ]
  pull_request:
  workflow_dispatch:
```
**2. Jobs**
The next part will be the actions that will run. Below is the initialization of the GitHub runner (ubuntu-latest image) that will be spun up as the workflow runs. 

```yml
jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: production
```
The other parts below have definitions in the sample file on what their purpose is:

```yml
steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v3

    # Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
      with:
        cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

    # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
    - name: Terraform Init
      run: terraform init

    # Checks that all Terraform configuration files adhere to a canonical format
    - name: Terraform Format
      run: terraform fmt -check

    # Generates an execution plan for Terraform
    - name: Terraform Plan
      run: terraform plan -input=false
```
One customization that needs to be done is to create the Terraform API token (TF_API_TOKEN) which will be saved in GitHub secrets as it is an authentication header that will be used. We can generate a token by following this Terraform documentation: https://developer.hashicorp.com/terraform/tutorials/cloud-get-started/cloud-login. Type `terraform login` and type yes on the prompt.

```sh
$ terraform login
Terraform will request an API token for app.terraform.io using your browser.

If login is successful, Terraform will store the token in plain text in
the following file for use by subsequent commands:
    C:\Users\kayea\AppData\Roaming\terraform.d\credentials.tfrc.json

Do you want to proceed?
  Only 'yes' will be accepted to confirm.

  Enter a value: yes


---------------------------------------------------------------------------------

Terraform must now open a web browser to the tokens page for app.terraform.io.

If a browser does not open this automatically, open the following URL to proceed:
    https://app.terraform.io/app/settings/tokens?source=terraform-login


---------------------------------------------------------------------------------

Generate a token using your browser, and copy-paste it into this prompt.

Terraform will store the token in plain text in the following file
for use by subsequent commands:
    C:\Users\kayea\AppData\Roaming\terraform.d\credentials.tfrc.json

Token for app.terraform.io:
  Enter a value:
```
On the browser, login to Terraform cloud, and you will be redirected to this page. You can type a name to identify this token, and set various expiration dates.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dsuw5xpv8yazqz4ab7vx.png)

You can now save this token under GitHub Secrets

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hxmvxv9oy64vndjw8di.png)

Go back to the GitHub actions template and commit the change:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dbit9br0iet06steupj.png)

Since the `push` action triggers the workflow, you would see that updating the code will trigger the action:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55qad13lfnlaoud8qz78.png)

**3. Create an AWS credential**
You'll notice that on the initial run, this would immediately fail on Terraform initialize. This is because we haven't configured any AWS credentials on the workflow. We can follow this [documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services), or follow along below:

Before this can be done, we need to create an IAM role. Go to the AWS management console and create a role for Web Identity, adding the fields.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06v96bvzbdiav1ikuv0x.png)

AWS also made it available to pre-generate the trust policy to be added to the role

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Principal": {
                "Federated": "arn:aws:iam::111111111111:oidc-provider/token.actions.githubusercontent.com"
            },
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": [
                        "sts.amazonaws.com"
                    ]
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": [
                        "repo:coderkaye/*"
                    ]
                }
            }
        }
    ]
}
```

Now add the GitHub action below and add the ARN of the role created.

```
- name: Configure AWS credentials from Test account
      uses: aws-actions/configure-aws-credentials@v3
      with:
        role-to-assume: arn:aws:iam::11111111111:role/aws-githubactions
        aws-region: us-east-1
```
You also need to update the permissions of id-token to write

```yml
permissions:
  id-token: write
  contents: read
```
On this run, it will not automatically apply the Terraform code, but show the plan. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4524qs5f5wpxmlmfavl.png)
**4. Final Updates**
You can now update your pipeline to add specific GitHub Actions triggers. Some best practices for Infrastructure pipeline are below:
1. Set a manual trigger of the workflow. We do not ever want to accidentally deploy infrastructure without proper review.
2. Add tests. One Terraform feature is to be able to test the linting of your terraform code. This is one good test to add on top of other tests.
3. Add approval checks. You can add notifications to your CI/CD pipeline to ensure that someone in authority is able to review the infrastructure being deployed before the infrastructure is actually updated.

---
If you have any questions or if any part is unclear, feel free to shoot a comment below!