Azure / bicep

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

Template fails because of missing API version when using condition #1631

Open sebader opened 3 years ago

sebader commented 3 years ago

Bicep version Bicep CLI version 0.2.472 (73d4e93e9c)

Describe the bug I have a couple of resources defined with an if-condition. This template is used as a module (not sure if that is important). If the condition solves to false, i.e. the resource would not be deployed, I get the following error on deployment:

{"error":{"code":"MultipleErrorsOccurred","message":"Multiple error occurred: BadRequest,BadRequest. Please see details.","details":[{"code":"InvalidTemplate","message":"Deployment template validation failed: 'The template resource 'Microsoft.ApiManagement/service/teamsloopsgermanywestcentralapim/namedValues/table-url' reference to 'Microsoft.Storage/storageAccounts/stggermanywestcentral4' requires an API version. Please see https://aka.ms/arm-template for usage details.'.","additionalInfo":[{"type":"TemplateViolation","info":{"lineNumber":0,"linePosition":0,"path":""}}]},{"code":"InvalidTemplate","message":"Deployment template 
validation failed: 'The template resource 'Microsoft.ApiManagement/service/teamsloopsfrancecentralapim/namedValues/table-url' reference to 'Microsoft.Storage/storageAccounts/stgfrancecentralac3cef' requires an API version. Please see https://aka.ms/arm-template for usage details.'.","additionalInfo":[{"type":"TemplateViolation","info":{"lineNumber":0,"linePosition":0,"path":""}}]}]}}

To Reproduce


resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01'  = if(useTableStorage) {
  name: take('stg${location}${replace(guid(resourceGroup().name, location, 'stg'), '-', '')}', 22) // build unique storage account name from 'stg', location and guid()
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_ZRS'
    tier: 'Standard'
  }
  properties: {
    supportsHttpsTrafficOnly: true
  }
}

resource table 'Microsoft.Storage/storageAccounts/tableServices/tables@2019-06-01' = if(useTableStorage) {
  name: '${storageAccount.name}/default/${tableName}'
}

resource namedValueTableUrl 'Microsoft.ApiManagement/service/namedValues@2020-06-01-preview' = if(useTableStorage) {
  name: '${apim.name}/table-url'
  properties: {
    displayName: 'table-url'
    value: '${storageAccount.properties.primaryEndpoints.table}${tableName}'
  }
}

Additional context Is this somewhat related to https://github.com/Azure/bicep/issues/1492 ? While I could use a work around for that issue, I don't see one for this new issue.

Let me know if you need more information. Also happy to jump on a call if needed.

The entire template including the ARM template is actually open: https://github.com/sebader/teams-distributor/tree/main/deployment

sebader commented 3 years ago

Looking further into the generated ARM template, adding the API version here actually fixes the validation error - but then I get a deployment error...

From:

"value": "[format('{0}{1}', reference(resourceId('Microsoft.Storage/storageAccounts', take(format('stg{0}{1}', parameters('location'), replace(guid(resourceGroup().name, parameters('location'), 'stg'), '-', '')), 22))).primaryEndpoints.table, variables('tableName'))]"

to

"value": "[format('{0}{1}', reference(resourceId('Microsoft.Storage/storageAccounts', take(format('stg{0}{1}', parameters('location'), replace(guid(resourceGroup().name, parameters('location'), 'stg'), '-', '')), 22)), '2019-06-01').primaryEndpoints.table, variables('tableName'))]"
Deployment failed. Correlation ID: 3524fadb-a2c9-4eaf-a468-ba33a707c056. {
  "status": "Failed",
  "error": {
    "code": "ResourceDeploymentFailure",
    "message": "The resource operation completed with terminal provisioning state 'Failed'.",
    "details": [
      {
        "code": "DeploymentFailed",
        "message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.",
        "details": [
          {
            "code": "NotFound",
            "message": "{\r\n  \"error\": {\r\n    \"code\": \"ParentResourceNotFound\",\r\n    \"message\": \"Can not perform requested operation on nested resource. Parent resource 'stgfrancecentralac3cef' not found.\"\r\n  }\r\n}"
          },
          {
            "code": "NotFound",
            "message": "{\r\n  \"error\": {\r\n    \"code\": \"ResourceNotFound\",\r\n    \"message\": \"The Resource 'Microsoft.Storage/storageAccounts/stgfrancecentralac3cef' under resource group 'teamsloops' was not found. For more details please go to https://aka.ms/ARMResourceNotFoundFix\"\r\n  }\r\n}"
          }
        ]
      }
    ]
  }
}
sebader commented 3 years ago

just tagging @anthony-c-martin and @majastrz to see if you could comment on this. thanks! :)

anthony-c-martin commented 3 years ago

The first example looks like a problem with the ARM deployment engine - it is attempting to evaluate the reference() function inside a resource body which is behind a condition and will not be deployed. I modified your repro slightly to create a shorter example:

param useTableStorage bool = false
var location = resourceGroup().location
var tableName = 'testTable'

resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01'  = if(useTableStorage) {
  name: take('stg${location}${replace(guid(resourceGroup().name, location, 'stg'), '-', '')}', 22) // build unique storage account name from 'stg', location and guid()
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_ZRS'
    tier: 'Standard'
  }
  properties: {
    supportsHttpsTrafficOnly: true
  }
}

resource table 'Microsoft.Storage/storageAccounts/tableServices/tables@2019-06-01' = if(useTableStorage) {
  name: '${storageAccount.name}/default/${tableName}'
  tags: {
    test: storageAccount.properties.primaryEndpoints.table
  }
}

This generates the following JSON:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "useTableStorage": {
      "type": "bool",
      "defaultValue": false
    }
  },
  "functions": [],
  "variables": {
    "location": "[resourceGroup().location]",
    "tableName": "testTable"
  },
  "resources": [
    {
      "condition": "[parameters('useTableStorage')]",
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-06-01",
      "name": "[take(format('stg{0}{1}', variables('location'), replace(guid(resourceGroup().name, variables('location'), 'stg'), '-', '')), 22)]",
      "location": "[variables('location')]",
      "kind": "StorageV2",
      "sku": {
        "name": "Standard_ZRS",
        "tier": "Standard"
      },
      "properties": {
        "supportsHttpsTrafficOnly": true
      }
    },
    {
      "condition": "[parameters('useTableStorage')]",
      "type": "Microsoft.Storage/storageAccounts/tableServices/tables",
      "apiVersion": "2019-06-01",
      "name": "[format('{0}/default/{1}', take(format('stg{0}{1}', variables('location'), replace(guid(resourceGroup().name, variables('location'), 'stg'), '-', '')), 22), variables('tableName'))]",
      "tags": {
        "test": "[reference(resourceId('Microsoft.Storage/storageAccounts', take(format('stg{0}{1}', variables('location'), replace(guid(resourceGroup().name, variables('location'), 'stg'), '-', '')), 22))).primaryEndpoints.table]"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', take(format('stg{0}{1}', variables('location'), replace(guid(resourceGroup().name, variables('location'), 'stg'), '-', '')), 22))]"
      ]
    }
  ],
  "metadata": {
    "_generator": {
      "name": "bicep",
      "version": "0.3.1.62928",
      "templateHash": "17601285248459050236"
    }
  }
}

I know we're tracking some issues with the ways expressions are evaluated inside conditional resources; I'll discuss this with the team and see if it's a known issue. @bmoore-msft FYI.

Trickermand commented 2 years ago

Any progress on the issue? This still happens in version 0.4.1272 I'll be happy to provide example, should it be necessary

alex-frankel commented 2 years ago

@shenglol is this a dup of #6008?

Trickermand commented 2 years ago

@shenglol is this a dup of #6008?

It seems like a duplicate of #6008, and it's fixed in Build #11498 (not official release)

shenglol commented 2 years ago

It's related but not a duplicate. #6008 is a regression for module output references only (we removed API versions from module output references in the latest release. This one is for resource references. I think the same fix that fixes #6008 can be applied here to add API versions for conditional resource references, but it won't fix the deployment error.

itpropro commented 2 years ago

I have this error in multiple customer deployments, what is the expected timeline to fix this compilation error? @alex-frankel

brettpostin commented 2 years ago

This seems to have made its way onto Azure DevOps build agents resulting in broken production pipelines.

alex-frankel commented 2 years ago

Can someone provide some recent repros? Looking through this issue, it's become unclear what the root issue that this thread is describing.

The conditional evaluation bug is loosely tracked with #3990 and #2371, but is more accurately tracked internally here: https://msazure.visualstudio.com/One/_sprints/taskboard/Azure-ARM-Deployments/One/Custom/Azure-ARM/Nickel?workitem=4830897

However, that is not a new issue or regression.

sudivate commented 2 years ago

@alex-frankel you can refer to this repro below

eaidland commented 2 years ago

6009

Release new version so that we don't get the " requires an API version" error

bmoore-msft commented 2 years ago

@sudivate - the failure in your case is a bit different - the apiVersion would make the template pass validation but it will [likely] fail deployment or result in something unexpected in the case where synapse001 is not deployed.

This deployment has a condition on it... https://github.com/Azure/data-product-batch/blob/f639ace2ca46e3b85e3fd381b3cdde4b5ce1b5ea/infra/main.json#L391

This reference to the deployment does not: https://github.com/Azure/data-product-batch/blob/f639ace2ca46e3b85e3fd381b3cdde4b5ce1b5ea/infra/main.json#L2300

So in the case when that synapse001 is not deployed, one of two things will happen - either deployment will fail (because the deployment does not exist) or a reference would be made to an old deployment, not part of this set of deployments. Not sure if that's intended.

@alex-frankel & @shenglol - I'm not sure #6009 is the right fix as I understand it... adding the apiVersion will fix the preflight error but I think it may cause a false sense of security in thinking that your code is correct when it's likely not. Generally in this scenario you should have an if() around the reference, and if that's used preflight will short circuit the "false" side of the statement.

rachit0412 commented 2 years ago

"Deployment template validation failed: 'The template output reference to 'Microsoft.Storage/storageAccounts/remehatestdataprocsa' requires an API version.

I have a very simple module

param storageAccountType string param storageAccountName string param location string param resourceTags object param deployStorageAccount bool

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = if (deployStorageAccount) { name: storageAccountName location: location sku: { name: storageAccountType } kind:'StorageV2' properties: { azureFilesIdentityBasedAuthentication: { directoryServiceOptions: 'None' } encryption: { services: { file: { keyType: 'Account' enabled: true } blob: { keyType: 'Account' enabled: true } table: { enabled: true keyType: 'Account' } queue: { enabled: true keyType: 'Account' } } keySource: 'Microsoft.Storage' } networkAcls: { bypass: 'AzureServices' virtualNetworkRules: [ ] ipRules: [ ] defaultAction: 'Deny' } accessTier: 'Hot' minimumTlsVersion: 'TLS1_2' allowBlobPublicAccess: false isHnsEnabled: true supportsHttpsTrafficOnly: true allowSharedKeyAccess: true } tags: resourceTags }

resource sabackupcontainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-08-01' = if (deployStorageAccount) { name: '${storageAccount.name}/default/backup' }

resource rawcontainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-08-01' = if (deployStorageAccount) { name: '${storageAccount.name}/default/raw' }

resource conformedcontainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-08-01' = if (deployStorageAccount) { name: '${storageAccount.name}/default/conformed' }

resource modeledcontainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-08-01' = if (deployStorageAccount) { name: '${storageAccount.name}/default/modeled' }

resource privateEndPoint_1StorageAccount 'Microsoft.Storage/storageAccounts/privateEndpointConnections@2021-08-01' = if (deployStorageAccount) { name: 'privateEndPoint${storageAccount.name}.utcNow()' parent: storageAccount properties: {} }

output accountURL string = storageAccount.properties.primaryEndpoints.dfs //output storageEndpoint object = storageAccount.properties.primaryEndpoints

rachit0412 commented 2 years ago

when it is enabled it works just fine .. problem is with false. I read this https://github.com/Azure/bicep/commit/fbe7a45ed8c90274902e63551f844a9a47ec94dd but not understand

rachit0412 commented 2 years ago

below is the module

// Storage Account name var fullStorageAccountName = '${toLower(organisationName)}${toLower(environmentType)}${substring(accountName,0,8)}sa'

// module to deploy - storage account module sa 'azurestorageaccount.bicep' = { name: 'storageAccountDeploy' params: { storageAccountName: fullStorageAccountName storageAccountType: storageAccountType location: location resourceTags: resourceTags deployStorageAccount: deployStorageAccount } }

alex-frankel commented 2 years ago

@bmoore-msft -- can you look at #3750 and #3990? Do either of those more completely resolve the issue?

bmoore-msft commented 2 years ago

I think #3990 would fix this - it's what we currently do for as a workaround... the thing I'm not sure about is if there are unwanted consequences to it (can't think of any off the top). Technically, one would be that "I want to reference the deployment and I know it exists even if not deployed in this template". But then the only proper fix for that scenario would be at run-time. Technically I don't think we have a way to author this in bicep if we do #3990 (where as we would do in JSON) but the scenario may be a bit contrived.

3990 should also catch #2371

rachit0412 commented 2 years ago

@alex-frankel thanks https://github.com/Azure/bicep/issues/3750 helped!

bmoore-msft commented 2 years ago

Ok, thinking about this more - this may cover all the scenarios...

Conditional Resource – when the resource that makes the run-time call is conditional, the run-time call can be wrapped in the same condition. It doesn’t matter if this run-time call is for a deployment output or any other resource reference, this scenario can be address by #3990.

The other scenario is a “New vs. Existing” or “Optional Feature” scenario. New/Existing means I want to sometimes reference a new resource and sometimes use an existing one. Also, the resource that makes this reference is unconditional (if it were conditional the fix in #3990 supersedes this). In this case the code that follow the developer’s intent is that the reference() call must have the apiVersion – primarily for the case when the resource already exists and is not being deployed in the current template. This reference() can be unconditional because the reference() call params is the same in either case. Adding the apiVersion in this case is required, but it’s not sufficient to correctly author the scenario. Note also that this scenario is true for any run-time reference/list on any resourceType, not just deployments/modules.

The “optional feature” scenario is when the developer may or may not want to enable a feature (e.g. publicIp, boot diags, replication). In this case the resource with the optional feature (i.e. the run-time function) does not have a condition, it’s always deployed. The optional feature (or property) has the condition. In bicep we’re seeing this implemented via a module in the current template, though this does not have to be the case… it may already exist or be in a template other than the current one. In this “optional feature” case the developer would need to wrap the optional feature property value in the appropriate condition, bicep cannot assert what this is in all cases. If the module (or resource) is in the current template and has a condition, this same condition could be used by the compiler. Adding the apiVersion in this case could be correct or incorrect depending on intent. So I think we have:

Note the “optional feature” scenario may also be combined with New/Existing, for example I want a new PublicIP address an existing one or no publicIp address. In this case the developer would just combine the two techniques.

xInfinitYz commented 10 months ago

Any updates regarding this? I have problems deploying an azure function slot conditionally in Bicep.

axm commented 1 month ago

Facing this myself... any updates?