Workshops
Book a 90-minute product workshop led by HashiCorp engineers and product experts during HashiConf Digital Reserve your spot

Automate and Extend Terraform

Terraform and CircleCI

CircleCI is a continuous integration tool for automation of software builds, tests, and deployments. The continuous integration workflow enables development teams to automate, self-test, quickly build, clone, and deploy software. Terraform allows for repeatable infrastructure deployment and by adding Terraform into a CircleCI workflow, you can deploy your infrastructure alongside software in the same pipeline.

In this guide you will use CircleCI to deploy Terraform-managed-infrastructure that hosts a web application. This S3 backed webapp will be controlled by the CircleCI workflow and Terraform Cloud integration. You should review the CircleCI getting started guide, sign up and try CircleCI, and have a working knowledge of Terraform OSS.

This guide assumes you have the following:

You can fork the code and configuration for this project at GitHub.

»Analyze a CircleCI configuration

First, review some of the CircleCI keywords.

Steps are actions that CircleCI takes in the workflow to perform your job. Steps are usually a collection of executable commands. For example, the checkout step checks out the source code for a job over SSH. Then, the run step executes the make test command using a non-login shell by default.

Jobs are collections of steps. Each job must declare an executor, an operating system which will launch and perform the actions you define, and a series of steps. This can be either docker, machine, windows or macos. You will use the docker executor in this guide.

Workspaces are a storage mechanism within CircleCI. The workspace stores data needed for downstream jobs, which can be useful for persisting state in Terraform.

Workflows define a list of jobs and their run order. It is possible to run jobs in parallel, sequentially, on a schedule, or with a manual gate using an approval job.

Review the first section of the config.yml file you will use to deploy your webapp. This starting configuration defines references to default base images, working directories, and default configurations for your containers. Our default_config reference downloads the latest Terraform Docker image from the HashiCorp Docker Hub and sets the same working directory for all of the containers you spin up.

version: 2

references:

base_image: &base_image hashicorp/terraform:light

working_directory: &working_directory ~/project

default_config: &default_config
  docker:
    - image: *base_image
  working_directory: *working_directory
  environment:
    BASH_ENV: /root/.bashrc
    TERRAFORM_ENV: ~/project/

repo_cache_key: &repo_cache_key v1-repo-{{ .Branch }}-{{ .Revision }}

Because you are running plan, apply, and destroy jobs that take place in different containers, restore_repo and save_repo allow you to restore the repository from cache into the containers.

# Step to restore repository from cache
restore_repo: &restore_repo
  restore_cache:
    key: *repo_cache_key

save_repo: &save_repo
  save_cache:
    key: *repo_cache_key
    paths:
      - *working_directory

This portion of the config sets the Terraform environment for the running containers. The TF_API_TOKEN is a Terraform Cloud environment variable CircleCI needs to operate on your behalf in the workflow.

set_terraform_environment: &set_terraform_environment
  run:
    name: set terraform environment
    command: |
      cd && touch $BASH_ENV
      cd ~/project/
terraform_init: &terraform_init
  run:
    name: terraform init
    command: |
      source $BASH_ENV
      cd ~/project/
      terraform init -backend-config="token=${TF_API_TOKEN}"

Once the image is successfully pulled, CircleCI runs the actions defined by steps in the jobs section.

The five jobs associated with this configuration are build, plan, apply, deployment, and destroy. Look at the plan job as an example. In plan, the steps to run terraform plan restore the repo to the new directory, set the environment to which the Terraform command will run, initialize the Terraform directory and then run terraform plan before continuing to the apply phase.

jobs:

  build:
    <<: *default_config
    steps:
      - checkout
      - *set_terraform_environment
      - run:
          name: terraform fmt
          command: |
            source $BASH_ENV
            cd ~/project/
            terraform init -backend-config="token=${TF_API_TOKEN}"
            terraform fmt
      - *save_repo

  plan:
    <<: *default_config
    steps:
      - *restore_repo
      - *set_terraform_environment
      - *terraform_init
      - run:
          name: terraform plan
          command: |
            source $BASH_ENV
            cd ~/project/
            terraform plan
  apply:
    <<: *apply_job
  apply-with-approval:
    <<: *apply_job

  deployment:
    docker: [{ image: "circleci/python:latest" }]
    working_directory: "~/project"

    steps:
      - *restore_repo
      - *set_terraform_environment
      - run:
          name: "Push to S3"
          command: |
            pip install --user awscli
            export PATH="~/.local/bin:$PATH"
            aws s3 sync --acl public-read "/home/circleci/project/assets" s3://${APP_BUCKET}

  destroy:
    <<: *default_config
    steps:
      - *restore_repo
      - *set_terraform_environment
      - *terraform_init
      - run:
          name: "Destruction of env"
          command: |
            source $BASH_ENV
            cd ~/project/
            terraform destroy --auto-approve

Finally, the last block in the configuration is the workflows. Workflow defines order, precedence, and requirements to perform the jobs within the pipeline. This workflow runs the jobs detailed throughout the config and details the order in which those jobs run as well as the dependencies of each job. The build_plan_approve_apply workflow runs through all of the steps in order and creates the application.

workflows:
  version: 2
  build_plan_approve_apply:
    jobs:
      - build
      - plan:
          requires:
            - build
      - apply:
          requires:
            - plan
      - deployment:
          requires:
            - apply
      - hold:
          type: approval
          requires:
            - deployment
      - destroy:
          requires:
            - hold

»Setup the Terraform Cloud UI

This guide uses Terraform Cloud as the free remote state storage. For more information on Terraform Cloud, check out the Getting Started Guide

In your Terraform Cloud account, navigate to the Teams section in your organization under Settings and choose "Create an authentication token" under Team API Token.

Team settings

Save this token in a safe place. Later you will add this token in CircleCI web UI to your project's Build Settings as an environment variable.

Generate a Team token

In your web browser, navigate to the Terraform Cloud web UI. Create a new workspace, choose "No VCS connection", and name the workspace learn-terraform-circleci. Navigate to "Variables" in this workspace and create the following Terraform Variables:

  1. region This example is in us-east-1 but you may choose a region closer to you.
  2. user This must be a unique new user for CircleCI to run operations with given permissions. Set this as circle-user.
  3. label The label in this example is a placeholder for a unique identifier. Set this as a Route53 domain in your AWS account, your last name and a random number, or some other random identifier.
  4. app The app refers to the app name that will join the label to form the URL as well as the S3 bucket. Set this as terramino.

In Environment Variables, you will need to generate an AWS secret access key. To create access keys, login to the AWS console, navigate to your username on the upper right and choose "My Security Credentials." On the AWS IAM Credentials tab, in the Access keys for CLI, SDK, and API access section and choose "Create access key." Then choose Download .csv file to save the access key ID and secret access key to a .csv file on your computer. Store the file in a secure location. You will not have access to the secret access key again after this dialog box closes. After you have downloaded the .csv file, choose Close. When you create an access key, the key pair is active by default, and you can use the pair right away.

  1. AWS_ACCESS_KEY_ID Set as sensitive.
  2. AWS_SECRET_ACCESS_KEY Set as sensitive.
  3. CONFIRM_DESTROY This is necessary for CircleCI to run destroy operations when the operator decides to destroy the application. Set this to 1

Terraform Variables

The pre-work for setting up CircleCI is complete and now you can kick off a run for your infrastructure. Start by updating your code in the GitHub repository.

»Setup the CircleCI UI

Fork the code repo from here and review the main.tf and .circle/config.yml files. The main.tf configuration is what CircleCI will run on your behalf. In order to pass this configuration to the CircleCI jobs, you have to setup your CirleCI project.

In the CircleCI web UI, add a new project.

Add Projects

Search for the repo you forked and choose Set Up Project. Choose "Hello World" as the language and ignore the sample .yml file generated.

Add Projects

Choose "Start Building" and you should be presented with a popup confirming you have created config.yml file.

Add Projects

Add Projects

It will fail because the Environment Variables are not set and are necessary to run the correct jobs.

Add Projects

In the right hand corner of this page, find the gear icon to be taken to the project settings and choose environment variables from Build Settings.

The four environment variables for this project are APP_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and TF_API_TOKEN. These are variables that CircleCI uses to inject data into the .config.yml file.

Env Variables

  1. APP_BUCKET refers to the website URL corresponding to the label & app name that you set in your Terraform Variables. This example uses terramino.hashicorp.fun.
  2. AWS_ACCESS_KEY_ID is the generated keys for the AWS user running this job. To generate an access key and secret access key file, log in to your AWS account and create in IAM.
  3. AWS_SECRET_ACCESS_KEY is bundled with the access key above.
  4. TF_API_TOKEN is the Team token created in Terraform Cloud. This can be found under Team settings.

Add and save these variables in your CircleCI Build Settings.

»Run the CircleCI workflow in GitHub

In your repository, make an edit to the main.tf file to add the Terraform remote backend.

terraform {
  backend "remote" {
    hostname = "app.terraform.io"
    organization = "<YOUR-ORG-NAME>"

    workspaces {
      name = "learn-terraform-circleci"
    }
  }
}

Replace the organization variable with the name of your Terraform Cloud organization. Then, push this change in GitHub. This will mimic the workflow of a GitHub deployment.

The CircleCI web UI should indicate that your build has started. The steps for this deployment will go through formatting your main.tf file, planning the Terraform deployment, applying upon successful plan, deploying the web app to an S3 bucket static website, then holding until you want to destroy your infrastructure.

CircleCI Workflow

Review that job by navigating to the workflows for build_plan_approve_apply. In each job in the workflow, you can click on each step to expand the output.

CircleCI Workflow

Once the deployment job is complete, your workflow will be on hold. The hold in your configuration is the step before destruction. CircleCI masks secrets in outputs, so CircleCI obscures the endpoint address in the web UI. In the Terraform Cloud web UI, navigate to your latest run and find the "Endpoint" output of your Terraform application in your workspace. Navigate to this website in your browser and enjoy your successful CircleCI deployment.

»Update and Destroy

Any changes to your GitHub repo will trigger another run of this workflow. As with any Terraform deployment, some resources will be destroyed or updated in place depending on the configuration and the cloud provider you are using in the future.

»Hold

The hold step in the workflow will prevent CircleCI from continuing to the next job in your workflow. The hold step is placed before destroy, which runs terraform destroy in our workspace, and allows you to decide when to move to the final step in the configuration. Click on the hold step and then choose "yes" to move on to the destroy job in this workflow.

Navigate to the destroy job in the CircleCI web UI to observe the output of terraform destroy in the "Destruction of env" drop down.

Destroy

»Next Steps

In this track you deployed a hosted webpage in S3 by integrating Terraform with your CircleCI workflow. If you would like to learn more about CircleCI or automated Terraform workflow best practices, consider these resources: