Unable to fetch data using external data source - azure

I am trying to fetch values from azure using external data source in terraform. However, i dont understand what am i doing wrong when i try to export values using write-output, getting an error
data.external.powershell_test: data.external.powershell_test: command "Powershell.exe" produced invalid JSON: invalid character 'l' looking for beginning of object key string"
Below is my script
$vm=(Get-AzureRmVM -ResourceGroupName MFA-RG -Name vm2).name | convertTo-json
Write-Output "{""first"" : ""$vm""}"
Main.tf file
data "external" "powershell_test" {
program = ["Powershell.exe", "./vm.ps1"]
}
output "value" {
value = "${data.external.powershell_test.result.first}"
}
Can someone tel me what wrong with the script ? and if i am using write-out properly ?
Edited-------------
Below is the screenshot when i run vm.ps1 directly
Also, when i directly assign value to a variable as below, terraform is able to execute the code.
$vm = "testvm"
Write-Output "{""first"" : ""$vm""}"

For your issue, you should change your PowerShell command like this:
$vm=(Get-AzureRmVM -ResourceGroupName MFA-RG -Name vm2).name | convertTo-json
Write-Output "{""first"" : $vm}"
And you could change the code in the data source like this or not, but I suggest you do this:
data "external" "powershell_test" {
program = ["Powershell.exe", "${path.module}/vm.ps1"]
}
The result on my side is below:
I use the new Azure PowerShell module Az and my code shows here:
PowerShell:
$vm=(Get-AzVM -ResourceGroupName charles -Name azureUbuntu18).name | convertTo-json
Write-Output "{""first"" : $vm}"
Terraform:
data "external" "powershell_test" {
program = ["Powershell.exe", "${path.module}/vm.ps1"]
}
output "value" {
value = "${data.external.powershell_test.result.first}"
}

data.external.powershell_test.result is the only valid attribute, and it is map.
So the code will be changed to
output "value" {
value = "${data.external.powershell_test.result['first']}"
}
Reference:
https://www.terraform.io/docs/configuration-0-11/interpolation.html#user-map-variables

Thanks Charles XU for the answer. I was looking for Azure Application Gateway and after lots of digging I ended up here, as Terraform is yet to provide a data source for Azure Application Gateway. However, the same can be done using shell script and azure rest API.
Using Shell Script
appgw.sh
#!/bin/bash
#Linux: Requires Azure cli and jq to be available
#Getting Application Gateway ID using azure application gateway rest API, az cli as data source doesn't exist for it.
appgwid=$(az rest -m get --header "Accept=application/json" -u 'https://management.azure.com/subscriptions/{subscription-id}/resourceGroups/newrg/providers/Microsoft.Network/applicationGateways?api-version=2020-07-01' | jq '.value[].id')
#Terraform External Data Source requires an output, else it will return in unmarshal json error with }
echo "{\"appgw_id\" : $appgwid}"
data.tf
data "external" "appgw_id_sh" {
program = ["/bin/bash", "${path.module}/appgw.sh"]
}
outputs.tf
output "appgw_id_sh" {
value = data.external.appgw_id_sh.result.appgw_id
}
Using Powershell
appgw.ps1
#Windows: Require Azure Powershell to be available
#1. Install-Module -Name PowerShellGet -Force
#2. Install-Module -Name Az -AllowClobber
#Getting Application Gateway ID using AzApplicationGateway AzResource as data source doesn't exist for it.
$appGw = (Get-AzApplicationGateway -Name "appgw-name" -ResourceGroupName "appgw-rg-name").id | convertTo-json
#Terraform External Data Source requires an output, else it will return in unmarshal json error with }
Write-Output "{""appgw_id"" : $appgw}"
data.tf
data "external" "appgw_id_ps" {
program = ["Powershell.exe", "${path.module}/appgw.ps1"]
}
outputs.tf
output "appgw_id_ps" {
value = data.external.appgw_id_ps.result.appgw_id
}

FYI - when I use powershell 7, I need to use pwsh.exe instead of powershell.exe inside program code.
data "external" "powershell_test" {
program = ["**pwsh.exe**", "${path.module}/vm.ps1"]
}

Related

To get the repository id from an existing project of Azure DevOps in terraform code

I need to get the repository id of an existing project to work on that repo. There seems no other way than using Azure DevOps REST API.
I tried to utilize the REST API to get the repo id in my terraform code:
data "http" "example" {
url = "https://dev.azure.com/{organization}/{project}/_apis/git/repositories?api-version=6.0"
request_headers = {
"Authorization" = "Basic ${base64encode("PAT:${var.personal_access_token}")}"
}
}
output "repository_id" {
value = data.http.example.json.value[0].id
}
It yields error while I was running terraform plan:
Error: Unsupported attribute
line 29, in output "repository_id":
29: value = data.http.example.json.value[0].id
I tried also with jsondecode (jq is already installed):
resource "null_resource" "example" {
provisioner "local-exec" {
command = "curl -s -H 'Authorization: Bearer ${var.pat}' https://dev.azure.com/{organization}/{project}/_apis/git/repositories?api-version=6.0 | jq '.value[0].id'"
interpreter = ["bash", "-c"]
}
}
output "repo_id" {
value = "${jsondecode(null_resource.example.stdout).id}"
}
That did not work either!!
Azure DevOps REST API works fine, I just cannot fetch the value from the responce into terraform! What would be the right code or can it be done without using REST API!
Thank you!
To proper way to interact with external API and return its output to TF is through external data source. TF docs linked provide example of how to use and create such a data source.

Creating custom host name binding for app service in terraform fails with Cannot find Certificate with name

I am trying to create custom host name binding for app service in terraform and I am using the following configuration for that
resource "azurerm_app_service_custom_hostname_binding" "webapp_fqdn" {
for_each = local.apsvc_fqdns_locations
hostname = each.value.fqdn
app_service_name = azurerm_app_service.webapp[each.value.apsvc_location_key].name
resource_group_name = var.regional_web_rg[each.value.location].name
ssl_state = "SniEnabled"
thumbprint = azurerm_app_service_certificate.cert[each.value.certificate_location_key].thumbprint
}
resource "azurerm_app_service_certificate" "cert" {
for_each = local.certificates_locations
name = each.value.certificate_name
resource_group_name = var.regional_web_rg[each.value.location].name
location = each.value.location
key_vault_secret_id = data.azurerm_key_vault_secret.cert[each.value.certificate_name].id
}
The code fails (sporadically) with the following error
"Cannot find Certificate with name 6CAC9XXXX."
When the error occures, I can go to portal and see the following
If I create those resources through Az PowerShell module by running the following
Set-AzWebApp -Name app508-resc-aa1-web-centralus-showcase-apsvc -ResourceGroupName app508-resc-aa1-web-centralus -HostNames #("showcase-aa1.np.dayforcehcm.com")
New-AzWebAppSSLBinding -ResourceGroupName app508-resc-aa1-web-centralus -WebAppName app508-resc-aa1-web-centralus-showcase-apsvc -Thumbprint "6CAC9XXXX" -Name "showcase-aa1.np.dayforcehcm.com"
it works perfectly fine and the resources are created properly.
It looks like terraform tries to create both - host name and the custom host name binding by using one resource azurerm_app_service_custom_hostname_binding so is it some sort of race condition that I am facing or am I using wrong resources?
Okay, I see it's a problem with the azurerm provider. Usually, a WaitForCompletionRef call or equivalent function (whatever is offered by Azure-go-SDK) is made to wait until the create/update function is complete.
In the case of the azurerm_app_service_certificate resource, post create function call of SDK, the resourceAppServiceCertificateRead function is called immediately without waiting for the resource to be created. I think this is why you have this intermittently occuring.
if _, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, certificate); err != nil {
return fmt.Errorf("creating/updating %s: %s", id, err)
}
d.SetId(id.ID())
return resourceAppServiceCertificateRead(d, meta)
You can create an issue here or create a PR to get this added. In the meantime, as a workaround, you can put some sleep for a few seconds until the resource is created. time_sleep
could help you with this.

Querying cosmosdb container via powershell showing only system defined columns instead of custom columns

I tried querying cosmosdb container using azure powershell from azure portal with powershell modules. The result I am getting is only system defined columns instead of custom defined columns too. What should I do in order to get all columns provided in the select query?
Below is the code that was executed on via azure runbook.
$CosmosDB_Name = "dbname"
$Abt_RG_Name = "RGName"
$accountName="Acc"
$cosmosDbContext = New-CosmosDbContext -Account $accountName -Database $CosmosDB_Name -ResourceGroup $Abt_RG_Name
$query="SELECT s.visibleSalesOrderId,c.requestTimestamp,c.acquirerAction,c.statusCode,(c.statusCode='2200')? 'Success': (c.statusCode='2109')? 'Declined': (c.statusCode='2115')? 'Timeout': (c.statusCode='2110')? 'Could not Process': (c.statusCode='2111')? 'ConcurrentProcess':(c.statusCode='2300')? 'JSON_message_isEmpty': ((c.statusCode='2102')? 'InternalServerError':'Unknown') AS StatusDescription, s.acquirerRetryCounter
from salesorder s join c IN s.acquirerLog where
s.visibleSalesOrderId in ('64738389hdh')"
$Data= Get-CosmosDbDocument -Context $cosmosDbContext -CollectionId 'salesorder' -Query $query -QueryEnableCrossPartition $true -verbose
$Data
Result I got is below system properties only.
id :
_etag :
_rid :
_ts :
_attachments :
Expected result is custom defined columns(present in select list of above query) are below.
"visibleSalesOrderId": "57P702M2hdhsdGC",
"requestTimestamp": "2021-12-15T18:03:42.000Z",
"acquirerAction": "endCycldde",
"statusCode": "2188",
"StatusDescription": "Declined"

Issue with install DSC extension on Azure VM during deployment using Terraform

I am trying to use the information in this article:
https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/dsc-template#default-configuration-script
to onboard a VM to Azure Automation at deployment time and apply a configuration.
I am using Terraform to do the deployment, below is the code I am using for the extensions:
resource "azurerm_virtual_machine_extension" "cse-dscconfig" {
name = "${var.vm_name}-dscconfig-cse"
location = "${azurerm_resource_group.my_rg.location}"
resource_group_name = "${azurerm_resource_group.my_rg.name}"
virtual_machine_name = "${azurerm_virtual_machine.my_vm.name}"
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "2.76"
depends_on = ["azurerm_virtual_machine.my_vm"]
settings = <<SETTINGS
{
"configurationArguments": {
"RegistrationUrl": "${var.endpoint}",
"NodeConfigurationName": "VMConfig"
}
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"configurationArguments": {
"registrationKey": {
"userName": "NOT_USED",
"Password": "${var.key}"
}
}
}
PROTECTED_SETTINGS
}
I am getting the RegistrationURL value at execution time by running the command below and passing the value into Terraform:
$endpoint = (Get-AzureRmAutomationRegistrationInfo -ResourceGroupName $tf_state_rg -AutomationAccountName $autoAcctName).Endpoint
I am getting the Password value at execution time by running the command below and passing the value into Terraform:
$key = (Get-AzureRmAutomationRegistrationInfo -ResourceGroupName $tf_state_rg -AutomationAccountName $autoAcctName).PrimaryKey
I can tell from the logs on the VM that the extension is getting installed but never registers with the Automation Account.
Figured out what the problem was. The documentation is thin on details in some areas so it really was by trial and error that I discovered what was causing the problem. I had the wrong value in the NodeConfigurationName properties. What the documentation says about this property: Specifies the node configuration in the Automation account to assign to the node. Not having much experience with DSC, I interrupted this to mean the name of the configuration as seen in the Configurations section of the State configuration (DSC) blade of the Automation Account in the Azure portal.
What the NodeConfigurationName property is really referring to is the Node definition inside the configuration and it should be in the format of ConfigurationName.NodeName. As an example, the name of my configuration is VMConfig and in the config source I have a Node block defined called localhost. So, with this...the value of the NodeConfigurationName property should be VMConfig.localhost.

How to use Powershell splatting for Azure CLI

I want to use Powershell splatting to conditionally control which parameters are used for some Azure CLI calls. Specifically for creating CosmosDb collections.
The target was something like this:
$params = #{
"db-name" = "test";
"collection-name"= "test2";
# makes no difference if I prefix with '-' or '--'
"-key" = "secretKey";
"url-connection" = "https://myaccount.documents.azure.com:443"
"-url-connection" = "https://myaccount.documents.azure.com:443"
}
az cosmosdb collection create #params
Unfortunately, this only works for db-name and collection-name. The other parameters fail with this error:
az : ERROR: az: error: unrecognized arguments: --url-connection:https://myaccount.documents.azure.com:443
--key:secretKey
After some back and forth, I ended up using array splatting:
$params = "--db-name", "test", "--collection-name", "test2",
"--key", "secretKey",
"--url-connection", "https://myaccount.documents.azure.com:443"
az cosmosdb collection create #params
Now I can do things like this:
if ($collectionExists) {
az cosmosdb collection update #colParams #colCreateUpdateParams
} else {
# note that the partition key cannot be changed by update
if ($partitionKey -ne $null) {
$colCreateUpdateParams += "--partition-key-path", $partitionKey
}
az cosmosdb collection create #colParams #colCreateUpdateParams
}

Resources