I would like to replace my parameter file and just give the parameter with powershell. My parameters file looks like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"subnets": {
"value": {
"settings": [
{
"name": "firstSubnet",
"addressPrefix": "10.0.0.0/24"
},
{
"name": "secondSubnet",
"addressPrefix": "10.0.1.0/24"
}
]
}
}
}
}
Which I try to override with:
-subnets {"settings":[{"name": "firstSubnet","addressPrefix": "10.0.0.0/24"},{"name": "secondSubnet","addressPrefix": "10.0.1.0/24"]}}
But I keep getting errors. What is the correct syntax for using hash table as parameter?
Your example is not a hash table. It's just an unquoted JSON string.
Hash tables are defined in PowerShell as
#{}
and arrays are
#()
So you'd be looking at constructing something that looks like this:
-subnets #{
settings = #(
#{
name = 'foo'
address = 'bar'
}
)
}
and so on.
You can use ConvertFrom-Json and ConvertTo-Json to convert JSON to/from PowerShell hash tables and arrays.
Related
I am attempting to deploy a resource with Terraform using an ARM template. I will give some simple sample code below that demonstrates the issue. I realize the code below is deploying a storage account and can be done in Terraform without an ARM template, but I am just providing a piece of simple code to demonstrate the problem. My real code is deploying a SQL Managed Instance which is much more complicated and likely just confuses the question.
Here is the main.tf:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "2.41.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "resourcegroup" {
location = "North Central US"
name = "test"
}
resource "azurerm_resource_group_template_deployment" "deployment" {
name = "test-${formatdate("YYYYMMDDhhmmss", timestamp())}"
resource_group_name = azurerm_resource_group.resourcegroup.name
deployment_mode = "Incremental"
template_content = file("template.json")
parameters_content = templatefile("parameters.json",
{
name = "vwtuvnpplzgelqey"
supportsHttpsTrafficOnly = true
}
)
}
Here is the ARM template.json file:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"type": "string"
},
"supportsHttpsTrafficOnly": {
"type": "bool"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[parameters('name')]",
"location": "northcentralus",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": "[parameters('supportsHttpsTrafficOnly')]"
}
}
]
}
Here is the ARM parameters.json file:
{
"name": {
"value": "${name}"
},
"supportsHttpsTrafficOnly": {
"value": "${supportsHttpsTrafficOnly}"
}
}
If I run terraform apply on this with just the name as a parameter and hard-code supportsHttpsTrafficOnly to a true value (e.g. "supportsHttpsTrafficOnly": true), it works perfectly and the storage account is successfully deployed. As soon as I change it to use the parameter for supportsHttpsTrafficOnly, it fails with the following error:
: Code="InvalidTemplate" Message="Deployment template validation failed: 'Template parameter JToken type is not valid. Expected 'Boolean'. Actual 'String'. Please see https://aka.ms/resource-manager-parameter-files for usage details.'." AdditionalInfo=[{"info":{"lineNumber":1,"linePosition":213,"path":"properties.template.parameters.supportsHttpsTrafficOnly"},"type":"TemplateViolation"}]
This error indicates that the value for supportsHttpsTrafficOnly is a string when it should be a boolean. I don't understand this error as I am clearly defining the type as bool in the template.json file.
What am I missing so that the value is interpreted as a bool value instead of a string?
It turns out this is actually an easy fix, if not an obvious one.
Apparently all parameters need to be defined as string and then use the ARM template conversion functions to convert to the correct type.
In the template.json, we would have this in the parameters section:
“supportsHttpsTrafficOnly”: {
“type”: “string”
}
And then in the resources section, convert the string to a boolean using a template conversion:
"properties": {
"supportsHttpsTrafficOnly": "[bool(parameters('supportsHttpsTrafficOnly'))]"
}
Then finally in main.tf, pass the value as a string:
parameters_content = templatefile("parameters.json",
{
name = "vwtuvnpplzgelqey"
supportsHttpsTrafficOnly = "true"
}
Per documentation: https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax#data-types
When specifying boolean and integer values in your template, don't
surround the value with quotation marks. Start and end string values
with double quotation marks.
What you have in your code is "value": "${supportsHttpsTrafficOnly}". This probably interpreted as "value": "true" when you pass a var into it.
Switching to "value": ${supportsHttpsTrafficOnly} should fix your issue.
But if I were you, I would go for the heredoc way, which is also the one used in the example of in the provider docs: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group_template_deployment
So use:
...
template_content = <<TEMPLATE
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
...
TEMPLATE
}
instead of using a different .json file.
I am looking for a way to convert an array (e.g. of strings) into one object, where the properties are generated from the array values.
Use case: I want to generate a tags object with links to resources, based on a list of resource names. I need to do this, to link App Service resources to an Application Insights resource.
The list of resources could be supplied using a parameter:
"parameters": {
"appServices": {
"type": "array",
"metadata": {
"description": "Names of app services to link this application insights resource to via hidden tags"
}
}
}
Sample input:
['appName1', 'appName2', 'appName3']
Sample output:
"tags":
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName1'))]": "Resource",
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName2'))]": "Resource",
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName3'))]": "Resource"
}
I know you can use copy to loop over arrays but that will create an array of objects and not a single object (which is required for tags), for example:
[
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName1'))]": "Resource"
},
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName2'))]": "Resource"
},
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName3'))]": "Resource"
}
]
It would be possible to use union to merge those objects again, but that function requires you to hardcode the objects you want to merge, so it does not work when you have an input with variable length.
What I am looking for is a way to do this in a dynamic way.
There is no direct option to convert array to object.
But here's a hack to achieve what you need. This will work for array of any length.
Steps:
append hidden-link text to service names
convert array to string
replace necessary symbols and make it a valid json string.
use json() to convert string to object
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServices": {
"type": "array",
"metadata": {
"description": "Names of app services to link this application insights resource to via hidden tags"
},
"defaultValue": [ "appName1", "appName2", "appName3" ]
}
},
"functions": [],
"variables": {
"copy": [
{
"name": "as",
"count": "[length(parameters('appServices'))]",
"input": "[concat('hidden-link:', resourceId('Microsoft.Web/sites/', parameters('appServices')[copyIndex('as')]))]"
}
],
"0": "[string(variables('as'))]",
"1": "[replace(variables('0'), '[', '{')]",
"2": "[replace(variables('1'), '\",', '\":\"Resource\",')]",
"3": "[replace(variables('2'), '\"]', '\":\"Resource\"}')]"
},
"resources": [],
"outputs": {
"op1": {
"type": "object",
"value": "[json(variables('3'))]"
}
}
}
Now that lambdas have been added to bicep you can convert arrays to objects using reduce. Note that the array you reduce must consist of object items. If it doesn't you can convert it to an object using map.
// With array of objects
var names = [
{
name: 'foo'
id: 'foo-id'
}
{
name: 'bar'
id: 'bar-id'
}
]
var nameIds = reduce(names, {}, (cur, next) => union(cur, {
'${next.name}': next.id
}))
output test object = nameIds
// With array of strings
var names = [
'foo'
'bar'
]
var nameMaps = map(names, (name) => {name: name})
var nameIds = reduce(nameMaps, {}, (cur, next) => union(cur, {
'${next.name}': next.name
}))
output test object = nameIds
I'm not sure if this is the best approach to this problem.
Tags are supposed to be metadata about a specific object/service. Wouldn't it make more sense to apply a tag (say your system name, environment, etc..) and then run a query against azure on that tag?
This should achieve the same result pulling back all related resources.
I don't know if it is still relevant, but since 2021 it is possible to do with items() function
I am tying to to provision Azure AD Domain Service using Terraform by giving Terraform the Azure ARM template, this is because Terrafrom does not support provisioning Azure AD Domain Service natively.
I have exported the ARM Template and it's parameters, one of the parameters is called "notificationSettings" which is a type Object and looks like below :
"notificationSettings": {
"value": {
"notifyGlobalAdmins": "Enabled",
"notifyDcAdmins": "Enabled",
"additionalRecipients": []
}
}
Other parameters are all strings and I can pass them without any issue, for example:
"apiVersion" = "2017-06-01"
I have tried passing this object to parameters like below :
"notificationSettings" = [{
"notifyGlobalAdmins" = "Enabled"
"notifyDcAdmins" ="Enabled"
"additionalRecipients" = []
}]
However, when I execute terrafrom apply, terrafrom complains and say:
Inappropriate value for attribute "parameters": element
"notificationSettings": string required.
How do I pass parameters type of Object to template body?
I have also tried giving the entire ARM json parameter as a file to terrafrom by using parameters_body option like below :
parameters_body = "${file("${path.module}/temp/params.json")}"
however, I am getting the followig error when executing the terrafrom script:
The request content was invalid and could not be deserialized: 'Error
converting value
"https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#"
to type
'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Data.Definitions.DeploymentParameterDefinition'.
Path 'properties.parameters.$schema', line 1, position 2952.'.
Below is the params.json file:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"apiVersion": {
"value": "2017-06-01"
},
"sku": {
"value": "Standard"
"location": {
"value": "westus"
},
"notificationSettings": {
"value": {
"notifyGlobalAdmins": "Enabled",
"notifyDcAdmins": "Enabled",
"additionalRecipients": []
}
},
"subnetName": {
"value": "xxxx"
},
"vnetName": {
"value": "xxxx"
},
"vnetAddressPrefixes": {
"value": [
"10.0.1.0/24"
]
},
"subnetAddressPrefix": {
"value": "10.0.1.0/24"
},
"nsgName": {
"value": "xxxxx"
}
}
}
There is a way to pass arbitrary data structures from Terraform to ARM.
There are two ways to pass data to the ARM template within the azure_template_deployment provider
use the parameters block, which is limited to string parameters only
use the parameters_body block, which is pretty much arbitrary JSON.
I find the easiest way to use the parameters block is to create a local variable with the structure I require, then call jsonencode on it. I also like to keep the ARM template in a separate file and pull it in via a file() call, reducing the complexity of the terraform.
locals {
location = "string"
members = [
"array",
"of",
"members"
]
enabled = true
tags = {
"key" = "value",
"simple" = "store"
}
# this is the format required by ARM templates
parameters_body = {
location = {
value = "${local.location}"
},
properties = {
value = {
users = {
members = "${local.members}"
}
boolparameter = "${local.enabled}"
}
}
tags = {
value = "${module.global.tags}"
}
}
}
resource "azurerm_template_deployment" "sample" {
name = "sample"
resource_group_name = "rg"
deployment_mode = "Incremental"
template_body = "${file("${path.module}/arm/sample_arm.json")}"
parameter_body = "${jsonencode(local.parameters_body)}"
}
The only caveat I've found is that the bool parameters pass as a string, so declare them as a string in the ARM parameters section, then use a ARM function to convert to bool
"parameters: {
"boolParameter": {
"type": "string"
}
},
"variables": {
"boolVariable": "[bool(parameters('boolParameter'))]"
},
"resources": [
...
"boolArm": "[variables('boolVariable')]",
...
]
I can't seem to find any way to use the value of a parameter as part of another parameter in a parameter file for ARM templates:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"someCustomParam": {
"value": "desired value"
},
"tags": {
"value": {
"tag1": "[parameters('someCustomParam')]",
"tag2": "some tag value"
}
},
}
}
Notice how I want to use the value of a previous parameter for the value of another.
The value for "tag1" is simply the string and the value does not get substituted in from the parameter() function. I've tested this by using the Test-AzResourceGroupDeployment PowerShell cmdlet.
Is there any way I can do this?
You can do this using PowerShell. Before calling Test-AzResourceGroupDeployment you can get content of parameter file in PowerShell variable/object using
$ParameterObject = Get-Content ./ParameterFileName.json
Update required value like:
$ParameterObject.parameters.tags.value.tag1 = #Value to assign
Pass $parameterObject to -TemplateParameterObject parameter of Test-AzResourceGroupDeployment
------OR------
Convert $parameterObject to JSON using ConvertTo-Json and Save as temp.json and pass temp.json to -TemplateParameterFile [reference below]
$TempParameterFile = ( $ParametersObject | ConvertTo-Json -Depth 20 ) -replace "\\u0027", "'" -replace "\\u0026", "&" | Out-File $tmp -Force
and use $TempParameterFile for -TemplateParameterFile
You need to use variables.
In variables, you can use "[parameters('parameterName')]".
And you can use variables in a similar way as parameters : "[variables('variableName')]"
Update:
Here is a sample:
"parameters": {
"someCustomParam": {
"type": "string"
}
},
"variables": {
"tags": {
"tag1": "[parameters('someCustomParam')]",
"tag2": "some tag value"
}
}
And then you can use the variable in the resource of your template.
You can do this just like what you normally do when you reference a parameter, an example as following:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"paramOne": {
"value": "hello"
},
"paramTwo": {
"value": "[concat(parameters('paramOne'), '-', 'world)]"
}
}
}
the output value of 'paramTwo' will be 'hello-world'.
Hope this helps whoever wants to utilize referencing a parameter in a parameter in a parameter file.
Does the ARM template offers a way to define a JSON object inline, as a template function parameter?
Something that could look like this, where I am mixing references, Azure template functions and JSON object.
"value": "[concat(reference('ArrayMaker').outputs.fooBarArray.value],
[{ "cat": "Tom", "mouse" : "Jerry"}, { "cat":"Garfield", "mouse":"[reference('MouseTrap').outputs.mouseTrap.value]"} ] )]"
Using variables would seem to be a natural fit for this, but since the value is constructed from a reference, variables can't be used.
well, not natively. you can hack around with nested deployments, something like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"garfiled": {
"type": "string"
},
"catData": {
"type": "object",
"defaultValue": {
"cat": "Tom"
}
}
},
"variables": {
"cat": { <<< if you can\need to construct whole variable in the nested template
"cat": "Garfield",
"mouse": "[parameters('garfiled')]"
},
"t&j": { <<< if you need to pass in part of the variable to the nested template, you can also create another variable to create an object of a proper structure to union with existing object
"mouse": "Jerry"
}
},
"resources": [],
"outputs": {
"garfiled": {
"type": "object",
"value": "[variables('cat')]"
},
"t&j": {
"type": "object",
"value": "[union(variables('t&j'), parameters('catData'))]"
}
}
}
You would then use nested template to pass reference to this template and output the result.