In this guide we will learn how to use GitHub actions to manage your infrastructure through code in the cloud using Terraform and Civo. This guide is a follow-up to Using the Civo Terraform Provider. I recommend you complete that guide first, as this one will use the setup you create there.

If you do not yet have a Civo account, you can sign up here.

Creating the repository in GitHub

First of all, you will need to create a repository in your or your organization's GitHub accout. You can call it whatever you want, in this case I will use the name auto-terraform. The repository itself can be public or private, depending on you.

The code

Now we need to create a secret in the GitHub repository. You can do so in the settings --> secrets section of your repo. In my case I added a secret with the name civo_token and the value of your API key, which you can find at https://dashboard.civo.com/security.

After that, you are almost ready to upload your code from the previous tutorial to the repo. We need make some changes to able to work with GitHub actions. We need to create a file called variable.tf in our project's root directory with the contents of the following:

variable "civo_token" { type = string }

If you already have the file, you can simply add the above to it.

After this, we need to modify the provider file, with this:

provider "civo" {
  token = var.civo_token
}

Now you can push the code to your repository, or if you already have done so, simply commit the new/modified files and push.

Preparation of our data store

We have a working system for provisioning infrastructure as code, but there would be a problem if if something were to happen with the tfstate file. In that case I recommend using an s3 backup for Terraform. In this way all the changes in the insfractucture will be saved in your s3 bucket. In my case I use MinIO to create my own s3 storage. For more information, you can read this article on installing MinIO on Civo or this on automating backups. The configuration would be in this way:

terraform {
  backend "s3" {
    endpoint = "http://<THE_IP_OF_YOUR_MINIO>:9000"
    bucket = "terraform"
    key = "terraform.tfstate"
    region = "lon1"
    force_path_style = true
    skip_credentials_validation = true
  }
}

The user and password will be passed as a secret. The syntax is in the format -backend-config="access_key=${{secrets.MINIO_USER}}" -backend-config="secret_key=${{secrets.MINIO_PASSWD}}" in the init command. So we have to add two more secrets, MINIO_USER andMINIO_PASSWD, to our repository. Use the values for the username and password you have from setting up your MinIO instance from the guides above.

The action

After all this we need to create a new GitHub Action from scratch and put this inside:

# This is a basic workflow to help you get started with Actions

name: terraform-deploy

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
    - uses: actions/checkout@v2

    - name: HashiCorp - Setup Terraform
      uses: hashicorp/setup-terraform@v1.0.1

    - name: Install civo provider
      run: wget -c https://github.com/civo/terraform-provider-civo/releases/download/v0.9.4/terraform-provider-civo_0.9.4_linux_amd64.tar.gz -O - | sudo tar -xz && mv terraform-provider-civo terraform-provider-civo_v0.9.4

    - name: Create structure
      run: mkdir -p .terraform/plugins/linux_amd64 && mv terraform-provider-civo_v0.9.4 .terraform/plugins/linux_amd64/

    # Runs a single command using the runners shell
    - name: Run a one-line script
      id: init
      run: terraform init -backend-config="access_key=${{ secrets.MINIO_USER }}" -backend-config="secret_key=${{ secrets.MINIO_PASSWD }}"

    - id: validate
      run: terraform validate -no-color

    - id: plan
      run: terraform plan -no-color -var="civo_token=${{ secrets.CIVO_TOKEN }}"
      continue-on-error: true

    - uses: actions/github-script@0.9.0
      if: github.event_name == 'pull_request'
      env:
        PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
      with:
        github-token: ${{ secrets.GHT }}
        script: |
          const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
          #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
          #### Terraform Validation 🤖${{ steps.validate.outputs.stdout }}
          #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

          <details><summary>Show Plan</summary>

          \`\`\`${process.env.PLAN}\`\`\`

          </details>

          *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;

          github.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: output
          })

    - name: Terraform Apply
      if: github.ref == 'refs/heads/master' && github.event_name == 'push'
      run: terraform apply -auto-approve -var="civo_token=${{ secrets.CIVO_TOKEN }}"

This action runs for each Pull Request. The action checks the changes and makes a comment in the pull request showing the Terraform plan. If everything looks ok, you can accept the merge and the action runs again. This time you will see the changes and apply.

Once you have done the above, to test it we would only have to create a new file called instances.tf with the following inside:

resource "civo_instance" "github-action" {
    hostname = "github-action.com"
    tags = ["test", "github-action"]
    notes = "this is a note for the server"
    initial_user = "root"
    size = "g2.xsmall"
}

We save, we commit to master or to a separate branch if you already have something running. If you are committing to a separate branch, create a pull request. You should see the information about changes to your infrastructure pop up as a comment.

Now we only have to put all our infrastructure code in git and the actions take care of the rest. This is a way of having everything centralized and a way that a DevOps Team can know all the changes and why they were made.

If you have any comments or suggestions on how to improve this article, let us know either in the Civo community Slack or on Twitter - you can find us at @civocloud.