Count and if else with Terraform & Devops

arindam0310018

Arindam Mitra

Posted on January 28, 2023

Count and if else with Terraform & Devops

Greetings my fellow Technology Advocates and Specialists.

In this Session, I will demonstrate, Count and If Else with Terraform and DevOps by deploying Azure Managed Grafana.

REQUIREMENTS:-
  1. Azure Subscription.
  2. Azure DevOps Organisation and Project.
  3. Service Principal with Required RBAC ( Contributor) applied on Subscription or Resource Group(s).
  4. Azure Resource Manager Service Connection in Azure DevOps.
  5. Microsoft DevLabs Terraform Extension Installed in Azure DevOps and in Local System (VS Code Extension).
OUT OF SCOPE:-
Azure DevOps Pipeline Code Snippet Explanation.
If you are interested to understand the Pipeline Code Snippet, please refer my other blogs in Terraform Series.
CODE REPOSITORY:-

COUNT AND IF ELSE WITH TERRAFORM AND DEVOPS:-

Greetings my fellow Technology Advocates and Specialists.

In this Session, I will demonstrate, Count and If Else with Terraform and DevOps by deploying Azure Managed Grafana.

REQUIREMENTS:-
  1. Azure Subscription.
  2. Azure DevOps Organisation and Project.
  3. Service Principal with Required RBAC ( Contributor) applied on Subscription or Resource Group(s).
  4. Azure Resource Manager Service Connection in Azure DevOps.
  5. Microsoft DevLabs Terraform Extension Installed in Azure DevOps and in Local System (VS Code Extension).
OUT OF SCOPE:-
Azure DevOps Pipeline Code Snippet Explanation.
If you are interested to understand the Pipeline Code Snippet, please refer my other blogs in Terraform Series.
HOW DOES MY CODE PLACEHOLDER LOOKS LIKE:-
Image description
PIPELINE CODE SNIPPET:-
AZURE DEVOPS YAML PIPELINE (azure-pipelines-az-managed-grafana-v1.0.yml):-
trigger
  none

######################
#DECLARE PARAMETERS:-
######################
parameters:
- name: SubscriptionID
  displayName: Subscription ID Details Follow Below:-
  default: 210e66cb-55cf-424e-8daa-6cad804ab604
  values:
  -  210e66cb-55cf-424e-8daa-6cad804ab604

- name: ServiceConnection
  displayName: Service Connection Name Follows
HOW DOES MY CODE PLACEHOLDER LOOKS LIKE:-
Image description
PIPELINE CODE SNIPPET:-
AZURE DEVOPS YAML PIPELINE (azure-pipelines-az-managed-grafana-v1.0.yml):-


trigger:
  none

######################
#DECLARE PARAMETERS:-
######################
parameters:
- name: SubscriptionID
  displayName: Subscription ID Details Follow Below:-
  default: 210e66cb-55cf-424e-8daa-6cad804ab604
  values:
  -  210e66cb-55cf-424e-8daa-6cad804ab604

- name: ServiceConnection
  displayName: Service Connection Name Follows Below:-
  default: amcloud-cicd-service-connection
  values:
  -  amcloud-cicd-service-connection

######################
#DECLARE VARIABLES:-
######################
variables:
  TfVars: "az-managed-grafana.tfvars"
  PlanOutput: "tfplan"
  ResourceGroup: "tfpipeline-rg"
  StorageAccount: "tfpipelinesa"
  Container: "terraform"
  TfstateFile: "AMG/AzManagedGrafana.tfstate"
  BuildAgent: "windows-latest"
  WorkingDir: "$(System.DefaultWorkingDirectory)/Az-Managed-Grafana"
  Target: "$(build.artifactstagingdirectory)/AMTF"
  Environment: "NonProd"
  Artifact: "AM"

#########################
# Declare Build Agents:-
#########################
pool:
  vmImage: $(BuildAgent)

###################
# Declare Stages:-
###################
stages:

- stage: PLAN
  jobs:
  - job: PLAN
    displayName: PLAN
    steps:
# Install Terraform Installer in the Build Agent:-
    - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
      displayName: INSTALL TERRAFORM VERSION - LATEST
      inputs:
        terraformVersion: 'latest'
# Terraform Init:-
    - task: TerraformTaskV2@2
      displayName: TERRAFORM INIT
      inputs:
        provider: 'azurerm'
        command: 'init'
        workingDirectory: '$(workingDir)' # Az DevOps can find the required Terraform code
        backendServiceArm: '${{ parameters.ServiceConnection }}' 
        backendAzureRmResourceGroupName: '$(ResourceGroup)' 
        backendAzureRmStorageAccountName: '$(StorageAccount)'
        backendAzureRmContainerName: '$(Container)'
        backendAzureRmKey: '$(TfstateFile)'
# Terraform Validate:-
    - task: TerraformTaskV2@2
      displayName: TERRAFORM VALIDATE
      inputs:
        provider: 'azurerm'
        command: 'validate'
        workingDirectory: '$(workingDir)'
        environmentServiceNameAzureRM: '${{ parameters.ServiceConnection }}'
# Terraform Plan:-
    - task: TerraformTaskV2@2
      displayName: TERRAFORM PLAN
      inputs:
        provider: 'azurerm'
        command: 'plan'
        workingDirectory: '$(workingDir)'
        commandOptions: "--var-file=$(TfVars) --out=$(PlanOutput)"
        environmentServiceNameAzureRM: '${{ parameters.ServiceConnection }}'

# Copy Files to Artifacts Staging Directory:-
    - task: CopyFiles@2
      displayName: COPY FILES ARTIFACTS STAGING DIRECTORY
      inputs:
        SourceFolder: '$(workingDir)'
        Contents: |
          **/*.tf
          **/*.tfvars
          **/*tfplan*
        TargetFolder: '$(Target)'
# Publish Artifacts:-
    - task: PublishBuildArtifacts@1
      displayName: PUBLISH ARTIFACTS
      inputs:
        targetPath: '$(Target)'
        artifactName: '$(Artifact)' 

- stage: DEPLOY
  condition: succeeded()
  dependsOn: PLAN
  jobs:
  - deployment: 
    displayName: Deploy
    environment: $(Environment)
    pool:
      vmImage: '$(BuildAgent)'
    strategy:
      runOnce:
        deploy:
          steps:
# Download Artifacts:-
          - task: DownloadBuildArtifacts@0
            displayName: DOWNLOAD ARTIFACTS
            inputs:
              buildType: 'current'
              downloadType: 'single'
              artifactName: '$(Artifact)'
              downloadPath: '$(System.ArtifactsDirectory)' 
# Install Terraform Installer in the Build Agent:-
          - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
            displayName: INSTALL TERRAFORM VERSION - LATEST
            inputs:
              terraformVersion: 'latest'
# Terraform Init:-
          - task: TerraformTaskV2@2 
            displayName: TERRAFORM INIT
            inputs:
              provider: 'azurerm'
              command: 'init'
              workingDirectory: '$(System.ArtifactsDirectory)/$(Artifact)/AMTF/' # Az DevOps can find the required Terraform code
              backendServiceArm: '${{ parameters.ServiceConnection }}' 
              backendAzureRmResourceGroupName: '$(ResourceGroup)' 
              backendAzureRmStorageAccountName: '$(StorageAccount)'
              backendAzureRmContainerName: '$(Container)'
              backendAzureRmKey: '$(TfstateFile)'
# Terraform Apply:-
          - task: TerraformTaskV2@2
            displayName: TERRAFORM APPLY # The terraform Plan stored earlier is used here to apply only the changes.
            inputs:
              provider: 'azurerm'
              command: 'apply'
              workingDirectory: '$(System.ArtifactsDirectory)/$(Artifact)/AMTF'
              commandOptions: '--var-file=$(TfVars)' # The terraform Plan stored earlier is used here to apply. 
              environmentServiceNameAzureRM: '${{ parameters.ServiceConnection }}'



Enter fullscreen mode Exit fullscreen mode
TERRAFORM CODE SNIPPET:-
TERRAFORM (main.tf):-


terraform {

  required_version = ">= 1.3.3"

  backend "azurerm" {
    resource_group_name  = "tfpipeline-rg"
    storage_account_name = "tfpipelinesa"
    container_name       = "terraform"
    key                  = "AMG/AzGrafana.tfstate"
  }

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.32.0"
    }

  }
}
provider "azurerm" {
  features {}
  skip_provider_registration = true
}



Enter fullscreen mode Exit fullscreen mode
EXPLANATION:-
The "main.tf" file contains:-
1. Terraform version.
2. Backend terraform state filename and location.
3. Required terraform providers.
TERRAFORM (az-managed-grafana.tf):-


#####################
## Azure AD Group:-
#####################

resource "azuread_group" "az-aad-group" {
  count                   = length(var.az-aad-group-name)
  display_name            = var.az-aad-group-name[count.index]
  owners                  = data.azuread_users.az-aad-grp-owner.object_ids
  prevent_duplicate_names = false
  security_enabled        = true
}

#####################
## Resource Group:-
#####################

resource "azurerm_resource_group" "rg" {
  name      = var.rg-name
  location  = var.location
}

##############################
## Azure Managed Grafana:-
##############################

resource "azurerm_dashboard_grafana" "az-grafana" {
  count                 = length(var.az-grafana-name)
  name                              = var.az-grafana-name[count.index]
  resource_group_name               = azurerm_resource_group.rg.name
  location                          = var.location
  zone_redundancy_enabled           = var.az-grafana-zone-redundancy != "" ? var.az-grafana-zone-redundancy : "true"
  api_key_enabled                   = var.az-grafana-api-key != "" ? var.az-grafana-api-key : "true"
  deterministic_outbound_ip_enabled = var.az-grafana-outbound-ip != "" ? var.az-grafana-outbound-ip : "true"
  public_network_access_enabled     = var.az-grafana-public-access != "" ? var.az-grafana-public-access : "false"

  identity {
    type = var.az-grafana-identity
  }

}

#######################################
## Role Based Access Control (RBAC):-
#######################################

resource "azurerm_role_assignment" "az-rbac-grafana-admin" {
  count                = length(azurerm_dashboard_grafana.az-grafana.*.id)
  scope                = azurerm_dashboard_grafana.az-grafana[count.index].id
  role_definition_name = "Grafana Admin"
  principal_id         = azuread_group.az-aad-group[0].id 
}

resource "azurerm_role_assignment" "az-rbac-grafana-editor" {
  count                = length(azurerm_dashboard_grafana.az-grafana.*.id)
  scope                = azurerm_dashboard_grafana.az-grafana[count.index].id
  role_definition_name = "Grafana Editor"
  principal_id         = azuread_group.az-aad-group[1].id 
}

resource "azurerm_role_assignment" "az-rbac-grafana-viewer" {
  count                = length(azurerm_dashboard_grafana.az-grafana.*.id)
  scope                = azurerm_dashboard_grafana.az-grafana[count.index].id
  role_definition_name = "Grafana Viewer"
  principal_id         = azuread_group.az-aad-group[2].id 
}



Enter fullscreen mode Exit fullscreen mode
EXPLANATION:-
The "az-managed-grafana.tf" file will perform below:-
1. Create one or more AAD Group(s) using terraform "count" and "length". The Names of the AAD group(s) are passed as an array - list(string).
2. The owners of the AAD Group(s) are referenced in Terraform Data block as they already exists.
3. Create a single resource group.
4. Create one or more Azure Managed Grafana instance(s) using terraform "count" and "length". The Names of the Azure Managed Grafana instance(s) are passed as an array - list(string).
5. The resource group under which one or more Azure Managed Grafana instance(s) gets deployed is referenced as "Implicit Dependency".
6. If else block is used for - "zone redundancy", "api key", "outbound IP" and "public network access".
7. The id of each Azure Managed Grafana Instance and Object id of each AAD Group is stored as "Terraform Output". RBAC was then created by counting output ids of Azure Managed Grafana instance(s) which is the scope. Role definition defined here is static (For Example - "Grafana Admin") which is mapped to Object id of the Azure AD Group referenced here as array index.
IF ELSE EXAMPLE AND EXPLANATION:-


zone_redundancy_enabled = var.az-grafana-zone-redundancy != "" ? var.az-grafana-zone-redundancy : "true"



Enter fullscreen mode Exit fullscreen mode

If the variable "var.az-grafana-zone-redundancy" is equal to NULL, then the value is "true". If not, then the value is, what is defined for the variable.

TERRAFORM (data.tf):-


# Data source to retrieve User object ID
data "azuread_users" "az-aad-grp-owner" {
  user_principal_names = var.az-aad-group-owner
  ignore_missing       = true
}



Enter fullscreen mode Exit fullscreen mode
NOTE:-
Terraform Data block to retrieve already existing User Principal name(s).
TERRAFORM (output.tf):-


output "az-grafana-id" {
  value = azurerm_dashboard_grafana.az-grafana.*.id
}

output "az-aad-group-id" {
  value = azuread_group.az-aad-group.*.id
}



Enter fullscreen mode Exit fullscreen mode
NOTE:-
Terraform output block to retrieve the id(s) of one or more Azure Managed Grafana Instances and object id(s) of one or more Azure AD Group(s).
TERRAFORM (variables.tf):-


variable "az-aad-group-name" {
  type        = list(string)
  description = "Names of the Azure Active Directory Group."
}

variable "az-aad-group-owner" {
  type        = list(string)
  description = "List of Users added as owner of the Azure Active Directory Group."
}

variable "rg-name" {
  type        = string
  description = "Name of the Resource Group."
}

variable "location" {
  type        = string
  description = "Location of the Resource Group and Resources."
}

variable "az-grafana-name" {
  type        = list(string)
  description = "Name of the Azure Managed Grafana."
}

variable "az-grafana-zone-redundancy" {
  type        = string
  description = "Enable zone redundancy setting of the Grafana Instance."  
}

variable "az-grafana-api-key" {
  type        = string
  description = "Enable the api key setting of the Grafana Instance."
}

variable "az-grafana-outbound-ip" {
  type        = string
  description = "Enable the Grafana Instance to use Deterministic outbound IPs."
}

variable "az-grafana-public-access" {
  type        = string
  description = "To enable traffic over the Public Interface."
}

variable "az-grafana-identity" {
  type        = string
  description = "System Assigned Managed Identity"
}



Enter fullscreen mode Exit fullscreen mode
NOTE:-
This is Self-Explanatory.
TERRAFORM (az-managed-grafana.tfvars):-


az-aad-group-name               = ["am-grafana-admin-aad-grp", "am-grafana-editor-aad-grp", "am-grafana-viewer-aad-grp"]
az-aad-group-owner              = ["U1@mitra008.onmicrosoft.com", "U2@mitra008.onmicrosoft.com"]
rg-name                         = "am-grafana-rg"
location                        = "west europe"
az-grafana-name                 = ["am-grafana-04", "am-grafana-05"]
az-grafana-zone-redundancy      = ""
az-grafana-api-key              = ""
az-grafana-outbound-ip          = ""
az-grafana-public-access        = ""
az-grafana-identity             = "SystemAssigned"



Enter fullscreen mode Exit fullscreen mode
NOTE:-
This is Self-Explanatory except where the value of the variables are NULL. This is where we have used Terraform If else which is explained above.
TEST RESULTS:-
Pipeline Execution:-
Image description
Azure Managed Grafana Deployed:-
Image description
Azure Managed Grafana Configuration:-
Image description
Image description
Azure AAD Group Deployed:-
Image description
Role Assignments:-
Image description
Image description

Hope You Enjoyed the Session!!!

Stay Safe | Keep Learning | Spread Knowledge

💖 💪 🙅 🚩
arindam0310018
Arindam Mitra

Posted on January 28, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related