Setup Multiple Environment Azure CI/CD with Terraform Cloud & GitHub Action

Introduction

Terraform is an open-source infrastructure as code (IaC) tool developed by HashiCorp. It allows you to define and provision infrastructure resources in a declarative manner using a simple and consistent syntax. With Terraform, you can describe your desired infrastructure state in configuration files, and then Terraform handles the provisioning and management of the resources needed to achieve that state.

Terraform Cloud is a SaaS (Software as a Service) platform provided by HashiCorp, the company behind Terraform. It is designed to enhance the collaboration, automation, and management capabilities of Terraform.

GitHub Actions is a powerful workflow automation and CI/CD (Continuous Integration/Continuous Deployment) platform provided by GitHub. It allows you to automate various tasks and processes within your software development lifecycle directly from your GitHub repository.

Azure Cloud, also known as Microsoft Azure, is a comprehensive cloud computing platform provided by Microsoft. It offers a wide range of cloud services and solutions to build, deploy, and manage various applications and services.

Prerequisites

  • Basic knowledge of terraform
  • Terraform Cloud account
  • GitHub account
  • Azure Account

In this post, We will show you how to create CI/CD GitHub Action’s workflow for multiple environment.

Step 1: Creating Workspace in Terraform Cloud

We need to design the environments and according to that we need to create a workspace in terraform account, in my case i am going to given three terraform workspace to differentiate environments.

  • dev
  • stage
  • prod

Go to your terraform cloud account and you should get a default organization over there and also you should get an option to create workspace like given screenshots.

Click in Create a workspace button

Now we need to choose the suitable workflow, We need to select the API-driven-workflow the 3rd workflow.

In this step we need to define the workspace name this will be our separates space based on environment, I am going to create dev workspace to manage dev environment resources of azure, If you are having more 1 project so also you can select the project name as well here.

Click on Create workspace button

Now you should get the workspace details and configuration.

Like this we need to create more 2 workspace for stage and prod.

Step 2: Generating Azure Credentials

We need to create a service principal in order to deploy the azure resources using terraform, for the same use the given steps on your azure account.

Click on cloud shell

To know subscriptions id execute the given command.

az account show

You should get your subscriptions id and other account details, After this you need to create the rbac contributor for that by following the command.

az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/<TYPE-YOUR-SUBSCRIPTION-ID-HERE>"

You should get your following creds to create secrets variable in terraform’s workspace.

{
  "appId": "xxxxxxxxx-yyyyyy-zzzzzzzz-ffffffffff",
  "displayName": "azure-cli-2020-09-25-05-00-59",
  "name": "http://azure-cli-2020-09-25-05-00-59",
  "password": "xxxxxxxxx-yyyyyy-zzzzzzzz-ffffffffff",
  "tenant": "xxxxxxxxx-yyyyyy-zzzzzzzz-ffffffffff"
}

Now we are having azure creds to configure in workspace sanative variables.

Step 3: Creating Workspace Creds Variable

Go to the terraform cloud account and select the dev workspace and click on variable tab in left side of the web page.

Click on Variables tab and create sensitive variable for azure creds like this.

Step 4: Generating User Token

We need to generate the TF_API_TOKEN from user setting, We need to go to the home page of terraform cloud.

Click on user profile to get profile options and click on User Settings button.

Click on Token button in the left menu side.

Click on Create an API token button as showing in the screenshots.

Define the description related to prepose and select the expiry date as per your need.

After clicking Generate token, You should get your token on the screen that will use in GitHub action workflow using secrets variable.

Step 5: Setting Up GitHub Repository & Workflow

We need to create a following resources for integrate terraform cloud and GitHub workflow

  • Create GitHub repository
  • Create secrets variable
  • Create backend.tf, variable.tf and main.tf file
  • Enable terraform workflow

Go to GitHub repository and click on setting button in order to create secrets variable by using following key value pair.

Click on Secrets and variables button in left menu and then action button.

Use the TF_API_TOKEN as a secrets variable and paste the token in secrets box.

after this you should get put out like this.

Creating backend.tf, variable.tf and main.tf file

backend.tf

terraform {
  cloud {
    organization = "REPLACE-WITH-ORG-NAME"

    workspaces {
      name = "REPLACE-WITH-WORKSPACE-NAME"
    }
  }
}

variables.tf

# Azure Subscription Id
variable "azure-subscription-id" {
  type        = string
  description = "Azure Subscription Id"
}
# Azure Client Id/appId
variable "azure-client-id" {
  type        = string
  description = "Azure Client Id/appId"
}
# Azure Client Id/appId
variable "azure-client-secret" {
  type        = string
  description = "Azure Client Id/appId"
}
# Azure Tenant Id
variable "azure-tenant-id" {
  type        = string
  description = "Azure Tenant Id"
}

main.tf

# Configure the Azure Provider

provider "azurerm" {
  features {}
  subscription_id = var.azure-subscription-id
  client_id       = var.azure-client-id
  client_secret   = var.azure-client-secret
  tenant_id       = var.azure-tenant-id
}

resource "azurerm_resource_group" "example" {
  name     = "${terraform.workspace}-rg"
  location = "East US"

  tags = {
    env = "${terraform.workspace}"
  }
}

Step 6: Enabling workflow

We need to create a GitHub action workflow for each environment based on branches like dev, stage and prod.

dev.yml

name: 'Terraform - Dev'

on:
  push:
    branches: [ "dev" ]
  pull_request:

permissions:
  contents: read

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: dev

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v3
      
    - name: Setting-up worksapce
      run: | 
        sed -i 's/REPLACEWORKSPACE/dev/g' main.tf
        cat main.tf      

    # 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

      # On push to "main", build or change infrastructure according to Terraform configuration files
      # Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
    - name: Terraform Apply
      #if: github.ref == 'refs/heads/"dev"' && github.event_name == 'push'
      run: terraform apply -auto-approve -input=false

stage.yml

# This workflow installs the latest version of Terraform CLI and configures the Terraform CLI configuration file
# with an API token for Terraform Cloud (app.terraform.io). On pull request events, this workflow will run
# `terraform init`, `terraform fmt`, and `terraform plan` (speculative plan via Terraform Cloud). On push events
# to the "main" branch, `terraform apply` will be executed.
#
# Documentation for `hashicorp/setup-terraform` is located here: https://github.com/hashicorp/setup-terraform
#
# To use this workflow, you will need to complete the following setup steps.
#
# 1. Create a `main.tf` file in the root of this repository with the `remote` backend and one or more resources defined.
#   Example `main.tf`:
#     # The configuration for the `remote` backend.
#     terraform {
#       backend "remote" {
#         # The name of your Terraform Cloud organization.
#         organization = "example-organization"
#
#         # The name of the Terraform Cloud workspace to store Terraform state files in.
#         workspaces {
#           name = "example-workspace"
#         }
#       }
#     }
#
#     # An example resource that does nothing.
#     resource "null_resource" "example" {
#       triggers = {
#         value = "A example resource that does nothing!"
#       }
#     }
#
#
# 2. Generate a Terraform Cloud user API token and store it as a GitHub secret (e.g. TF_API_TOKEN) on this repository.
#   Documentation:
#     - https://www.terraform.io/docs/cloud/users-teams-organizations/api-tokens.html
#     - https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets
#
# 3. Reference the GitHub secret in step using the `hashicorp/setup-terraform` GitHub Action.
#   Example:
#     - name: Setup Terraform
#       uses: hashicorp/setup-terraform@v1
#       with:
#         cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

name: 'Terraform - Stage'

on:
  push:
    branches: [ "stage" ]
  pull_request:

permissions:
  contents: read

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: stage

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v3
      
    - name: Setting-up worksapce
      run: | 
        ls
        sed -i 's/REPLACEWORKSPACE/stage/g' main.tf
        cat main.tf      

    # 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

      # On push to "main", build or change infrastructure according to Terraform configuration files
      # Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
    - name: Terraform Apply
      #if: github.ref == 'refs/heads/"stage"' && github.event_name == 'push'
      run: terraform apply -auto-approve -input=false # This workflow installs the latest version of Terraform CLI and configures the Terraform CLI configuration file

prod.yml

# This workflow installs the latest version of Terraform CLI and configures the Terraform CLI configuration file
# with an API token for Terraform Cloud (app.terraform.io). On pull request events, this workflow will run
# `terraform init`, `terraform fmt`, and `terraform plan` (speculative plan via Terraform Cloud). On push events
# to the "main" branch, `terraform apply` will be executed.
#
# Documentation for `hashicorp/setup-terraform` is located here: https://github.com/hashicorp/setup-terraform
#
# To use this workflow, you will need to complete the following setup steps.
#
# 1. Create a `main.tf` file in the root of this repository with the `remote` backend and one or more resources defined.
#   Example `main.tf`:
#     # The configuration for the `remote` backend.
#     terraform {
#       backend "remote" {
#         # The name of your Terraform Cloud organization.
#         organization = "example-organization"
#
#         # The name of the Terraform Cloud workspace to store Terraform state files in.
#         workspaces {
#           name = "example-workspace"
#         }
#       }
#     }
#
#     # An example resource that does nothing.
#     resource "null_resource" "example" {
#       triggers = {
#         value = "A example resource that does nothing!"
#       }
#     }
#
#
# 2. Generate a Terraform Cloud user API token and store it as a GitHub secret (e.g. TF_API_TOKEN) on this repository.
#   Documentation:
#     - https://www.terraform.io/docs/cloud/users-teams-organizations/api-tokens.html
#     - https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets
#
# 3. Reference the GitHub secret in step using the `hashicorp/setup-terraform` GitHub Action.
#   Example:
#     - name: Setup Terraform
#       uses: hashicorp/setup-terraform@v1
#       with:
#         cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

name: 'Terraform - Prod'

on:
  push:
    branches: [ "main" ]
  pull_request:

permissions:
  contents: read

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: prod

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v3
      
    - name: Setting-up worksapce
      run: | 
        ls
        sed -i 's/REPLACEWORKSPACE/prod/g' main.tf
        cat main.tf

    # 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

      # On push to "main", build or change infrastructure according to Terraform configuration files
      # Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
    - name: Terraform Apply
      #if: github.ref == 'refs/heads/"main"' && github.event_name == 'push'
      run: terraform apply -auto-approve -input=false

Step 7: Testing Workflow

We are good to push and create pull request in order test the separate environment based of azure resources.

While doing push and pull request activity we should get the workflow execution like this for each envs.

Validate the resource group in azure portal.

Conclusion

We have successfully configure multiple envs with terraform workspace using GitHub Action , If you still have questions, please post them in the comments section below.

Author

Setup Multiple Environment Azure CI/CD with Terraform Cloud & GitHub Action
Scroll to top