Azure / bicep

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

Solving BCP178 with "inline" modules #4555

Open mitchdenny opened 3 years ago

mitchdenny commented 3 years ago

Is your feature request related to a problem? Please describe. One of the problems that I was hoping that Bicep would solve more cleanly for me is the need to create a parent dnsZone and a child dnsZone and create the NS delegation records in the parent zone. Here is my Bicep file where I attempt to do this:

param baseDomainName string = 'parent'
param location string = resourceGroup().location

resource parentDomain 'Microsoft.Network/dnsZones@2018-05-01' = {
  name: '${baseDomainName}-domain.io'
  location: location
}

resource childDomain 'Microsoft.Network/dnsZones@2018-05-01' = {
  name: 'child.${baseDomainName}-domain.io'
  location: location
}

resource childDomainDelegation 'Microsoft.Network/dnsZones/NS@2018-05-01' = {
  name: '${baseDomainName}-domain.io/child'
  properties: {
    NSRecords: [for dnsServer in childDomain.properties.nameServers: {
      nsdname: dnsServer
    }]
  }
}

Unfortunately, just like ARM I can't do this in a single file. In the case of Bicep I get a warning for the childDomain.properties.nameServers usage.

Describe the solution you'd like If I could wave a magic wand, ARM would be able to handle this. But assuming this platform limitation is hard to remove I'd like Bicep to do the heavy lifting for me. When I look at the way the DNS Zone extension for the Azure Portal solves this, it calls out to a separate deployment template which takes care of the delegation.

If my magic wand isn't as powerful I would love Bicep to support the concept of a inline modules which if used results in two JSON templates being generated from the build taking care of the references for me. It might look something like this:

param baseDomainName string = 'parent'
param location string = resourceGroup().location

resource parentDomain 'Microsoft.Network/dnsZones@2018-05-01' = {
  name: '${baseDomainName}-domain.io'
  location: location
}

resource childDomain 'Microsoft.Network/dnsZones@2018-05-01' = {
  name: 'child.${baseDomainName}-domain.io'
  location: location
}

module delegateDomains = {
  resource childDomainDelegation 'Microsoft.Network/dnsZones/NS@2018-05-01' = {
    name: '${baseDomainName}-domain.io/child'
    properties: {
      NSRecords: [for dnsServer in childDomain.properties.nameServers: {
        nsdname: dnsServer
      }]
    }
  }
}

I'm not too attached to the syntax, but the idea is that an inline module is just a module where there is no reference to another .bicep file and the parameters and outputs are inferred from what is consumed-from/used-by the enclosing scope.

alex-frankel commented 3 years ago

I would love Bicep to support the concept of a inline modules

If we allowed this, I think you'd have to pass in relevant parameters just like in a multi-file module declaration in order to preserve the say parameter scoping works in bicep. Something like:

module delegateDomains = {
   params: {
     nameServers: childDomain.properties.nameServers
   }
   code: {
     param nameServers array    

     resource childDomainDelegation 'Microsoft.Network/dnsZones/NS@2018-05-01' = {
      name: '${baseDomainName}-domain.io/child'
      properties: {
        NSRecords: [for dnsServer in nameServers: {
          nsdname: dnsServer
        }]
      }
  }
}

With the extra code required, does that still work for you?

mitchdenny commented 3 years ago

I'd probably take anything that allows me to do this in a single Bicep template ;) That said is there a reason why the compiler couldn't figure it out itself? Other than being more verbose, what would this more explicit syntax get me as a user?

anthony-c-martin commented 3 years ago

@alex-frankel - the restriction on deploy-time constants for property loops feels like something we should be able to address in the intermediate language. Wouldn't that be another option for solving this particular issue?

alex-frankel commented 3 years ago

Good point - that would be a better solution

alex-frankel commented 3 years ago

Raised #4666

slavizh commented 3 years ago

+1

slavizh commented 3 years ago

You should be able to do this:

param wafPolicyId string
param associations array

resource symbolicName 'Microsoft.Cdn/profiles/securityPolicies@2020-09-01' = {
  name: 'someName'
  properties: {
    parameters: {
      type: 'WebApplicationFirewall'
      associations: [for (association, i) in associations: {
        domains: [for (domain, i) in association.domains: {
          id: domain.id
        }]
        patternsToMatch: association.patterns
      }]
      wafPolicy: {
        id: wafPolicyId 
      }
    }
  }
}

This will help with not having to create modules that collect and transform. Creating such results in longer deployment times.

//cc @alex-frankel @anthony-c-martin

mitchdenny commented 3 years ago

What would this look like applied to the DNS scenario that I sketched out above?

Nevermind, its covered in the new issue.

TomasMalecek commented 6 days ago

There are multiple places where ARM requires a cycle in the dependencies graph. As the same resource may not be specified multiple times in a single deployment, the workaround is to use multiple deployments, bootstrapping the infrastructure in layers. In such cases, it is very handy if the multiple versions of a single resource can be in the same file.

Specific examples of such scenarios:

In both cases, I was able to suppress the no-deployments-resources warning and use a resource of type Microsoft.Resources/deployments as a poor man's inline module.

But as soon as I start using user-defined types (or likely anything that triggers the use of languageVersion 2.0), I start getting the nested-deployment-template-scoping error for each symbolic name from the outer scope that I use inside the Microsoft.Resources/deployments resource.

ERROR: C:\Path\To\The\main.bicep(584,17) : Error nested-deployment-template-scoping: The symbol "frontDoorName" is declared in the context of the outer deployment and cannot be accessed by expressions within a nested deployment template that uses inner scoping for expression evaluation. [https://aka.ms/bicep/linter/nested-deployment-template-scoping]

I believe the inline modules proposed in this issue could help me with this.