Azure / bicep

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

Support for functions #447

Closed slavizh closed 1 year ago

slavizh commented 4 years ago

Is your feature request related to a problem? Please describe. In ARM Templates we have the notion of functions. Will that be supported in Bicep. I think where it can be useful in Bicep is that you can define the function once and you can use it across multiple Bicep files. Other thing is that it might simplify some complex code. Of course the function define in Bicep does not necessary need to become function in ARM Template. It could be you just convert the code of the function directly to where it is used. The function could also simplify the way they are written compared to ARM where you have to define namespace and members and your logic is output.

alex-frankel commented 4 years ago

We definitely want to support user-defined functions. We think they should translate into ARM Template functions so there is parity in the IL, but user-defined functions have a lot of limitations in ARM templates, so we would need to improve that side as well.

aelij commented 3 years ago

Can't Bicep just inline the functions in the compiled JSON? ARM could take a while to make the necessary changes. And functions bring a LOT of value.

stan-sz commented 3 years ago

Referencing #2

anthony-c-martin commented 3 years ago

Can't Bicep just inline the functions in the compiled JSON? ARM could take a while to make the necessary changes. And functions bring a LOT of value.

Technically, yes. However I'd be worried that the repetition of inlining more complex logic would lead to the generated JSON templates getting unexpectedly large and reaching service-side size limitations much faster.

There's also the fact that fixing limitations in the ARM JSON benefits non-Bicep users and Bicep users alike, so it would be optimal to prioritize fixes there.

@alex-frankel it would be helpful to have a list of the relevant limitations documented, do you know if we've got anything available?

aelij commented 3 years ago

reaching service-side size limitations

The Bicep compiler/lang service can produce an error if this happens.

benefits non-Bicep users and Bicep users alike

True, but the ARM function spec is currently very limited. And my guess is that Bicep's update cadence will be far greater, as it doesn't have much of back-compat to worry about. When it's available in ARM, then it could be optimized.

Perhaps Bicep could introduce a new concept - macros, that will only run during compilation, as opposed to functions that will be compiled into ARM functions.

alex-frankel commented 3 years ago

True, but the ARM function spec is currently very limited.

I think if we support user-defined functions (UDF) we'd like to improve the current limitations of ARM Template UDFs and then expose those in bicep, otherwise the complexity gets added to the bicep compiler which is hard to manage.

Limitations are here

image

ggirard07 commented 3 years ago

Just getting into bicep and kind of surprised this is not available yet, as feature parity was announced for 0.3 😞 I understand the plan here is to accomplish much more than ARM user functions, but you should revise the feature parity and document the limitation. Especially for people like us jumping in bicep due to this precise announcement.

alex-frankel commented 3 years ago

We'll document this as a known limitation. Out of curiosity, what are you using user-defined functions for today? Would it be possible to accomplish similar functionality with variables and/or modules?

ggirard07 commented 3 years ago

@alex-frankel One of the main purpose we use those is to share some basic common logic we want to avoid duplicating in the templates, especially around formatting strings with concat() (like resource name based on a standard format, customizing connection strings, ...). I understand string interpolation now helps for this, but in this scenario we would have to duplicate the resource format over everywhere instead of centralizing it a single a user function.

alex-frankel commented 3 years ago

I'd be curious how far you could get with the "hack" of using a module in the meantime. Definitely not elegant, but you could ensure you are not rewriting the same code in multiple places.

function.bicep:

param inputValue

var standardSuffix

var newInputValue = '${inputValue}${standardSuffix}'
var makeLowercase = toLower(newInputValue)

output calculatedResourceName string = makeLowerCase

main.bicep:

module functionHack 'function.bicep' = {
  name: 'functionHackDeploy'
  params: {
    inputValue: 'foobar'
  }
}

var outputFromFunciton = functionHack.outputs.calculatedResourceName

Note this will not work well for resource naming since resource names must be known at compile time. Consuming the output of a module prevents that from happening.

ggirard07 commented 3 years ago

For the general purpose of avoiding duplicating some really complex logic, it could... But from the point of view of simplifying the code and keep it DRY, not really, especially for such a simple task as formatting a connection string.

Module can only be declared as a "root" resource, not as a sub-resource, right? I will need to duplicate that module import at root level as many times as I will normally just inline the function in the ARM template, regardless of the resource level in that case (every appsettings under a Microsoft.Web/sites and its slots). In that case I would prefer simply just copying over the connection string formatting.

Also module is going to create its own deployment in the deployment history. So definitively something I would use only for complex cases.

ishepherd commented 3 years ago

use case:

yoHasse commented 3 years ago

Additional use case.

We generate our resource-names based on a specific pattern including uniqueString. As per today we use the workaround of having the naming convention in a separate module.

However since you're not allowed to set the name of a resource based on a module output we have to put those resources in another module which breaks the reading flow and the complexity increases a notch.

We would love to use a function like in this official example. Aka. Generate a resource name based on a set of rules.

chgeuer commented 2 years ago

@alex-frankel Would love to simplyfy something like this using a custome function:

output AZURE_METERING_INFRA_CHECKPOINTS_CONTAINER string =  'https://${substring(checkpointContainer.name, 0, indexOf(checkpointContainer.name, '/'))}.blob.${environment().suffixes.storage}/${substring(checkpointContainer.name, lastIndexOf(checkpointContainer.name, '/') + 1)}'
output AZURE_METERING_INFRA_SNAPSHOTS_CONTAINER string = 'https://${substring(snapshotsContainer.name, 0, indexOf(snapshotsContainer.name, '/'))}.blob.${environment().suffixes.storage}/${substring(snapshotsContainer.name, lastIndexOf(snapshotsContainer.name, '/') + 1)}' 
output AZURE_METERING_INFRA_CAPTURE_CONTAINER string = 'https://${substring(captureContainer.name, 0, indexOf(captureContainer.name, '/'))}.blob.${environment().suffixes.storage}/${substring(captureContainer.name, lastIndexOf(captureContainer.name, '/') + 1)}' 
obiwanjacobi commented 2 years ago

I would be satisfied when I can reuse bicep user functions across different bicep files. I am sure some extra features can be added because of the extra bicep->arm processing (like default values for parameters). For now I would rather have something than wait for the ultimate (ARM) fix. Is there an ETA for this feature?

anthony-c-martin commented 2 years ago

@alex-frankel Would love to simplyfy something like this using a custome function:

output AZURE_METERING_INFRA_CHECKPOINTS_CONTAINER string =  'https://${substring(checkpointContainer.name, 0, indexOf(checkpointContainer.name, '/'))}.blob.${environment().suffixes.storage}/${substring(checkpointContainer.name, lastIndexOf(checkpointContainer.name, '/') + 1)}'
output AZURE_METERING_INFRA_SNAPSHOTS_CONTAINER string = 'https://${substring(snapshotsContainer.name, 0, indexOf(snapshotsContainer.name, '/'))}.blob.${environment().suffixes.storage}/${substring(snapshotsContainer.name, lastIndexOf(snapshotsContainer.name, '/') + 1)}' 
output AZURE_METERING_INFRA_CAPTURE_CONTAINER string = 'https://${substring(captureContainer.name, 0, indexOf(captureContainer.name, '/'))}.blob.${environment().suffixes.storage}/${substring(captureContainer.name, lastIndexOf(captureContainer.name, '/') + 1)}' 

FYI, once #7083 has been released, you should be able to do something like:

var containers = map([checkpointContainer, snapshotsContainer, captureContainer],
  c => 'https://${substring(c.name, 0, indexOf(c.name, '/'))}.blob.${environment().suffixes.storage}/${substring(c.name, lastIndexOf(c.name, '/') + 1)}')

output AZURE_METERING_INFRA_CHECKPOINTS_CONTAINER string =  containers[0]
output AZURE_METERING_INFRA_SNAPSHOTS_CONTAINER string = containers[1]
output AZURE_METERING_INFRA_CAPTURE_CONTAINER string = containers[2]
zachbugay commented 1 year ago

Is the team still looking into this?

anthony-c-martin commented 1 year ago

@SimonWahlin were you still interested in working on this?

SimonWahlin commented 1 year ago

I am, just havent found the time. If someone else wants to go for it I dont mind, otherwise I'll try to make time for it asap.

SimonWahlin commented 1 year ago

Here is the proposal I wrote: https://github.com/Azure/bicep/issues/9239 I've just started to work on an implementation

zachbugay commented 1 year ago

Here is the proposal I wrote: #9239 I've just started to work on an implementation

Very excited for this. Thank you very much for working on this! It is something I've been looking forward to for a very long time.