Azure / bicep

Bicep is a declarative language for describing and deploying Azure resources
MIT License
3.25k stars 753 forks source link

Support referencing resources in variables using shorthand name #456

Open Adunaphel opened 4 years ago

Adunaphel commented 4 years ago

I have the following code snippet:

param virtualMachine object
var ipv4NicConfig = {
    name: virtualMachine.network.privateIpConfigurationName
    properties: {
        privateIPAllocationMethod: 'Dynamic'
        privateIPAddressVersion: 'IPv4'
        subnet: resourceId(virtualMachine.network.virtualNetworkResourceGroup, 'Microsoft.Network/subnets', virtualMachine.network.virtualNetworkName, virtualMachine.network.subnetName)
        publicIpAddress: pupbipv4
    }
}

resource pubipv4 'Microsoft.Network/publicIpAddresses@2020-05-01' = {
(....)
}

And the message I get is "The name 'pubipv4' does not exist in the current context." For a lot of our templates, we build properties sections in variables because it allows us to be far more flexible (in this case creating a single stack ipv4 nic or a dual stack ipv4/ipv6 nic depending on config) so being able to access the shorthand resources would be really helpful

alex-frankel commented 4 years ago

I think you are getting that error because of a spelling issue. In the variable I see pupbipv4, but the resource symbolic name is pubipv4.

That being said, I'm not exactly sure what you are trying to accomplish. Can you elaborate a bit more and also show a JSON equivalent of what you do in ARM Templates?

Referencing a resource like this in a variable is actually not allowed in the json. We should block this in bicep.

anthony-c-martin commented 4 years ago

@Adunaphel - here's an example of referencing a publicIp in a nic, which I think shows something similar to what you're looking for:

https://github.com/Azure/bicep/blob/f0fe9029fefce94a8f7d86b7de646fd97e5d7128/docs/examples/101/1vm-2nics-2subnets-1vnet/main.bicep#L126-L139

Adunaphel commented 4 years ago

Indeed you are right, I only now notice the typo.

To answer your question, the thing I want to achieve here is to be able to dynamically set the NIC configuration depending on whether the VM in question needs only ipv4 or dual stack ipv4 and ipv6 networking (leaving out a lot of properties for brevity):

    "variables": {
        "publicIpAddressId": {
            "id": "[resourceId( 'Microsoft.Network/publicIPAddresses', variables( 'publicIpAddressName' ) )]"
        },
        "publicIpv6AddressId": {
            "id": "[resourceId( 'Microsoft.Network/publicIPAddresses', concat( variables( 'publicIpAddressName' ), 'v6' ) )]"
        },
        "ipv4NicConfig": {
            "name": "[parameters( 'virtualMachine' ).network.privateIpConfigurationName]",
            "properties": {
                "privateIPAddressVersion": "IPv4",
                "subnet": {
                    "id": "[resourceId( 'Microsoft.Network/virtualNetworks/subnets', parameters( 'virtualMachine' ).network.virtualNetworkName, parameters( 'virtualMachine' ).network.subnetName )]"
                },
                "publicIpAddress": "[if( parameters( 'virtualMachine' ).network.needsPublicIp, variables( 'publicIpAddressId' ), json( 'null' ) )]",
                "primary": true,
            }
        },
        "ipv6NicConfig": {
            "name": "[parameters( 'virtualMachine' ).network.privateIpv6ConfigurationName]",
            "properties": {
                "privateIPAddressVersion": "IPv6",
                "subnet": {
                    "id": "[resourceId( 'Microsoft.Network/virtualNetworks/subnets', parameters( 'virtualMachine' ).network.virtualNetworkName, parameters( 'virtualMachine' ).network.subnetName )]"
                },
                "publicIpAddress": "[if( parameters( 'virtualMachine' ).network.needsPublicIpv6, variables( 'publicIpv6AddressId' ), json( 'null' ) )]",
            }
        },
        "nicConfig": {
            "singleStack": [
                "[variables( 'ipv4NicConfig' )]"
            ],
            "dualStack": [
                "[variables( 'ipv4NicConfig' )]",
                "[variables( 'ipv6NicConfig' )]"
            ]
        }
    }
    "resources": [
        {
            "condition": "[parameters( 'virtualMachine' ).network.needsPublicIp]",
            "name": "[variables( 'publicIpAddressName' )]",
(....)
        },
        {
            "condition": "[parameters( 'virtualMachine' ).network.needsPublicIpv6]",
            "name": "[concat( variables( 'publicIpAddressName' ), 'v6' )]",
(....)
        },
        {
            "dependsOn": [
                "[resourceId( 'Microsoft.Network/publicIPAddresses', variables( 'publicIpAddressName' ) )]",
                "[resourceId( 'Microsoft.Network/publicIPAddresses', concat( variables( 'publicIpAddressName' ), 'v6' ) )]"
            ],
            "name": "[variables( 'networkInterfaceName' )]",
            "type": "Microsoft.Network/networkInterfaces",
            "apiVersion": "[variables( 'apiVersions' ).networkInterfaces]",
            "location": "[resourceGroup().location]",
            "tags": "[union( parameters( 'virtualMachine' ).tags, parameters( 'tags' ) )]",
            "properties": {
                "ipConfigurations": "[if( parameters( 'virtualMachine' ).network.needsPublicIpv6, variables( 'nicConfig' ).dualStack, variables( 'nicConfig' ).singleStack )]",
                "enableAcceleratedNetworking": "[parameters( 'virtualMachine' ).network.enableAcceleratedNetworking]"
            }
        }
Adunaphel commented 4 years ago

After fixing my typo and building the template, there is still an issue here, because referencing the resources results in a reference() call in the variables, which is not allowed, instead of just a resourceId call to get the resource's ID:

param virtualMachine object
param tags object

var zones = virtualMachine.zone > 0 ? createArray(virtualMachine.zone) : []
var publicIpDnsSettings = {
    domainNameLabel: virtualMachine.network.publicIpDnsName
}
var ipv4nicConfig = {
    name: virtualMachine.network.privateIpConfigurationName
    properties: {
        privateIPAllocationMethod: (empty(virtualMachine.network.privateIpAddress)) ? 'Dynamic': 'Static'
        privateIPAddress: (empty(virtualMachine.network.privateIPAddress)) ? null : virtualMachine.network.privateIpAddress
        privateIPAddressVersion: 'IPv4'
        subnet: resourceId(virtualMachine.network.virtualNetworkResourceGroup, 'Microsoft.Network/subnets', virtualMachine.network.virtualNetworkName, virtualMachine.network.subnetName)
        publicIpAddress: (virtualMachine.network.needsPublicIp) ? pubipv4 : null
    }
}
var ipv6nicConfig = {
    name: virtualMachine.network.privateIpv6ConfigurationName
    properties: {
        privateIPAllocationMethod: (empty(virtualMachine.network.privateIpAddress)) ? 'Dynamic': 'Static'
        privateIPAddress: (empty(virtualMachine.network.privateIPAddress)) ? null : virtualMachine.network.privateIpv6Address
        privateIPAddressVersion: 'IPv6'
        subnet: resourceId(virtualMachine.network.virtualNetworkResourceGroup, 'Microsoft.Network/subnets', virtualMachine.network.virtualNetworkName, virtualMachine.network.subnetName)
        publicIpAddress: (virtualMachine.network.needsPublicIpv6) ? pubipv6 : null
    }
}
var nicConfig = {
    singleStack: [
        ipv4nicConfig
    ]
    dualStack: [
        ipv4nicConfig
        ipv6nicConfig
    ]
}

resource pubipv4 'Microsoft.Network/publicIpAddresses@2020-05-01' = {
    name: '${virtualMachine.name}-pip'
    location: resourceGroup().location
    //zones: virtualMachine.network.enabledPublicIpZone ? zones : null
    tags: union(tags, virtualMachine.tags)
    sku: {
        name: 'Standard'
    }
    properties: {
        publicIpAllocationMethod: virtualMachine.network.publicIpAllocationMethod
        publicIpAddressVersion: 'IPv4'
        idleTimeoutInMinutes: virtualMachine.network.publicIpIdleTimeoutInMinutes
        dnsSettings: (empty(virtualMachine.network.publicIpDnsName)) ? null : publicIpDnsSettings
        publicIpPrefix: {
            id: resourceId(virtualMachine.network.publicIpPrefix.resourceGroup, 'Microsoft.Network/publicIPPrefixes', virtualMachine.network.publicIpPrefix.name)
        }
    }
}

resource pubipv6 'Microsoft.Network/publicIpAddresses@2020-05-01' = {
    name: '${virtualMachine.name}-pipv6'
    location: resourceGroup().location
    //zones: virtualMachine.network.enabledPublicIpZone ? zones : null
    tags: union(tags, virtualMachine.tags)
    sku: {
        name: 'Standard'
    }
    properties: {
        publicIpAllocationMethod: virtualMachine.network.publicIpv6AllocationMethod
        publicIpAddressVersion: 'IPv6'
        idleTimeoutInMinutes: virtualMachine.network.publicIpv6IdleTimeoutInMinutes
        dnsSettings: (empty(virtualMachine.network.publicIpv6DnsName)) ? null : publicIpDnsSettings
        publicIpPrefix: {
            id: resourceId(virtualMachine.network.publicIpv6Prefix.resourceGroup, 'Microsoft.Network/publicIPPrefixes', virtualMachine.network.publicIpv6Prefix.name)
        }
    }
}

resource vmnic 'Microsoft.Network/networkInterfaces@2020-05-01' = {
    name: (empty(virtualMachine.networkInterfaceName)) ? '${virtualMachine.name}-nic' : '${virtualMachine.networkInterfaceName}'
    location: resourceGroup().location
    tags: union(tags, virtualMachine.tags)
    properties: {
        ipConfigurations: (virtualMachine.network.needsPublicIpv6) ? nicConfig.dualStack : nicConfig.singleStack
        enableAcceleratedNetworking: virtualMachine.network.enableAcceleratedNetworking
    }
}
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "virtualMachine": {
      "type": "object"
    },
    "tags": {
      "type": "object"
    }
  },
  "functions": [],
  "variables": {
    "publicIpDnsSettings": {
      "domainNameLabel": "[parameters('virtualMachine').network.publicIpDnsName]"
    },
    "ipv4nicConfig": {
      "name": "[parameters('virtualMachine').network.privateIpConfigurationName]",
      "properties": {
        "privateIPAllocationMethod": "[if(empty(parameters('virtualMachine').network.privateIpAddress), 'Dynamic', 'Static')]",
        "privateIPAddress": "[if(empty(parameters('virtualMachine').network.privateIPAddress), json('null'), parameters('virtualMachine').network.privateIpAddress)]",
        "privateIPAddressVersion": "IPv4",
        "subnet": "[resourceId(parameters('virtualMachine').network.virtualNetworkResourceGroup, 'Microsoft.Network/subnets', parameters('virtualMachine').network.virtualNetworkName, parameters('virtualMachine').network.subnetName)]",
        "publicIpAddress": "[if(parameters('virtualMachine').network.needsPublicIp, reference(resourceId('Microsoft.Network/publicIpAddresses', format('{0}-pip', parameters('virtualMachine').name)), '2020-05-01', 'full'), json('null'))]"
      }
    },
    "ipv6nicConfig": {
      "name": "[parameters('virtualMachine').network.privateIpv6ConfigurationName]",
      "properties": {
        "privateIPAllocationMethod": "[if(empty(parameters('virtualMachine').network.privateIpAddress), 'Dynamic', 'Static')]",
        "privateIPAddress": "[if(empty(parameters('virtualMachine').network.privateIPAddress), json('null'), parameters('virtualMachine').network.privateIpv6Address)]",
        "privateIPAddressVersion": "IPv6",
        "subnet": "[resourceId(parameters('virtualMachine').network.virtualNetworkResourceGroup, 'Microsoft.Network/subnets', parameters('virtualMachine').network.virtualNetworkName, parameters('virtualMachine').network.subnetName)]",
        "publicIpAddress": "[if(parameters('virtualMachine').network.needsPublicIpv6, reference(resourceId('Microsoft.Network/publicIpAddresses', format('{0}-pipv6', parameters('virtualMachine').name)), '2020-05-01', 'full'), json('null'))]"
      }
    },
    "nicConfig": {
      "singleStack": [
        "[variables('ipv4nicConfig')]"
      ],
      "dualStack": [
        "[variables('ipv4nicConfig')]",
        "[variables('ipv6nicConfig')]"
      ]
    }
  },
  "resources": [
    {
      "type": "Microsoft.Network/publicIpAddresses",
      "apiVersion": "2020-05-01",
      "name": "[format('{0}-pip', parameters('virtualMachine').name)]",
      "location": "[resourceGroup().location]",
      "tags": "[union(parameters('tags'), parameters('virtualMachine').tags)]",
      "sku": {
        "name": "Standard"
      },
      "properties": {
        "publicIpAllocationMethod": "[parameters('virtualMachine').network.publicIpAllocationMethod]",
        "publicIpAddressVersion": "IPv4",
        "idleTimeoutInMinutes": "[parameters('virtualMachine').network.publicIpIdleTimeoutInMinutes]",
        "dnsSettings": "[if(empty(parameters('virtualMachine').network.publicIpDnsName), json('null'), variables('publicIpDnsSettings'))]",
        "publicIpPrefix": {
          "id": "[resourceId(parameters('virtualMachine').network.publicIpPrefix.resourceGroup, 'Microsoft.Network/publicIPPrefixes', parameters('virtualMachine').network.publicIpPrefix.name)]"
        }
      }
    },
    {
      "type": "Microsoft.Network/publicIpAddresses",
      "apiVersion": "2020-05-01",
      "name": "[format('{0}-pipv6', parameters('virtualMachine').name)]",
      "location": "[resourceGroup().location]",
      "tags": "[union(parameters('tags'), parameters('virtualMachine').tags)]",
      "sku": {
        "name": "Standard"
      },
      "properties": {
        "publicIpAllocationMethod": "[parameters('virtualMachine').network.publicIpv6AllocationMethod]",
        "publicIpAddressVersion": "IPv6",
        "idleTimeoutInMinutes": "[parameters('virtualMachine').network.publicIpv6IdleTimeoutInMinutes]",
        "dnsSettings": "[if(empty(parameters('virtualMachine').network.publicIpv6DnsName), json('null'), variables('publicIpDnsSettings'))]",
        "publicIpPrefix": {
          "id": "[resourceId(parameters('virtualMachine').network.publicIpv6Prefix.resourceGroup, 'Microsoft.Network/publicIPPrefixes', parameters('virtualMachine').network.publicIpv6Prefix.name)]"
        }
      }
    },
    {
      "type": "Microsoft.Network/networkInterfaces",
      "apiVersion": "2020-05-01",
      "name": "[if(empty(parameters('virtualMachine').networkInterfaceName), format('{0}-nic', parameters('virtualMachine').name), format('{0}', parameters('virtualMachine').networkInterfaceName))]",
      "location": "[resourceGroup().location]",
      "tags": "[union(parameters('tags'), parameters('virtualMachine').tags)]",
      "properties": {
        "ipConfigurations": "[if(parameters('virtualMachine').network.needsPublicIpv6, variables('nicConfig').dualStack, variables('nicConfig').singleStack)]",
        "enableAcceleratedNetworking": "[parameters('virtualMachine').network.enableAcceleratedNetworking]"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Network/publicIpAddresses', format('{0}-pip', parameters('virtualMachine').name))]",
        "[resourceId('Microsoft.Network/publicIpAddresses', format('{0}-pipv6', parameters('virtualMachine').name))]"
      ]
    }
  ],
  "outputs": {}
}
alex-frankel commented 4 years ago

Gotcha - so this is an ARM template limitation. Ideally we fix this there, but optionally we can "handle it" in bicep by removing the variable in the generated JSON and "hardcoding" the values where you would have otherwise used the variable.

anthony-c-martin commented 4 years ago

This is still an issue - minimal repro here.

We correctly inline variables for a reference to myRes.properties, but not a reference to myRes itself.