DEV Community

Cover image for Break Terraform state lease using Github Actions
Arindam Mitra
Arindam Mitra

Posted on • Updated on

Break Terraform state lease using Github Actions

Greetings to my fellow Technology Advocates and Specialists.

This Blog post is a follow-up to my previous post - Break Terraform State Lease Using Azure DevOps

In this Session, I will demonstrate how to Break Terraform State Lease Using Github Actions.

AUTOMATION OBJECTIVE:-
Validate If Resource Group Exists.
Validate If Storage Account Exists.
Validate If Storage Account Container Exists.
Validate If Terraform State File Exists in the Specified Storage Account Container.
If any One of the above validation DOES NOT PASS, Pipeline will Fail immediately.
If All of the above validation is SUCCESSFUL, Pipeline will then check the Terraform Blob State.
If Terraform Blob State is == AVAILABLE, Pipeline fails with Exit Code 1.
If Terraform Blob State is == LEASED, Pipeline executes successfully by breaking the Terraform State Lease.
If Terraform Blob State is == BROKEN, Pipeline Still executes successfully without altering the present state.
IMPORTANT TO NOTE:-
There is No way to find the Blob Lease State before executing the az storage blob lease break command.
Refer the Link for more Information: az cli blob lease break
REQUIREMENTS:-
  1. Azure Subscription.
  2. Service Principal with Required RBAC ( Contributor) applied on Subscription or Resource Group(s).
  3. GitHub Repository with Mandatory Repository Secrets and Optionally Environments Configured.
  4. Microsoft DevLabs Terraform Extension Installed in Local System (VS Code Extension).
GITHUB REPOSITORY CONFIGURATION:-
Repository Secrets
Image description
Refer the MS Documentation Authenticate from Azure to Github for more details.
Environments
Image description
Image description
REPOSITORY SECRETS FORMAT:-
Below is how you configure Repository Secret named AZURE_CREDENTIALS
{
"clientId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"clientSecret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"subscriptionId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"tenantId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
Enter fullscreen mode Exit fullscreen mode



CODE REPOSITORY:-

GitHub logo arindam0310018 / 08-Aug-2022-GithubActions__Break-Terraform-State-Lease

BREAK TERRAFORM STATE LEASE USING GITHUB ACTIONS

BREAK TERRAFORM STATE LEASE USING GITHUB ACTIONS

Greetings to my fellow Technology Advocates and Specialists.

This Blog post is a follow-up to my previous post - Break Terraform State Lease Using Azure DevOps

In this Session, I will demonstrate how to Break Terraform State Lease Using Github Actions.

AUTOMATION OBJECTIVE:-
Validate If Resource Group Exists.
Validate If Storage Account Exists.
Validate If Storage Account Container Exists.
Validate If Terraform State File Exists in the Specified Storage Account Container.
If any One of the above validation DOES NOT PASS, Pipeline will Fail immediately.
If All of the above validation is SUCCESSFUL, Pipeline will then check the Terraform Blob State.
If Terraform Blob State is == AVAILABLE, Pipeline fails with Exit Code 1.
If Terraform Blob State is == LEASED, Pipeline executes successfully by breaking the Terraform State Lease.
If Terraform Blob State is == BROKEN, Pipeline Still executes successfully
HOW DOES MY CODE PLACEHOLDER LOOKS LIKE:-
Image description
PIPELINE CODE SNIPPET:-
GITHUB ACTIONS YAML WORKFLOW (gh-actions-tf-break-lease-v1.0.yml):-
name: 'Break Terraform Lease'

on: [workflow_dispatch]

env:
  SUBSCRIPTIONID: '210e66cb-55cf-424e-8daa-6cad804ab604' 
  RGNAME: 'tfpipeline-rg'
  STORAGEACCOUNTNAME: 'tfpipelinesa'
  STORAGEACCOUNTCONTAINERNAME: 'terraform'
  TFSTATEFILENAME: 'TF-LEASE/BreakLease.tfstate'


jobs:

  build-and-deploy:
    runs-on: windows-latest
    environment: dev
    steps:

    - name: 'Azure CLI Login'
      uses: azure/login@v1
      with:
        creds: '${{ secrets.AZURE_CREDENTIALS }}'

    - name: 'Break TF State'
      run: |
          az --version
          az account set --subscription ${{ env.SUBSCRIPTIONID }}
          az account show  
          $i = az group exists -n ${{ env.RGNAME }}
            if ($i -eq "true") {
              echo "#####################################################"
              echo "Resource Group ${{ env.RGNAME }} exists!!!"
              echo "#####################################################"
              $j = az storage account check-name --name ${{ env.STORAGEACCOUNTNAME }} --query "reason" --out tsv
                if ($j -eq "AlreadyExists") {
                  echo "#####################################################"
                  echo "Storage Account ${{ env.STORAGEACCOUNTNAME }} exists!!!"
                  echo "#####################################################"  
                  $k = az storage container exists --account-name ${{ env.STORAGEACCOUNTNAME }} --account-key $(az storage account keys list -g ${{ env.RGNAME }} -n ${{ env.STORAGEACCOUNTNAME }} --query [0].value -o tsv) --name ${{ env.STORAGEACCOUNTCONTAINERNAME }} --query "exists" --out tsv 
                    if ($k -eq "true") {
                      echo "#####################################################"
                      echo "Storage Account Container ${{ env.STORAGEACCOUNTCONTAINERNAME }} exists!!!"
                      echo "#####################################################"  
                      $l = az storage blob exists --account-name ${{ env.STORAGEACCOUNTNAME }} --account-key $(az storage account keys list -g ${{ env.RGNAME }} -n ${{ env.STORAGEACCOUNTNAME }} --query [0].value -o tsv) --container-name ${{ env.STORAGEACCOUNTCONTAINERNAME }} --name ${{ env.TFSTATEFILENAME }} --query "exists" --out tsv  
                        if ($l -eq "true") {
                          echo "#####################################################"
                          echo "Terraform State Blob ${{ env.TFSTATEFILENAME }} exists!!!"
                          echo "#####################################################"
                          az storage blob lease break --account-name ${{ env.STORAGEACCOUNTNAME }} --account-key $(az storage account keys list -g ${{ env.RGNAME }} -n ${{ env.STORAGEACCOUNTNAME }} --query [0].value -o tsv) --container-name ${{ env.STORAGEACCOUNTCONTAINERNAME }} --blob-name ${{ env.TFSTATEFILENAME }}                           
                          }
                        else {
                          echo "#####################################################"
                          echo "Terraform State Blob ${{ env.TFSTATEFILENAME }} DOES NOT EXISTS in ${{ env.STORAGEACCOUNTCONTAINERNAME }}"
                          echo "#####################################################"
                          exit 1
                          }
                    }
                    else {
                      echo "#####################################################"
                      echo "Storage Account Container ${{ env.STORAGEACCOUNTCONTAINERNAME }} DOES NOT EXISTS in STORAGE ACCOUNT ${{ env.STORAGEACCOUNTNAME }}!!!"
                      echo "#####################################################"
                      exit 1
                    }
                }
                else {
                  echo "#####################################################"
                  echo "Storage Account ${{ env.STORAGEACCOUNTNAME }} DOES NOT EXISTS!!!"
                  echo "#####################################################"
                  exit 1
                }              
            }
            else {
              echo "#####################################################"
              echo "Resource Group ${{ env.RGNAME }} DOES NOT EXISTS!!!"
              echo "#####################################################"
              exit 1
            }

Enter fullscreen mode Exit fullscreen mode

Now, let me explain each part of GitHub Actions YAML Workflow for better understanding.

PART 1:-
BELOW FOLLOWS WORKFLOW TRIGGER CODE SNIPPET:-
on: [workflow_dispatch]

Enter fullscreen mode Exit fullscreen mode

This indicates that GitHub Actions Workflow needs to be triggered manually.

PART 2:-
BELOW FOLLOWS WORKFLOW VARIABLES CODE SNIPPET:-
env:
  SUBSCRIPTIONID: '210e66cb-55cf-424e-8daa-6cad804ab604' 
  RGNAME: 'tfpipeline-rg'
  STORAGEACCOUNTNAME: 'tfpipelinesa'
  STORAGEACCOUNTCONTAINERNAME: 'terraform'
  TFSTATEFILENAME: 'TF-LEASE/BreakLease.tfstate'

Enter fullscreen mode Exit fullscreen mode
NOTE:-
Please feel free to change the values of the variables.
The entire workflow is build using variables. No Values are Hardcoded.
In GitHub Actions, the variables are designed to work in such a way that the Declared Variables are Mapped to Environmental Variables.
PART 3:-
BELOW FOLLOWS RUNNER AND ENVIRONMENT CODE SNIPPET:-
runs-on: windows-latest
environment: dev
Enter fullscreen mode Exit fullscreen mode
NOTE:-
RUNNER in GitHub Actions = BUILD AGENT in Azure DevOps.
ENVIRONMENT = Workflow Environment where Approval Gate is Configured.
PART 4:-
BELOW FOLLOWS AZURE CLI LOGIN CODE SNIPPET USING GITHUB ACTIONS FOR AZURE CLI:-
- name: 'Azure CLI Login'
      uses: azure/login@v1
      with:
        creds: '${{ secrets.AZURE_CREDENTIALS }}'
Enter fullscreen mode Exit fullscreen mode
NOTE:-
Refer GitHub Actions For Azure CLI for more details.
PART 5:-
BELOW FOLLOWS BREAK TERRAFORM STATE LEASE CODE SNIPPET :-
- name: 'Break TF State'
      run: |
          az --version
          az account set --subscription ${{ env.SUBSCRIPTIONID }}
          az account show  
          $i = az group exists -n ${{ env.RGNAME }}
            if ($i -eq "true") {
              echo "#####################################################"
              echo "Resource Group ${{ env.RGNAME }} exists!!!"
              echo "#####################################################"
              $j = az storage account check-name --name ${{ env.STORAGEACCOUNTNAME }} --query "reason" --out tsv
                if ($j -eq "AlreadyExists") {
                  echo "#####################################################"
                  echo "Storage Account ${{ env.STORAGEACCOUNTNAME }} exists!!!"
                  echo "#####################################################"  
                  $k = az storage container exists --account-name ${{ env.STORAGEACCOUNTNAME }} --account-key $(az storage account keys list -g ${{ env.RGNAME }} -n ${{ env.STORAGEACCOUNTNAME }} --query [0].value -o tsv) --name ${{ env.STORAGEACCOUNTCONTAINERNAME }} --query "exists" --out tsv 
                    if ($k -eq "true") {
                      echo "#####################################################"
                      echo "Storage Account Container ${{ env.STORAGEACCOUNTCONTAINERNAME }} exists!!!"
                      echo "#####################################################"  
                      $l = az storage blob exists --account-name ${{ env.STORAGEACCOUNTNAME }} --account-key $(az storage account keys list -g ${{ env.RGNAME }} -n ${{ env.STORAGEACCOUNTNAME }} --query [0].value -o tsv) --container-name ${{ env.STORAGEACCOUNTCONTAINERNAME }} --name ${{ env.TFSTATEFILENAME }} --query "exists" --out tsv  
                        if ($l -eq "true") {
                          echo "#####################################################"
                          echo "Terraform State Blob ${{ env.TFSTATEFILENAME }} exists!!!"
                          echo "#####################################################"
                          az storage blob lease break --account-name ${{ env.STORAGEACCOUNTNAME }} --account-key $(az storage account keys list -g ${{ env.RGNAME }} -n ${{ env.STORAGEACCOUNTNAME }} --query [0].value -o tsv) --container-name ${{ env.STORAGEACCOUNTCONTAINERNAME }} --blob-name ${{ env.TFSTATEFILENAME }}                           
                          }
                        else {
                          echo "#####################################################"
                          echo "Terraform State Blob ${{ env.TFSTATEFILENAME }} DOES NOT EXISTS in ${{ env.STORAGEACCOUNTCONTAINERNAME }}"
                          echo "#####################################################"
                          exit 1
                          }
                    }
                    else {
                      echo "#####################################################"
                      echo "Storage Account Container ${{ env.STORAGEACCOUNTCONTAINERNAME }} DOES NOT EXISTS in STORAGE ACCOUNT ${{ env.STORAGEACCOUNTNAME }}!!!"
                      echo "#####################################################"
                      exit 1
                    }
                }
                else {
                  echo "#####################################################"
                  echo "Storage Account ${{ env.STORAGEACCOUNTNAME }} DOES NOT EXISTS!!!"
                  echo "#####################################################"
                  exit 1
                }              
            }
            else {
              echo "#####################################################"
              echo "Resource Group ${{ env.RGNAME }} DOES NOT EXISTS!!!"
              echo "#####################################################"
              exit 1
            }

Enter fullscreen mode Exit fullscreen mode
NOTE:-
Az CLI commands are used with RUN action, without the need of GitHub Actions for Azure CLI (azure/login@v1)
OBJECTIVE OF TERRAFORM CODE SNIPPET:-
Create a Resource Group and User Assigned System Managed Identity.
The Purpose of the Terraform Code Snippet is to Reproduce the Issue, by Locking Terraform State File .
TERRAFORM (main.tf):-
terraform {
  required_version = ">= 1.2.3"

   backend "azurerm" {
    resource_group_name  = "tfpipeline-rg"
    storage_account_name = "tfpipelinesa"
    container_name       = "terraform"
    key                  = "TF-LEASE/BreakLease.tfstate"
  }
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.2"
    }   
  }
}
provider "azurerm" {
  features {}
  skip_provider_registration = true
}

Enter fullscreen mode Exit fullscreen mode
TERRAFORM (usrmid.tf):-
## Azure Resource Group:-
resource "azurerm_resource_group" "rg" {
 name     = var.rg-name
 location = var.rg-location
}

## Azure User Assigned Managed Identities:-
resource "azurerm_user_assigned_identity" "az-usr-mid" {

 name                = var.usr-mid-name
 resource_group_name = azurerm_resource_group.rg.name
 location            = azurerm_resource_group.rg.location

 depends_on          = [azurerm_resource_group.rg]
 }

Enter fullscreen mode Exit fullscreen mode
TERRAFORM (variables.tf):-
variable "rg-name" {
  type        = string
  description = "Name of the Resource Group"
}

variable "rg-location" {
  type        = string
  description = "Resource Group Location"
}

variable "usr-mid-name" {
  type        = string
  description = "Name of the User Assigned Managed Identity"
}

Enter fullscreen mode Exit fullscreen mode
TERRAFORM (usrmid.tfvars):-
rg-name         = "AMTest100"
rg-location     = "West Europe"
usr-mid-name    = "AMUSRMID100"

Enter fullscreen mode Exit fullscreen mode
HOW TO LOCK TERRAFORM STATE FILE:-

Run the Terraform Apply Command manually in your local System as mentioned below:-

terraform apply --var-file="usrmid.tfvars"

Enter fullscreen mode Exit fullscreen mode

When Prompted for "Yes", Press Control + C to terminate the Execution.

Image description

Next time, when you Re-Execute the Command, It will Inform the User that Terraform State File is in Locked State.

Image description

NOW ITS TIME TO TEST !!!...

TEST CASES:-
TEST CASE #1: TERRAFORM BLOB STATE == AVAILABLE:-
DESIRED OUTPUT: PIPELINE WILL FAIL WITH EXIT CODE 1.
TERRAFORM BLOB STATE:-
Image description
WORKFLOW RUN:-
Image description
TEST CASE #2: TERRAFORM BLOB STATE == LEASED:-
DESIRED OUTPUT: PIPELINE EXECUTES SUCCESSFULLY BREAKING TERRAFORM BLOB STATE LEASE.
TERRAFORM BLOB STATE:-
Image description
WORKFLOW RUN:-
Image description
TEST CASE #3: TERRAFORM BLOB STATE == BROKEN:-
DESIRED OUTPUT: PIPELINE EXECUTES SUCCESSFULLY WITHOUT ANY ALTERATIONS TO THE CURRENT STATE.
TERRAFORM BLOB STATE:-
Image description
WORKFLOW RUN:-
Image description
OVERALL GITHUB ACTIONS WORKFLOW RUNS:-
Image description

Hope You Enjoyed the Session!!!

Stay Safe | Keep Learning | Spread Knowledge

Top comments (0)