Azure / bicep

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

[Story] Bicep Parameters File #7301

Closed sydkar closed 1 year ago

sydkar commented 2 years ago

First Release: #9567


Bicep Parameter Files

Bicep Parameters has been one of our most requested features. During the summer, the Bicep team will be building a prototype of what this experience looks like, with the intention to ship the MVP described below if we get a positive reaction.

Goals

Non-Goals

Requirements

Summer

Basic Language Features

Syntax
Compilation/CLI
Code Completion and Navigation

Example

using './main.bicep'

param paramString = 'foo'
param paramNum = 1
param paramObject = {
  name: 'VNet1'
  location: 'EastUS'
}

MVP

Basic Language Features

Code Completion and Navigation

String Replacement and Expressions

Environment Variables

Load Functions

Bicep Parameter File Generation

Compatibility with Current Tooling

Future Tasks

These features may require further investigation and we intend to spend more time to provide a detailed spec that reflects their functionality to the fullest extent. We would love to hear further feedback on these items!

Modular Parameters

Setting Deployment Properties in Param Files

getSecret()


Open Questions

wedoazure commented 2 years ago

This was mentioned on the comments in the community call, but just adding my two cents here.

The file name extension for the params file, in my opinion, should remain as .bicep.

Logic should be introduced to a .bicep file to reverse the param file logic of "using", for example "usedby" or similar.

Thus tying the two files together.

I know there was mention of introducing inherited or root structure param files, I presume the approach here would be parent and child, again the same logic of tying files together could be used.

My main point I guess is, I don't see the value in introducing a new file name extension as well as having to specify the file within the code itself.

musaugurlu commented 2 years ago

A parameter file is a great idea. Thank you for working on it. I am just throwing out my two thoughts about it.

  1. The parameters file is not a good location for using './main.bicep'.

    1. To my understanding, we use this line to tell VS Code Extension where to find template params so that it can validate the values in the parameters file. In this case, the validation is the VS Code extension's concern, not the bicep engine's. In my opinion, VS Code extension should ask users where to find the template file when the parameters file is opened if it is not already set.
    2. if the bicep engine also uses this line during the compilation, the engine should get the template file from the command. Users should pass the parameters file along with the template file. i.e. az deployment group create -g ExampleGroup --template-file main.bicep --parameters @dev.bicepparams
  2. The word param at the beginning of each parameter in the parameters file is unnecessary as everything in the parameters file will be the value of a parameter

Example

paramString = 'foo'
paramNum = 1
paramObject = {
  name: 'VNet1'
  location: 'EastUS'
}
sydkar commented 2 years ago

@wedoazure We need to introduce a new extension for the parameter files so the language server knows how to validate the files. For example, when defining parameters in Bicep files, the syntax param paramName type is used, but in parameter files, when you set the parameter value, you use param paramName = value. Without a separate extension, we would be unable to determine which was valid for the context.

The direction of linking the files (pointing the parameters at the Bicep template) was something we discussed thoroughly, and we came to this decision because through our interviews, we recognized that it was more common to have multiple parameter files used for one Bicep file. In the future, with inherited parameter files, we hope to make parameters more reusable.

sydkar commented 2 years ago

@musaugurlu

  1. While we considered using an extension to point at the template parameters, we decided to use an explicit link in order to remind users which Bicep file it is linked to and keep the validation consistent when deploying with the different tools. To address your second point, for the first iteration, we will build the parameters file into a params.json file which will be passed to the command line in the same way it is now. In the future, we intend to be able to deploy just by providing the parameter file, since the two will be linked.
  2. Although seemingly redundant, we kept the param keyword because we intend to introduce variables in the future with the var keyword, similar to those in Bicep files, and to keep the language consistent, we decided to keep the directive.
musaugurlu commented 2 years ago

@sydkar I am unaware of how other tools deploy bicep templates, but using using statement makes sense if there is any intention to deploy with parameters file only. Thank you for explaining it.

But I am curious about the use cases to pass variable values from the parameters file. If you pass values to both parameters and variables from the same file(or from outside of the template), what is the difference between parameters and variables? Other tools such as Terraform and Ansible pass variables from the command line or file because they don’t have parameters.

sydkar commented 2 years ago

@musaugurlu The intent with variables is not to pass them between the Bicep template file and the parameters, it was meant to be used only in the parameter files to enable expressions and functions.

krowlandson commented 2 years ago

I agree with @musaugurlu regarding the exclusion of param from each entry. This feels unnecessary as all entries should be parameter key-value pairs.

As a user of both Bicep and Terraform (and seeing the similarities between the two), it would make sense to keep the param file as simple as possible, similar to the Terraform tfvars files.

As such, whilst I understand the thinking behind supporting vars and functions within the param file, I think this type of logic (beyond loading values from other file sources) should occur in the deployment template instead.

However, I can see the benefit of linking a param file to the associated bicep template with the using 'path/filename.bicep' syntax. Terraform does this by virtue of the way modules are constructed. i.e. all files within a folder are evaluated as a single "deployment". As bicep works at the individual file level, having this (optional) reference makes it easier to have the bicep compiler and extensions work with minimal user input.

Thinking about file naming, rather than using a specific file name, it might work well to use .bicep and then allow a bicep deployment to utilise the presence of the using 'path/filename.bicep' entry to enable deployment using the parameter file only. i.e. bicep will automatically load the bicep template when a valid parameter file is selected for deployment.

Thinking about other use cases for the using 'path/filename.bicep' syntax. It might also be useful to allow multiple param files to be chained together. This would be really useful for multi-environment deployment scenarios as it would allow a developer to specify a parameter file per environment containing values for that specific environment only, which points to a generic parameter file containing "safe defaults" for parameters, which then points to the deployment template.

Thinking about this in the context of a GitOps process, this would allow CODEOWNERS to be used to control who can make changes to parameters for each environment, and for the "safe defaults". If you can provide a way to flag a variable in the "safe defaults" as mandatory, you could then alter the merge behaviour to stop an environment owner overriding a safe default. The only difficulty I can see with this is how to enforce the correct order of dependencies.

For the proposed feature "I can use a VS Code action to generate a Bicep parameter file based off a Bicep template" it would be useful to automatically add all parameters with default values where present. If no default is specified, the type should be specified so the developer knows what is expected.

Great to see this on the backlog though, and thank you for continuing to make these great additions to bicep!

sydkar commented 2 years ago

@krowlandson While we want to keep the parameter files as simple to author as possible, we want to enable users to use variables with the intent that it will help with string replacement in parameter values.

We have also looked into the extension and came to the conclusion that having a separate extension would be better as it would create a clear distinction for the language server to provide syntax validation. We are planning to eventually be able to automatically load the bicep template when the parameter file is deployed, as you described.

Your suggestion on chaining multiple parameter files together is something we are looking into and hoping to implement sometime in the future. We understand the want to be able to link multiple params files for a deployment and think it is an important capability to have.

Finally, to your point on the generated parameter file, I like your suggestions, and they align with the current behavior of ARM parameter generation, so we will be sure to keep these in mind!

dciborow commented 2 years ago

Super excited to see this being worked on. Currently, parameters make my bicep files look TERRIBLE, taking up dozens up dozens of lines at the top of my files. Great start here! Do I understand this correct that I will be able to convert my bicep file to having a seperate file for my parameters? See here for the before https://github.com/Azure/azure-quickstart-templates/blob/master/application-workloads/azure-gamedev/gamedev-vm/main.bicep

After

main.bicep

using main.parameters.bicep

module gamedevvm './nestedtemplates/gamedev-vm.bicep'  = {
  name: 'gamingDevVM'
  params: {
    location: location
    vmSize: vmSize
    adminName: administratorLogin
    adminPass: passwordAdministratorLogin
    osType: osType
    gameEngine: gameEngine
    remoteAccessTechnology: remoteAccessTechnology
    _artifactsLocation: _artifactsLocation
    _artifactsLocationSasToken: _artifactsLocationSasToken
  }
}

main.parameters.bicep

@description('Deployment Location')
param location string = resourceGroup().location

@description('The base URI where artifacts required by this template are located including a trailing \'/\'')
param _artifactsLocation string = deployment().properties.templateLink.uri

@description('The sasToken required to access _artifactsLocation.')
@secure()
param _artifactsLocationSasToken string = ''

@description('Select Game Engine Version')
@allowed([
  'ue_4_27_2'
  'ue_5_0_1'
])
param gameEngine string = 'ue_4_27_2'

@description('Select Operating System')
@allowed([
  'win10'
  'ws2019'
])
param osType string = 'win10'

@description('Select Virtual Machine Skew')
@allowed([
  'Standard_NC4as_T4_v3'
  'Standard_NC8as_T4_v3'
  'Standard_NC16as_T4_v3'
  'Standard_NC64as_T4_v3'
  'Standard_NV6'
  'Standard_NV12'
  'Standard_NV24'
  'Standard_NV12s_v3'
  'Standard_NV24s_v3'
  'Standard_NV48s_v3'
])
param vmSize string = 'Standard_NV12s_v3'

@description('Administrator Login for access')
param administratorLogin string

@description('Administrator Password for access')
@secure()
param passwordAdministratorLogin string

@description('Remote Access technology')
@allowed([
  'RDP'
  'Teradici'
  'Parsec'
])
param remoteAccessTechnology string = 'RDP'
dciborow commented 2 years ago

But... even given the new option for a parameters file, I still find the syntex for parameters to be extremely bloated, because of all the annotations that are allowed. Even if each annotation collapses to a single line, the rate of lines per parameters is still always greater then 1:1. (meaning if I add one parameter, it adds at least 3 lines to my file if I include description and new line)

The bicep formatter already collapses code after the "=". I would be nice to leverage this syntex for parameters too. Particularly with some hopes in the future where parameter types can be more advanced.

Extended

image

Collapsed

image

(the collapse would be even more perfect, if instead of '...' we showed the preview of the text on the single line, as well as collapse that extra '}' in with the rest, but was going to make that its own request)

dciborow commented 2 years ago

I also do not think it is likely that this issue will be solved as first requested, but improving how parameters are defined could resolve it. #4184

mimachniak commented 2 years ago

[Idea]

Don't generate parameter in parameter files if default value is function like,


param par_instance_number string = substring((uniqueString(subscription().id)),0,2)

param par_base_name string = substring((subscription().subscriptionId),0,4)
krowlandson commented 2 years ago

[Idea]

Don't generate parameter in parameter files if default value is function like,

param par_instance_number string = substring((uniqueString(subscription().id)),0,2)

param par_base_name string = substring((subscription().subscriptionId),0,4)

Or create them commented out to simplify use if someone wants to override?

krowlandson commented 2 years ago

Super excited to see this being worked on. Currently, parameters make my bicep files look TERRIBLE, taking up dozens up dozens of lines at the top of my files. Great start here! Do I understand this correct that I will be able to convert my bicep file to having a seperate file for my parameters?

I'm not sure whether this was the true intent of the parameters file option in Bicep, but it raises an interesting point around another value add we could get from Bicep.

Terraform (sorry, not sorry) uses the concept of a "root module" whereby all files within the root module directory are evaluated as a whole when determining what to deploy. This allows developers to break apart code into multiple files. A common pattern is to have a file per resource type, or a file for specific groups of variables, etc. This makes the code easier to maintain for large deployments.

Taking the idea from @dciborow, it would be awesome if Bicep would allow the compilation of a deployment from multiple files. This could enable re-use of code without having to formally make it a "module".

Why might you want to do this? Each Bicep module translates to an ARM "deployment". Each ARM deployment can take a while to start, resulting in unnecessary provisioning delays (especially when no changes are needed). But more importantly, delays in replication across the platform (eventual consistency) can also make it problematic when deploying dependent resources across multiple deployments. This can result in failed deployments despite using a "valid" template, resulting in a poor user experience.

Similar to my idea above of allowing "parameter chaining", making use of the using concept could also allow a primary Bicep template to reference other Bicep templates to include as part of the final ARM template and allow resources "sourced from modules" to be included within a single ARM deployment.

Modules can then be used for more specific scenarios like when a change of deployment scope is needed, or for parallel deployment of batches of the same group of resources, etc.

alex-frankel commented 2 years ago

@krowlandson -- I think the scenario you are describing is covered in #363. Can you confirm?

alex-frankel commented 2 years ago

@mimachniak -- we cannot allow non-deterministic functions in the parameters file because we need to be able to generate a JSON parameters file with only hardcoded, static values. Also, FWIW, can't parameters like this be a variable in the template itself? Do you want the parameter to always be that value or would that depend on who is deploying the bicep file?

krowlandson commented 2 years ago

@krowlandson Kevin Rowlandson FTE -- I think the scenario you are describing is covered in #363. Can you confirm?

Certainly seems so @alex-frankel...

I think the only difference would be that #363 implies that Bicep evaluates "all files in the specified directory" (which is aligned to the Terraform approach) whilst I was suggesting that using could explicitly pull multiple Bicep files into a single deployment template, offering greater flexibility and control over which files to combine (and from where).

Both seem valid scenarios.

anthony-c-martin commented 1 year ago

First Release - Plans

Here are the features we're proposing for the first official release (combination of the "Summer" & "MVP" section - with some MVP pieces cut out). Most of the "Summer" work has already been completed, which is why it's not included here.

Basic Language Features

Code Completion and Navigation

Code Completion and Navigation (Verify these are already done)

Environment Variables

Bicep Parameter File Generation

Compatibility with Current Tooling

Other

akata72 commented 1 year ago

Is bicepparams available for testing with a feature flag in bicepconfig.json?

alex-frankel commented 1 year ago

@stephaniezyen / @Usman0111 -- do we have an easy way to test this out yet? I know at one point it required building the extension from source, but not sure if that is still the case.

Usman0111 commented 1 year ago

@akata72 we will enable the vs code extension to provide intellisense for .bicepparam files in v0.16 release so you should be able to use them with a feature flag set in bicepconfig.json see instructions

brwilkinson commented 1 year ago

< edit > I found this on the TODO list ✅ </ edit >

Any plan for: convert json param file to bicepparam file?

A decompile for json param.

JorgeDios commented 1 year ago

Thank you for all the effort. You are doing a really great job making Bicep truly awesome! I'm using bicep since its very first announcement and it's only been getting better and better.

Creating parameter files in bicep format is certainly an interesting feature which will make the whole bicep approach more consistent. And speaking of consistency, as commented in the past community call:

  <template-name>.json   <->   <template-name>.parameters.json
  <template-name>.bicep  <->   <template-name>.parameters.bicep

I would appreciate if someone could elaborate what the rationale is behind the choice for an additional file extension.

Thank you again and keep the good work!

jikuja commented 1 year ago

I would appreciate if someone could elaborate what the rationale is behind the choice for an additional file extension.

Value replacement is really hard with valid json. Example:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "intValueParam": {
      "value": ${env:something}
    },
    "otherIntValueParam": {
      "value": ${referenceToSomeOtherParameterPassedByCLI}
    }
  }
}

I think this is the most obvious example. Some of the replacement can be done with current tolling using ReplaceTokens task but writing JSON-based parameter file is PITA because authoring tooling complains when supplied values does not match with paramter types and the JSON is not valid.

alex-frankel commented 1 year ago

@JorgeDios -- to put it simply, the rules are different for what is valid bicepparam syntax vs what is valid bicep syntax. For ARM templates, while the template and parameter files both used the .json file extension, they were distinguished by the $schema property being specific to each case. Using a different file extension essentially serves the same purpose without having to clutter the actual file contents.

For those that would prefer everything be a .bicep file extension, can you help us understand what the benefit is? Everything being JSON was helpful in that there are many JSON parsing libraries, but there are no .bicep or .bicepparam parsing libraries other than what we ship with the language service, so it is not obvious to me what the benefit is.

jikuja commented 1 year ago

I would like to see a tooling to extract parameter values from bicepparams at some point. Use case:

JorgeDios commented 1 year ago

@alex-frankel -- Thank you for the clear and concise clarification. I totally understand now why it has been chosen to use a new file extension.

To me, it was a matter of uniformity. The whole purpose of a file extension is to indicate the file format so the parser / system knows how to process it. I was therefore assuming that the bicep format would be enriched to identify what was being declared as parameter input.

However, if I understand you correctly, a different approach is implemented: a bicepparam will be using a similar but different syntax that the one used by bicep (templates). Using a different extension will indicate the system / parser which set of rules to apply to interpret it.

A matter of taste / style, I guess. Or may be a way to reduce complexity of the overall bicep implementation... Anyhow, it is clear to me now, and based on the bicep's team track record, this will be probably the right thing to do.

brwilkinson commented 1 year ago

Does it make sense to include the ability to include the description in the bicepparam file?

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "Prefix": {
      "value": "AEU1",
      "metadata": {
        "description": "region prefix e.g. acu1 or aeu1"
      }
    }
  }
}
@description('region prefix e.g. acu1 or aeu1')
param prefix = 'AEU1'
tomgielow commented 1 year ago

I like what I am reading here, but admittedly still consider myself a neophyte with Bicep. We do use objects as parameters, will this be covered with .bicepparam?

BartDecker commented 1 year ago

Does it make sense to include the ability to include the description in the bicepparam file?

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "Prefix": {
      "value": "AEU1",
      "metadata": {
        "description": "region prefix e.g. acu1 or aeu1"
      }
    }
  }
}
@description('region prefix e.g. acu1 or aeu1')
param prefix = 'AEU1'

I had indeed the same question. We provide our ops teams with several parameter input files which are used in combination with parent modules calling child modules for resource deployment.

It would be very helpful to have the possibility to add descriptions to the parameter files because it will save the ops teams a lot of time. They don't need to go to check either the readme.md of the parent module or the parameter description in the parent module.

The parameter files now are quite hard to read because they consist of quite large sets of parameters. It's not always clear from the parameter name alone what the parameter is about. The ppl filling out the parameter files are usually also not into the details of the workings of the parent modules etc.

chriswue commented 1 year ago

Not sure if this has already been considered but what would be really great is to reference secrets via key vault (similar to Azure Container Apps secrets references pointing to a secret URI in a key vault) and they get pulled out automatically during deployment. Arguably that would require ARM support but that would be the ultimate feature to avoid sprinkling secrets all over the place during deployments and having to take care to not accidentally leak them via logging, temp file artifacts or command line parameter snooping via ps

jikuja commented 1 year ago

Key vault references is already supported by ARM/ JSON paramter files: https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-use-key-vault and by Bicep: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-resource#getsecret

There is a ticket (and PR) to provide getSecret() in .bicepparams context: https://github.com/Azure/bicep/issues/10652

neopsyon commented 10 months ago

Hello, I am facing the following issue: I defined multiple parameters in the Bicep file, and one of the parameters has a validation (can be valueA or valueB). I want to pass a value for this parameter inline (calculate logic with powershell and decide what the value will be during runtime), while I have a parameter file as well (static values). If I define a parameter in the bicepparam file, and give it a blank value, and as well overwrite it inline, I get the error from the bicepparam file, that the value must be either valueA or valueB. If I leave out the parameter from the bicepparam file, validation fails because the parameter is not present. How do I go around this issue? @sydkar @jikuja