Friday, 11 September 2020

Azure Terraform Infra as Code Deployment via Custom PowerShell with Azure DevOps Pipelines – Part 1 – Create Plan

As described in the post “Why Azure DevOps Terraform Extension Task by Microsoft DevLabs to Deploy Infra to Azure Does Not Work for Me”, the Microsoft DevLabs task to plan and apply terraform infrastructure as code, is demanding to store state in Azure blob storage, which requires to create Azure resources manually as prerequisite of using the task. In this post let us look at a custom implementation of terraform plan and apply task utilizing PowerShell, while storing terraform state and plans in Azure Git repo and have an approval in between the plan and apply steps, to enhance the deployment workflow.

PowerShell Script to Execute Terraform Plan

To execute terraform via command line each variable must be passed in below syntax.


Example: -var='envName=dev'

To build up such variables all parameters can be passed to PowerShell as params and then these parameters can be used to build the variable requirement syntax as shown below.


# Set terraform variable with value

$AzureSubscriptionId='AzureSubscriptionId',$AzureSubscriptionId -join $joinChar;

The out come of above would be varibaleName=variableValue. The using these type of formatted variable in PowerShell for each parameter, terraform required variable passing syntax as a string can be built utilizing PowerShell script segment similar to below.

# Build up terraform variable format string

$varPrefix= "-var";

$vars =  ($varPrefix,("'",$AzureSubscriptionId,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$AzureSPNAppId,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$AzureSPNPwd,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$AzureTenantId,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$projectName,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$envName,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$prodEnvName,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$location,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$dotnetFramework,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$deploySlotName,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$planKind,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$planReserved,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$planSKUTier,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$planSKUSize,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$storageTier,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$storageReplicatonType,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$functionVersion,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$keyvaultPurgeProtection,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$SqlSvrVersion,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$SqlSvrAdminUser,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$SqlSvrAdminUserPwd,"'" -join '') -join $joinChar

       ),($varPrefix,("'",$SQLDatabaseEdition,"'" -join '') -join $joinChar

       ) -join ' '

Above is just an example set of parameters. This can be altered based on the infrastructure creation need of yours. Outcome of above would be space separated string of parameters for terraform such as example below.

-var='AzureSubscriptionId=subsid' -var='AzureSPNAppId=appregid' -var='AzureSPNPwd=spnpwd' -var='AzureTenantId=tenatid' -var='projectName=qonsult' -var='envName=dev'

Next would be utilizing the parameter string build up the command for terraform plan and execute it via PowerShell.

# Set terraform plan file path and state file path parameters      

$outParam = '-out',("'",$planFilePath,"'" -join '') -join $joinChar

$stateParam = '-state',("'",$tfStateFilePath,"'" -join '') -join $joinChar

# Build up terraform plan command

$planCommand = 'terraform plan',$outParam,$stateParam,$vars -join ' '

write-host ($planCommand)

# Execute terraform plan

Invoke-Expression $planCommand

Full script sample can be found here, which can be used with your own parameters (sample parameters defined) based on your need of infrastructure.

Execute Terraform Plan with Azure Pipeline and Store State and Plan in a Repo

The first step is to download the package containing the terraform, and the PowerShell file to execute the terraform plan.

As next step terraform can be installed to the agent machine and set the path for enable execution of command line terraform commands. This can be done via Install Terraform task by the extension of Microsoft DevLabs.

Once the package downloaded and extracted in the same package extracted folder need to create a folder and clone the repo which contains the terraform state and plans. This could be empty repo just containing the InfraRelease folder.


The cloning can be performed with below PowerShell script.

$clonePath = '$(releaseDataRepoClonePath)';

$cloneUrl = '$(releaseDataRepoCloneUrl)';

$planFileFolderPath = '$(planFileFolderPath)';

md $clonePath

cd $clonePath


git clone $cloneUrl $clonePath

If(!(test-path $planFileFolderPath))


New-Item -ItemType Directory -Force -Path $planFileFolderPath



Such script can be inline script of a PowerShell task. The parameters release repo clone path should be within the extracted package folder to make it easy to refer to the files. The clone url embedded with credentials can be generated using Azure DevOps Git repo clone options.

The clone repo url should be in the format of as shown below.

Then terraform init should be run to get the required terraform modules and plugins loaded so that the can be executed.

All these download package, install terraform to agent, clone the repo containing terraform state and plans, and initializing terraform can be created as pre task group and can be utilized for both Plan and Execute steps.

The plan step would contain above for tasks done as prerequisites and the execution of terraform plan via the PowerShell script described previously.

The execution should happen from the extracted package path which is having the PowerShell script, and varaibles tf, and contains a folder with cloned repo where the state and plan files may exist if the command is running after any initial executions. Correct plan file path and state file path, which would be inside cloned repo, should be provided to the PowerShell script.

After execution plan execution is completed a new plan file would be added and the repo should be pushed with the changes. This need can be achieved via another PowerShell script as shown below.

$clonePath = '$(releaseDataRepoClonePath)';


cd $clonePath


git config "$(gitUserEmail)"

git config "$(gitUserName)"

# Upload plan file and state file to repo

$commitMessage = "Add terraform plan and state for env",$envName,"for release",$env:RELEASE_RELEASENAME -join ' '

git add .

git commit -m "$commitMessage"

git push

The step will be executed from the extracted cloned repo path.

In summary following are the steps.

· Download, and terraform plan execution PowerShell to a folder

· Install terraform in the agent

· Clone the terraform state and plan file repo to folder in path where the etc. extracted

· Execute a terraform init command from the location of to ensure plugins and modules are loaded.

· Execute the terraform plan supplying terraform state path (state file may not exist in first execution and that is not an issue) and path to save plan. These paths should be in the cloned repo.

· Push the new plan added to the Azure Git repo

Once above steps executed the plan to be applied can be viewed in the log of Azure DevOps pipeline.

In the next post let’s understand how to add the manual intervention step and wait for approval of plan by viewing the plan in the log as shown above, and get the same terraform plan executed with the pipeline.

No comments:

Popular Posts