Can one develop non-nested azure arm firewallrules? - azure

This is to find out if something I'm attempting is even possible...
I've seen several examples of developing an entry ARM template (azuredeploy.json) that refs/imports an external child resource template (eg: azuredeploy.sql.server.json) within which is defined a new SQL Server as well as -- within the sql server's resources section -- nested resources, such as a firewallrule.
I've seen one example (https://blogs.msdn.microsoft.com/azuresqldbsupport/2017/01/11/arm-template-to-deploy-server-with-auditing-and-threat-detection-turned-on/) where the entry/parent ARM template (eg: azuredeploy.json) defines the SQL Server, and instead of defining the firewall rules as a nested resources, defines the firewall rules in parallel, using dependsOn to define execution order.
That appears to be a bit more maintainable/less nested than the first approach.
But I'd like to push it further, where the above resources are all defined in external templates: azuredeploy.json invoking azuredeploy.sql.server.json and azuredeploy.sql.server.firewallRules.json
Unfortunately, I've not found a single example of the above approach.
I've tried for most of the afternoon -- but having changed slashes of ids and names to every configuration imaginable, have run over and over again into:
Code=InvalidTemplate
Message=Deployment template validation failed: 'The template resource {resource-name}'
for type {resource-type} has incorrect segment lengths.
So the questions are:
a) any reason it should not be done this way? (I felt it allowed for a more modular set of arm templates, that could be referenced from a flat entry arm file, only requiring a correct set of 'dependsOn' attributes being defined)
b) can the above actually be done?!
c) is there an online example to study of the above approach and understand my approach error?
d) Just in case: when it's giving the error messages regarding the segment lengths...any chance it's getting itself confused, and it's taking into account "Microsoft.Resources/deployments", when it should be taking into account only "Microsoft.Sql/servers" and "Microsoft.Sql/servers/firewallRules"?
Much appreciated if anybody can advance me on this sticking point.

Yes, you should be able to do this, see: https://learn.microsoft.com/en-us/azure/templates/microsoft.sql/servers/firewallrules
The error you posted suggests that you're missing a name segment or type segment - those must match as in the example below.
"type": "Microsoft.Sql/servers/firewallrules",
"apiVersion": "2015-05-01-preview",
"name": "[concat(parameters('serverName'), '/', 'AllowAllWindowsAzureIps')]",
"location": "[resourceGroup().location]",
"properties": {
"endIpAddress": "0.0.0.0",
"startIpAddress": "0.0.0.0"
}
So your name property will be something like
sqlServerResourceName/whateverYouWantToNameTheFirewallRules - and type exactly as in the example above.

Related

Extracting raw (non string) parameter values from terraform using terraform-config-inspect

I'm trying to generate json from terraform modules using terraform-config-inspect (https://github.com/hashicorp/terraform-config-inspect).
Note: Started with terraform-docs but then found what it uses underneath and it's terraform-config-inspect library.
The problem is that I want to go beyond what terraform-config-inspect provides out of box at the moment:
As an example, I want to get the name of aws_ssm_parameter resource.
For example, I have resource like this:
resource "aws_ssm_parameter" "service_security_group_id" {
name = "/${var.deployment}/shared/${var.service_name}/security_group_id"
type = "String"
value = aws_security_group.service.id
overwrite = "true"
tags = var.tags
}
and I would like to extract the value of the name parameter but by default it does not output this parameter. I tried to hack the code by modifying resource schema and other parts but ended up in getting empty string instead of name value or error because it contains parts like ${var.deployment}.
When I set it to plain string then my modified code returns what I expect
"aws_ssm_parameter.service_security_group_id": {
"mode": "managed",
"type": "aws_ssm_parameter",
"name": "service_security_group_id",
"value": "/test-env/shared/my-service/security_group_id",
"provider": {
"name": "aws"
}
}
but in normal case it fails with the following error
{
"severity": "error",
"summary": "Unsuitable value type",
"detail": "Unsuitable value: value must be known",
...
}
I know that I could build something totally custom for my specific use case but I hope there is something that could be re-used :)
So the questions are:
Is it somehow possible to take the real raw value from terraform resource so I could get "/${var.deployment}/shared/${var.service_name}/security_group_id" in json output?
Maybe some other tool out there?
Thanks in advance!
Input Variables in Terraform are a planning option and so to resolve them fully requires creating a Terraform plan. If you are able to create a Terraform plan then you can find the resolved values in the JSON serialization of the plan, using steps like the following:
terraform plan -out=tfplan (optionally include -var=... and -var-file=... if you need to set particular values for those variables.
terraform show -json tfplan to get a JSON representation of the plan.
Alternatively, if you've already applied the configuration you want to analyse then you can get similar information from the JSON representation of the latest state snapshot:
terraform show -json to get a JSON representation of the latest state snapshot.
As you've seen, terraform-config-inspect is only for static analysis of the top-level declarations and so it contains no functionality for evaluating expressions.
In order to properly evaluate expressions here without creating a Terraform plan or reading from a Terraform state snapshot would require reimplementing the Terraform Core runtime, at least to some extent. However, for this particular expression (which only relies on input variable values) you could potentially use the HCL API directly with some hard-coded placeholder values for those variables in order to get a value for that argument, derived from whatever you happen to have set var.deployment and var.service_name to in the hcl.EvalContext you construct yourself.

How to use an Azure Arm Template Output in a variable?

I'm trying to solve a problem I'm having with an Azure ARM template, whereby I need to capture the output of a queried resource in my ARM template, then in that same template, feed that output into a variable / inject that output into a script on another resource that depends on it.
An example -
"outputs":{
"downloadLocation": {
"type": "string",
"value": "[reference(resourceId('randomResource', variables('ResourceName'))).downloadLocation]"
}
}
And
variables: {
"downloadLocation": "[outputs('downloadLocation')]"
}
This variable is then referenced in one of the resources that depends on the source of the queried output.
The downloadLocation can't be formatted in anyway, it contains several signatures and unpredictable strings.
FYI - the below code stops the arm template from being used, by producing the error 'The template function 'outputs' is not valid.'
I'm not locked to storing it as an output, I just need to be able to use that value in another resource - however it's achieved!
The only other route I know would work, but I don't want to explore yet, is that the output could be stored in a file somewhere, then a subsequent script picks it up and injects it into a second ARM template.
If there is a way to use this please let me know, it would help me out significantly!
Thanks
I have found the solution.
The reference function can be called when inside a resource field. So I've had to use reference function as seen above, but it can't be stored in a variable or parameter, it instead needs to be called directly in the resource that's requiring it. Which in this case was the osProfile - customData field.

How do I find Azure ARM template output properties for a resoruce

When writing the outputs part of ARM template how do the do what properties are available for a resource. in the below example for public ip resource how do I find out dnsSettings.fqdn or .ipAddress is available
"outputs": {
"fqdn": {
"value": "[reference(parameters('publicIPAddresses_name')).dnsSettings.fqdn]",
"type": "string"
},
"ipaddress": {
"value": "[reference(parameters('publicIPAddresses_name')).ipAddress]",
"type": "string"
}
}
Your ask is related to Retrieve FQDN of Azure SQL from a linkted template question.
The easiest way to accomplish your requirement is illustrated in below screenshot.
Hope this helps!! Cheers!!
Note: If you think your question has been answered then please 'accept' it, if just helped then click "This answer is useful" and provide an up vote. This can be beneficial to other community members reading this thread.
One way I've found, using only ARM, is to output the whole object:
"outputs": {
"ipaddress": {
"type": "Object",
"value": "[reference(parameters('publicIPAddresses_name'))]"
}
When you apply the policy, the output will show all the possible properties and their values.
You can view the whole data structure in json at https://resources.azure.com.
you dont really know, because some properties are amended by default (and the other answer doesnt mention that at all, which could mislead you badly). One thing you can do is look at the rest api definition of the resource and use Full reference to the resource, that way you will always get what you see in the api definition.
reference(parameters('publicIPAddresses_name'), 'api-version', 'Full')
but, object structure will be different, as far as I remember you'd need to access properties of the object for the most of the output. What I tend to do is - create a template that does nothing but output the existing object I'm interested in and run it and examine the output.
Outputs are almost never needed so it's not that big of an issue in my mind.
Rest Api definitions: https://learn.microsoft.com/en-us/rest/api/azure/

Can I dynamically generate parameters in Azure templates?

AFAIK, all the parameters have to be defined in the parent template right from the start. Is it possible to generate parameters dynamically at all, such as looping n times to generate n name fields?
This shows how parameters are defined in templates. Note that none of the parameters are created dynamically.
You can use parameters that depend on parameters to emulate something like that:
"parameters": {
"first": {
"type": "string",
"defaultValue": "lol"
},
"second": {
"type": "string",
"defaultValue": "[concat('not_so_', parameters('first'))]"
}
}
would give you value of not_so_lol for the first parameter.
You another option is to create variables that take values depending on the parameter:
"parameterOne": "defaultValue": x, - I'm lazy to type out proper definition in json.
...
"option-x": "something"
"option-y": "something-else"
"result": "[variables(concat('option-', parameters('parameterOne')))]"
so this is basically an If statement in ARM template. the value of the result variable equals to "[variables('option-x')]" or "[variables('option-y')]", depending on your input.
Another (a bit more complex option) is to use deployments outputs. So an example would be, you create a deployment filled with different outputs needed by you (basically you create a pool of constants), and after that, you can reference that deployment outputs in all of your templates (given they reside in the same subscription, but you can create that deployment in all of the subscriptions). that would basically create a pool of constants you can get needed value based on the current value.
"something": "[reference(concat('resourceGroupName', 'Microsoft.Resources/deployments/', parameters('deploymentName')),'2015-01-01').outputs]",
The last (most complex) option is to construct needed stuff on the fly, using nested templates. That's a bit too much to get through in an answer, but I'll just say that in this case you need to use nested templates as aggregator\transformator, where you feed values in and get desired output. This is pretty advanced stuff, but worth knowing. This would be a good example (for starters).
According to your description, we can use uniqueString() to achieve generate parameters dynamically. This function is helpful when you need to create a unique name for a resource.
More information about uniqueString, please refer to this link.

Azure ARM Template variable: Get Subscription name property

Is there a way to get the current subscriptions name into a variable?
Something like this:
"variables": {
"Purpose": "[subscription().SubscriptionName]"
},
Visual Studio says "A property of 'subscription' must be one of the following: id, subscriptionId, tenantId." So the above won't work.
I've also found some examples of the "reference" function and tried to use it thus:
"variables": {
"SubName": "[reference('/subscriptions/subscription().subscriptionId','2015-01-01').outputs.name.value]"
},
But when calling the template it errors with:
function 'reference' is not expected at this location
I'm not sure where I should put it and how I get it into a variable.
In PowerShell, I could do this:
(Get-AzureRmSubscription).subscriptionname
For the sack of interest, we have several subscriptions. The subscription name contains a 3 digit "short code" that is used in the naming of resource groups within the given subscription. It serves no purpose other than make it easier to pinpoint what belongs to what. It is part of our naming convention to help admins (who aren't particularly familiar with Azure) easily see what resources are where. I know there are other ways like RBAC etc, but Microsoft's incessant credential cookie capture doesn't lend itself to logging in with different credentials to different subscriptions.
Thanks
W.
At the time of writing this answer (2016 Oct 16) I'm afraid you can't.
According to the docs, this is what subsbscription() returns:
{
"id": "/subscriptions/#####",
"subscriptionId": "#####",
"tenantId": "#####"
}
So you should expect failing on subscription().SubscriptionName.
In the same doc I linked, there is a discussion around this in the comments section, see comment #2694777590 where a Microsoftie say:
Ok - I've added a bug to add the name attribute to the subcription() expression.
this was 2016 June 6, So I guess it's on the way.
As a workaround, you could inject the subscription name from the thing that is executing the template. For example if it's PowerShell you could do
$subName = (Get-AzureRmSubscription).subscriptionname
New-AzureRmResourceGroupDeployment ... -SecscriptionName $subName
Also, I don't know what's the purpose of that string manipulation based on subscription name you talked about, but if is to create subscription scoped unique names, a good practice is to use uniqueString(subscription().subscriptionId) as part of the name.
The subscription() now does have an additional attribute displayName, according to documentation.
The following should work for you:
"variables": {
"Purpose": "[subscription().displayName]"
}
You should be able to use:
subscription().displayName
(I'll get a bug filed on the VS behavior)
I was able to get subscription().subscriptionId to work in a variable my template.
I think this error you are seeing is a result of how Azure reads the template. Based on Microsoft's reference documentation, references cannot be used in variables:
Remarks
The reference function derives its value from a runtime state, and therefore cannot be used in the variables section.
I got the same function 'reference' is not expected at this location until I moved my actual reference call into the body of the template.

Resources