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

Support for functions #447

Closed
slavizh opened this issue Sep 4, 2020 · 21 comments · Fixed by #10465
Closed

Support for functions #447

slavizh opened this issue Sep 4, 2020 · 21 comments · Fixed by #10465
Labels
enhancement New feature or request intermediate language Related to the intermediate language

Comments

@slavizh
Copy link
Contributor

slavizh commented Sep 4, 2020

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.

@slavizh slavizh added the enhancement New feature or request label Sep 4, 2020
@ghost ghost added the Needs: Triage 🔍 label Sep 4, 2020
@alex-frankel alex-frankel added intermediate language Related to the intermediate language and removed Needs: Triage 🔍 labels Sep 9, 2020
@alex-frankel
Copy link
Collaborator

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.

@alex-frankel alex-frankel added this to the Committed Backlog milestone Oct 7, 2020
@aelij
Copy link
Member

aelij commented Mar 7, 2021

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
Copy link
Contributor

stan-sz commented Mar 8, 2021

Referencing #2

@anthony-c-martin
Copy link
Member

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
Copy link
Member

aelij commented Mar 8, 2021

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
Copy link
Collaborator

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
Copy link

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
Copy link
Collaborator

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
Copy link

@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
Copy link
Collaborator

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
Copy link

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
Copy link

use case:

  • I have a Bicep template setting up a VNET.
  • All the subnets have different Network Security Groups.
  • But some of the securityRules are very similar and they are verbose to define.
  • I'd like to make functions that produce JSON objects for the securityRules array.
  • The functions would parameterize what's unique about that rule (e.g. which destination port is allowed) and save me repeating all the rest.

@yoHasse
Copy link

yoHasse commented Oct 18, 2021

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
Copy link
Member

chgeuer commented Mar 30, 2022

@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
Copy link

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
Copy link
Member

@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
Copy link

Is the team still looking into this?

@anthony-c-martin
Copy link
Member

@SimonWahlin were you still interested in working on this?

@SimonWahlin
Copy link
Collaborator

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
Copy link
Collaborator

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

@zachbugay
Copy link

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.

anthony-c-martin added a commit that referenced this issue May 1, 2023
See
https://github.com/Azure/bicep/blob/ant/exp/func/src/Bicep.Core.Samples/Files/Functions_LF/main.bicep
for a syntax example.

Notes/limitations:
* UDFs in JSON require declaring the return type, so I've modified the
spec to require the user to declare the return type.
* User-defined types in UDF params & outputs are unsupported.
* UDFs are currently quite limited - they can't call UDFs, or access
outer scoped variables.
* This is currently behind an experimental feature flag
("userDefinedFunctions")

Closes #447
Closes #9239

###### Microsoft Reviewers: [Open in
CodeFlow](https://portal.fabricbot.ms/api/codeflow?pullrequest=https://github.com/Azure/bicep/pull/10465)
@ghost ghost locked as resolved and limited conversation to collaborators Jun 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request intermediate language Related to the intermediate language
Projects
None yet
Development

Successfully merging a pull request may close this issue.