Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loop body is evaluated even when array is empty #1754

Closed
nianton opened this issue Mar 6, 2021 · 16 comments
Closed

Loop body is evaluated even when array is empty #1754

nianton opened this issue Mar 6, 2021 · 16 comments
Assignees
Labels
bug Something isn't working intermediate language Related to the intermediate language
Projects
Milestone

Comments

@nianton
Copy link

nianton commented Mar 6, 2021

When an array is used to create multiple resources in loop, there is currently no way to create a condition for this loop, causing the template to fail during deployment when an empty array is passed.

On the following example:

param containerNames array = []

resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = [for containerName in containerNames: {
  name: '${storage.name}/default/${containerName}'  
}]

When containerNames is an empty array, the deployment fails with the error: The language expression property array index '0' is out of bounds.

Would it possible to support something like the following (or even this case to be automatically handled by the compilation):

param containerNames array = []
var createContainers = !empty(containerNames)

resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = if (createContainers) [for containerName in containerNames: {
  name: '${storage.name}/default/${containerName}'  
}]

Update: After looking into the open issues, I think it might be similar or the same with #1667 (not sure whether the condition is meant to be on the loop level -described here, or on iteration level per resource as I interpret #1667)

@nianton nianton added the enhancement New feature or request label Mar 6, 2021
@ghost ghost added the Needs: Triage 🔍 label Mar 6, 2021
@jmshal
Copy link

jmshal commented Mar 7, 2021

I also just ran into this issue. I'm keen to know whether there is currently any workaround. I've done some googling and I have found similar (if not identical) issues related to ARM templates (for obvious reasons), however, it's hard at this stage for me to know whether the workaround used for ARM can somehow be used with Bicep.

And also - thank you, members of Project Bicep, for doing such an amazing job!

@alex-frankel
Copy link
Collaborator

Loops + conditions will be covered by #1667, though there is a separate issue with the deployments runtime where we will "check" the body of the resource even if the condition is false or if the array is empty, which is what you are hitting.

Can you see if something like this works:

param containerNames array = []

resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = [for containerName in containerNames: {
  name: !empty(containerNames)? '${storage.name}/default/${containerName}' : ''
}]

I'm hoping in the next 2-3 months we have the root cause of this issue fixed in the runtime.

@alex-frankel alex-frankel changed the title Support for conditional on loops Loop body is evaluated even when array is empty Mar 8, 2021
@alex-frankel alex-frankel added bug Something isn't working intermediate language Related to the intermediate language and removed Needs: Triage 🔍 enhancement New feature or request labels Mar 8, 2021
@jmshal
Copy link

jmshal commented Mar 8, 2021

I gave that a go, @alex-frankel. Unfortunately it still fails:

InvalidTemplate - Deployment template validation failed: 'The template resource '' of type 'Microsoft.Storage/storageAccounts/blobServices/containers' at line '1' and column '437' is not valid. The name property cannot be null or empty. Please see https://aka.ms/arm-template/#resources for usage details.'.

@miqm
Copy link
Collaborator

miqm commented Mar 10, 2021

@jmshal - I'm facing similar problems and the best solution (for now) is to put the resource loop into a module and place conditional on the module call.

@anthony-c-martin
Copy link
Member

Does it make sense to automatically codegen a conditional check for empty() in the JSON until we have support in the underlying platform for this? This should be relatively simple to do, and forcing users to think about whether or not the array could be empty feels against the spirit of Bicep :)

@bmoore-msft
Copy link
Contributor

A couple thoughts...

  1. it's not just about empty - the name of a resource needs to be valid. So some "valid" placeholder needs to be supplied
param containerNames array = []

resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = [for containerName in containerNames: {
  name: !empty(containerNames)? '${storage.name}/default/${containerName}' : 'placeholder'
}]

See: https://github.com/bmoore-msft/AzureRM-Samples/blob/master/leap-2020/modules/module.ultimate-vm.json#L324 for old school.

The thing I don't know for sure is how valid does the name need to be - i.e. is pre-flight run on the resource if not, we could probably add a guid and that would pass.

Finally, I think there's a fix for this in PR - depending on what fix we chose, we may be able to copy in bicep. One fix we talked about was just slapping in a guid for the name (restating the above), if that works (and we don't run preflight) seems like we could do it in bicep for any conditional or looped resource.

  1. the name isn't the only property that may be checked for validity (e.g. runtime functions are still evaluated) so and empty() check is only a partial fix depending on the resource. So again thinking of preflight and any other processing we do at run-time (or even compile time). For example, I could have another loop in the resource (or condition) that would not be valid, but we could still throw. Not that we shouldn't do a partial solution, but if we want to go after it, let's look at both.

@SenthuranSivananthan
Copy link

SenthuranSivananthan commented Mar 18, 2021

I've tested two working scenarios based on recommendations from @alex-frankel and @bmoore-msft

1. Using Module + Condition

roleAssignContributor.bicep

targetScope = 'subscription'

param subscriptionContributorGroupObjectIds array = []

resource group_roleAssignment_Contributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for groupId in subscriptionContributorGroupObjectIds: {
  name: guid(subscription().id, groupId, 'Contributor')
  scope: subscription()
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
    principalId: groupId
    principalType: 'Group'
  }
}]

main.bicep:

param subscriptionContributorGroupObjectIds array = []

module group_roleAssignment_Contributor 'roleAssignContributor.bicep' = if (!(empty(subscriptionContributorGroupObjectIds))) {
  name: 'roleAssignContributor'
  scope: subscription()
  params: {
    subscriptionContributorGroupObjectIds: subscriptionContributorGroupObjectIds
  }
}

2. Using ternary operator + "placeholder" value

resource group_roleAssignment_Contributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for groupId in subscriptionContributorGroupObjectIds: {
  name: !empty(subscriptionContributorGroupObjectIds) ? guid(subscription().id, groupId, 'Contributor') : 'placeholder'
  scope: subscription()
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
    principalId: groupId
    principalType: 'Group'
  }
}]

@miqm
Copy link
Collaborator

miqm commented Mar 18, 2021

Problem with a const ‘placeholder’ string starts when you’ll have 2 such resources. Then arms unique validation kicks in and you will no be able to deploy.

we’d need to generate random string on compile time.

@alex-frankel alex-frankel modified the milestones: v0.4, v0.5 Jun 3, 2021
@majastrz
Copy link
Member

The fix in the runtime has been checked in and is awaiting deployment. [w26]

@miqm
Copy link
Collaborator

miqm commented Jul 14, 2021

@majastrz - just to confirm - w26 means 26th week of 2021? if so, that's in the past...

@majastrz
Copy link
Member

Yeah it's a number of the week when the build was produced, but it hasn't been deployed yet.

@benc-uk
Copy link

benc-uk commented Jul 19, 2021

Oh glad this is fixed, it's driving me crazy!

@majastrz
Copy link
Member

The fix has been deployed to all public regions btw 🙂.

@majastrz majastrz moved this from Blocked to In Review in 0.5 release Jul 29, 2021
@stephaniezyen stephaniezyen moved this from In Review to Done in 0.5 release Jul 29, 2021
@AlexisHW
Copy link

AlexisHW commented Jan 18, 2022

Please clarify what exactly the fix did - it skips the deployment now if the array is empty or doesn't exist without throwing an error?

@bmoore-msft
Copy link
Contributor

It treats the resources as if it's not deployed (or validated). Similar to what you'd get on condtion == false or if() evaluating to false.

@majastrz
Copy link
Member

Before the fix, we would also try to skip the deployment of the particular resource in the loop if the condition evaluated to false. The issue was that we would evaluate the expressions inside the loop body even when the condition was false. If the name of a resource was set to myNames[i] and condition on the loop was length(myNames) > 0, we would still evaluate myNames[i] even when myNames array was empty and it would fail telling the user that the index i was not valid. With the fix, we no longer evaluate the expressions in those cases.

We still have a similar issue with non-loop condition false resources. We are still working on that one.

@ghost ghost locked as resolved and limited conversation to collaborators May 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working intermediate language Related to the intermediate language
Projects
Development

No branches or pull requests

10 participants