Invoke An Azure Runbook Using Terraform

Introduction

 
In continuation with our Terraform blog series – Creating Resource Groups In AzureUsing Terraform and Creating Azure Resource Groups Using Terraform And Azure Key Vault, we will be sharing knowledge on one of the Terraform limitations w.r.t Azure Runbooks that we recently dealt with and how to work around it.
 
Before we start with explaining the implementation, let’s first take a pause to acknowledge Azure Runbooks a bit and if you haven’t already used Azure Automation Accounts, you absolutely should. So, what are Azure Automation Runbooks? Here is the answer - an Azure Runbook is a feature of Azure Automation that allows you to execute workflows from within Azure or remotely to automate processes. Many companies who have their Infrastructure hosted on Microsoft Azure Cloud are using Azure Automation Runbooks to initiate automated processes like powering on or off Azure Virtual Machines, generating of custom reports for resources, or checking the health of specific type of Azure Resources, etc. on a defined schedule or demand basis. This feature is very useful in performing automated operations in Azure when you want to perform something from a centralized location as it runs via a PaaS resource called Azure Automation Account. There are different types of Azure Runbooks available which can be understood in detail in this article.
 
Let’s jump into the problem that we have - we want to invoke one of our Azure Automation Runbooks from Terraform to perform a specific task where we had to pass some parameters that were required for runbook execution. We are going to state the obvious and it may come as a surprise to some of you – like any other tools/platforms, Terraform has its own set of limitations and is known for provisioning new resources only, It does not offer a direct way to invoke runbooks by passing required parameters.
With the options available in Terraform for runbooks, you can only create a new runbook with new schedule but cannot perform operations like invoking an existing runbook by passing required parameter values, using terraform resources. So, we had to look for alternative options available in Terraform. Guess what we got….local-exec provisioner. With this local-exec provisioner, we tried to fulfill our requirements. Let us explain how we implemented this so that others can reuse this ready to use stuff and can avoid spending hours on the same thing. The reusable code for this use case is available here: Invoke-Azure-Runbooks.
 
Pre-Requisites
  • Azure Automation Account
  • PowerShell Runbook hosted on the above automation account
  • Terraform Open Source or Enterprise Version
Runbook Implementation
 
For the sake of simplicity, we will be explaining a simple scenario where you can invoke a runbook by passing the required parameters to it from Terraform and runbook will output the values of passed parameters. As mentioned above, there are different types of Runbooks but for this blog, we will be focusing on PowerShell type runbook. If you are new to PowerShell, please have a look at this very good article for more details on Microsoft PowerShell. We are not detailing the runbook implementation as it may differ based on use cases or requirements and the purpose of this article is to explain runbook invocation via terraform by passing parameters only.
 
Let’s begin with implementation. The first order of action is to create a runbook in the Azure Automation account and a simple PowerShell script for it which accepts parameters.
 
Please follow the below steps:
  • Open your Automation account in Azure Portal.
  • Click Runbooks under Process Automation. The list of runbooks is displayed.
  • Click Create a runbook at the top of the list.
  • Enter a name for the runbook name in the Name field and select PowerShell for the Runbook type field.
  • Click on the Create button to create a runbook.
  • Now, on “Edit PowerShell Runbook”, copy and paste below PowerShell code.
    1. param(  
    2.     [Parameter(Mandatory = $true)]  
    3.     [object] $WebhookData)  
    4. try {  
    5.     # If runbook was called from Webhook, WebhookData will not be null.  
    6.     if($WebhookData) {  
    7.         #Retrieve data from Webhook request body  
    8.         $inputs = (ConvertFrom - Json - InputObject $WebhookData.RequestBody)  
    9.         #Fetch values of parameters passed to webhook in form of request body  
    10.         $Attribute1 = $inputs.Attribute1  
    11.         $Attribute2 = $inputs.Attribute2  
    12.         Write - Output "=========================================================="  
    13.         Write - Output "Value of First Parameter - "  
    14.         $Attribute1  
    15.         Write - Output "Value of Second Parameter - "  
    16.         $Attribute2  
    17.         Write - Output "=========================================================="  
    18.     }  
    19. catch {  
    20.     Write - Output $_.Exception.Message  
    21.     Write - Error "This runbook is meant to be started from webhook only." - ErrorAction Stop  
    22. }  
  • Click on Save and then publish buttons visible on the same screen.
  • Now, click on Webhook button available on the published runbook screen
  • On the Add Webhook screen, click on Create a new Webhook option.
  • On the next screen, provide a name in the Name field and expiry DateTime for your Webhook. Copy the Webhook Url and keep it aside as it will be using in Terraform for invoking the runbook
  • Now, Click on OK and then Create.
Now we are all set with our runbook that can be invoked from anywhere using its Webhook URL.
 
Terraform Implementation
 
We will follow the below code hierarchy that we followed in our earlier article where each deployment type has a separate folder containing all required files including a main file, a variables file, an output file, and an optional .tfvars file that provides inputs for executions.
 
main.tf
 
Let’s begin with Terraform implementation by opening the main.tf file and adding the following code block. As we mentioned in the above sections, Terraform does not support the invocation of runbooks through its native resources and therefore, we will be using a “local-exec” provisioner with a “null_resource” resource where a PowerShell script will be used to invoke our runbook created in the above steps.
  1. #Invocation of Azure Runbook via Local - Exec  
  2. resource "null_resource"  
  3. "invoke_azure_runbook" {  
  4.     provisioner "local-exec" {  
  5.         command = ".\\Invocation-Script.ps1 -Param1 '${var.variable1}' -Param2 '${local.variable2}'"  
  6.         interpreter = ["pwsh""-Command"]  
  7.     }  
  8. }   
Here, we are creating a “null_resource” resource of Terraform with “local-exec” provisioner used for executing a PowerShell script i.e. “Invocation-Script.ps1” by passing the arguments to it from variable.tf file or local variables. Now, we will be adding a locals block to define the local variables that we used in above code. In this block, we are manipulating the “resource_group_name” variable value a bit, passed from variable.tf file and passing the manipulated value as a local variable value to the local-exec provisioner.
 
We are adding this block to show that we can also pass local variables (manipulated variables) in command of the “local-exec” provisioner as argument values.
  1. #Define Local Variables  
  2. locals {  
  3.     #Replacing blank spaces from variable value and storing it in local variable called“ variable2”  
  4.     variable2 = replace(var.variable2, " """)  
  5. }   
Now, let’s create “Invocation-Script.ps1” PowerShell file, add it to the root of your Terraform directory where all other files like main.tf, variable.tf are placed and add the following simple code.
  1. # Define Parameters to be passed to script from Terraform via Local - Exec  
  2. param(  
  3.     [string] $Param1,  
  4.     [string] $Param2)  
  5. #Create a request body object  
  6. for webhook  
  7. $body = @ {  
  8.     Attribute1 = $Param1  
  9.     Attribute2 = $Param2  
  10. }  
  11. #Convert above object into Json  
  12. $requestBody = $body | ConvertTo - Json  
  13. $uri = "Your_Runbook_Webhook_Uri which you copied during webhook creation"  
  14. #Invoke the webhook using Webhook Uri  
  15. $response = Invoke - WebRequest - Method Post - Uri $uri - Body $requestBody  
  16. if ($response.StatusCode - eq "202" - and $response.StatusDescription - eq "Accepted") {  
  17.     Write - Host "Your webhook invocation is completed."  
  18. else {  
  19.     Write - Host "Your webhook invocation has failed."  
  20. }   
This “Invocation-Script.ps1” is accepting the parameter values which are passed from Terraform to it as argument values and is invoking the webhook using the “Invoke-WebRequest” command of PowerShell and passing the parameter values as Webhook request body in Json format.
 
The main executable code is now ready and we will just create the two additional files for variables and output to define the input and output variables that will be part of this execution.
 
variables.tf
  1. variable variable1 {  
  2.     type = string  
  3.     description = "First variable to be passed to Terraform"  
  4.     default = "Runbook Invocation"  
  5. }  
  6. variable variable2 {  
  7.     type = string  
  8.     description = "First variable to be passed to Terraform"  
  9.     default = "Through Terraform"  
  10. }   
outputs.tf
 
As “null_resource” does not return output of executed PowerShell script, so we can have a custom output stored in a variable that will be dependent on “null_resource” resource execution completion.
  1. output "Remarks" {  
  2.     value = "Your runbook has been invoked successfully"  
  3.     depends_on = [  
  4.         null_resource.invoke_azure_runbook  
  5.     ]  
  6. }   
That’s it!! We are ready with the code now that will invoke runbook through webhook by passing required parameters from Terraform. As mentioned in our earlier articles also, remember to perform a Terraform Init before doing a plan and apply. We are using a sample tfvars file for our local execution with variables defined under,
  1. variable1 = "your-value-for-variable1"  
  2. variable2 = "your-value-for-variable2"  
  3. terraform plan -var-file="<yourfilename>.tfvars" or terraform plan  
  4. terraform apply -var-file="<yourfilename>.tfvars" or terraform apply  
Once you will run the “Terraform Apply” operation, it will give you below output:
 
 
If you will go to the runbook jobs at Azure Portal, following output will be displayed in under “All Logs” against job executed from your webhook invocation.
 
 
We have now successfully invoked Azure Runbook through Terrafrom by passing the required parameters. You can get the solution code at our GitHub Repository for this use case. Happy Coding!!