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

loadTextContent() - support variable or parameter as input #3816

Open
slavizh opened this issue Jul 29, 2021 · 52 comments
Open

loadTextContent() - support variable or parameter as input #3816

slavizh opened this issue Jul 29, 2021 · 52 comments
Labels
enhancement New feature or request Needs: Upvote This issue requires more votes to be considered

Comments

@slavizh
Copy link
Contributor

slavizh commented Jul 29, 2021

Is your feature request related to a problem? Please describe.

I would like for loadTextContent() to support variable or parameter as input parameter. What I would like to offer is for the end user to specify text from file or to use the default file available. Currently if I use the below syntax I get error:
The value must be a compile-time constant.bicep(BCP032)

I know that function is being used at compile time so this will probably require some other approach and even changes in template spec for supporting it there as well.

var alertRules = {
  memoryLow: {
    description: empty(bastionHostMonitoring.alertRules.memoryLow.descriptionFilePath) ? loadTextContent('alert-descriptions/memory-low.txt') : loadTextContent(bastionHostMonitoring.alertRules.memoryLow.descriptionFilePath)
  }
}
@slavizh slavizh added the enhancement New feature or request label Jul 29, 2021
@ghost ghost added the Needs: Triage 🔍 label Jul 29, 2021
@alex-frankel
Copy link
Collaborator

We cannot do this with a param since we won't know the value at compile time. In other words, if you were to run bicep build with a param argument, we'd have no idea which file to "load".

In theory, this should be possible with variables, but my understanding is it requires more of #444 implemented

@mblant
Copy link

mblant commented Dec 16, 2021

Very interested in this thread.

The ability to load another file inside a var would open a load of options around not hardcoded 10s or 100s are variables to reference existing file to load data

var manifest = json(loadTextContent('./data/manifest.json'))

var itemsArray = [ for item in manifest : {
  data: json(loadTextContent('./data/${item.dataFile}'))
  parameters: json(loadTextContent('./data/${item.parametersFile}'))
}

In theory, this should all be available at compile time as all files are static and nothing is dynamically generated.
The manifest object is be generated. So, the itemsArray should be compiled off manifest as well at compile time. Depending on how the complier interrupts order of operation, right?

@alex-frankel
Copy link
Collaborator

#3607 seems related to this @mblant, but it is not the same ask

@mblant
Copy link

mblant commented Jan 5, 2022

I removed the string interpellation and just put the file path in the manifest file and it worked. So, I then assumed it is the interpellation causing the compile time error.

But then, after three or four runs in my test environment, it’s back to the compile time error “The value must be a compile-time constant. bicep(BCP032)”
Not sure what is up in the compiler that it let it run several times (and it actually worked!!) only to go back to throwing the error with no changes?
Happy to get some more data, if someone can give a pointer on where to start.

az bicep version
Bicep CLI version 0.4.1124 (66c84c8)

@mblant
Copy link

mblant commented Jan 6, 2022

Interesting. No errors if there is only a single element in the manifest.json. Add additional elements and back to failure.

@leechan-bicep
Copy link

Hi, I have the same error when providing more than one file location getting the "The value must be a compile-time constant." error.Please let us know how to fix this issue

var stringArray = [
'./policies/policy_1.json'
]

var policies = [for i in range(0, length(stringArray)): json(loadTextContent(stringArray[i]))]

@alex-frankel
Copy link
Collaborator

Repro for triage:

works:

var stringArray = [
  './policies/policy_1.json'
]
  
var policies = [for i in range(0, length(stringArray)): json(loadTextContent(stringArray[i]))]

doesn't work:

var stringArray = [
  './policies/policy_1.json'
  './policies/policy_2.json'
]
  
var policies = [for i in range(0, length(stringArray)): json(loadTextContent(stringArray[i]))]

@alex-frankel
Copy link
Collaborator

The reason this works is because the type of a single-item string array gets simplified down to a string literal, which the function can handle. It's not something we intentionally designed and in fact we should probably block this scenario. We are going to take a look at validating this better.

@alex-frankel
Copy link
Collaborator

@miqm will break out this last comment and track the fix with a separate issue

@miqm
Copy link
Collaborator

miqm commented Jan 27, 2022

Anyway, back to the original proposal - using parameters as arguments for loadTextContent is not possible. Parameters are evaluated during the deployment (runtime), while file loading in loadTextContent is done during compilation (bicep build command). the azure cli does the compilation on the fly before the actual deployment occurs, hence the impression that it could be done.

The only way I can think of to provide this functionality is to introduce a compile-time parameters (similar to C# symbols - https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#conditional-compilation).
AFAIR there are some other issues that refers to introducing similar functionality (i.e. #3109, #1761). It might be worth to think about it. but IMHO it will not be as fast as you would like :)

@miqm miqm added the Needs: Upvote This issue requires more votes to be considered label Jan 27, 2022
@cpinotossi
Copy link

Hello, would be great if we would support this and make it more general. So it will also support similar functions like loadFileAsBase64.
It would give more flexibility when working with modules.

@miqm
Copy link
Collaborator

miqm commented Feb 7, 2022

Would having function that loads json from file be a solution? Mind, that vars can be runtime values, while load* functions can accept only compile-time values as arguments.

@wsucoug69
Copy link

We have the same requirement\need, we are creating role definitions and looking to pass in the json file containing the permissions.

@garrettsingletary
Copy link

+1 to this. Have the same need for populating permissions from json.

@charotAmine
Copy link

charotAmine commented May 10, 2022

Hello @leechan-bicep , @alex-frankel

A workaround for this :

Hi, I have the same error when providing more than one file location getting the "The value must be a compile-time constant." error.Please let us know how to fix this issue

var stringArray = [ './policies/policy_1.json' ]

var policies = [for i in range(0, length(stringArray)): json(loadTextContent(stringArray[i]))]

May be :
var stringArray = [
loadTextContent('./policy-1.json')
loadTextContent('./policy-2.json')
loadTextContent('./policy-3.json')
]

var policies = [for i in range(0, length(stringArray)): json(stringArray[i])]
Since in anyways, we should put a constant path.

@SeSeicht
Copy link

SeSeicht commented Jun 1, 2022

hey, I tried @charotAmine code, but it didn't worked for me. But I created a similar way to solve this:
@alex-frankel goes in the same direction like your code snippet

var stringArray = [
  json(loadTextContent('./file1.json'))
  json(loadTextContent('./file2json'))
  json(loadTextContent('./file3.json'))
  ]

module m_pol_def 'br/modules:microsoft.authorization.policydefinitions:1.0' = [for onePol in stringArray: { 
  name: onePol.name
    params: {...}
}]

@calebherbison
Copy link

+1 for using this to load local files for openapi specs

@krowlandson
Copy link

@alex-frankel

I have a similar situation which appears to be a cross between this issue and #3607.

In my case, I am looking to load multiple "resource configuration" files. These files may vary from deployment to deployment but will be re-usable. Depending on the specific scenario, I would like to point my Bicep configuration at a single JSON file containing a list of required "resource configuration" files, and then loop over that to load in the "resource configuration" files.

I will always know the location of the first file, and the point of this is to abstract the list of other sources from the template to simplify code maintenance and D.R.Y.

Take an example such as the following:

configLocations.Scenario1.json example

[
    "config/resource1settings.json",
    "config/resource2settings.json",
    "config/resource3settings.json",
    "config/resource4settings.json",
    "config/resource5settings.json",
    "config/resource6settings.json"
]

config/resource1settings.json example

{
  "setting1": "value",
  "setting2": 100,
  "setting3": true,
  "setting4": []
}

sample.bicep example

// The following var is used to load the list of resource configs to import
var listResourceConfigs = loadJsonContent('configLocations.Scenario1.json')

// The following var is used to load the resource configs
var resourceConfigs = [for config in listResourceConfigs: loadJsonContent(config)]

This results in the error The value must be a compile-time constant.bicep(BCP032):

Bicep error: The value must be a compile-time constant.bicep(BCP032)

As such, it would be really useful to have a way to do the following for compile-time operations:

  1. Declare constants (only visible in Bicep, maybe using a decorator format such as @constant(key='value'))
  2. Support for loops (again, possibly using a different format to declare the loops so they are explicitly identified as used at compile-time)

In my scenario, explicitly declaring the list of "resource configuration" files into the bicep file as proposed in the above "work-arounds" would defeat the point of this abstraction.

Thank you

@akhilthomas011
Copy link

@alex-frankel +1. This is a frequent use case for us.

@Kaloszer
Copy link

+1

3 similar comments
@sofia-valles
Copy link

+1

@Gabriel123N
Copy link

+1

@tuomaskujala
Copy link

+1

@asos-gurpreetsingh
Copy link

+100 from my company

@brwilkinson
Copy link
Collaborator

Adding comment for support.

  • Currently a custom PowerShell deployment Script is required to pass in metadata for deployment.
    • I would like to remove the requirement for this custom deploy script.

I currently pull in 3 files with Global or Regional metadata.

  1. Global metadata
  2. Regional metadata
  3. Custom role definition lookups
  • E.g. For 3, exported lists for role assignments for a specific subscription, to do friendly name lookups for role assignments. This is required because different custom role assignments are available in different subscriptions.
    # Read in the Rolegroups Lookup.
    $RolesGroupsLookup = Get-Content -Path $Artifacts/tenants/$App/Global-Config.json | ConvertFrom-Json -Depth 10 | ForEach-Object RolesGroupsLookup
    $Global.Add('RolesGroupsLookup', ($RolesGroupsLookup | ConvertTo-Json -Compress -Depth 10))

I would like to move this into the Bicep template file itself.

param App string = 'LAB'
var computeGlobal = json(loadTextContent('../tenants/${App}/Global-Config.json'))

Since the project can deploy to multiple subscriptions, I need to pass in the App to differentiate which Role lookup file to use,

The value must be a compile-time constant.

  • Since this is unsupported, I still depend on the custom deployment PowerShell script.

The deployment script that I currently use for ALL deployments.

Examples of the metadata files that I have-to pass in at runtime (per tenant/App) in the above script.

@UmairSyed
Copy link

+1 please

@Kielar-Peter
Copy link

+1

@AshutoshNirkhe
Copy link

This will be really a value addition!

@jamesSampica
Copy link

jamesSampica commented Jun 7, 2023

This is something Terraform supports so Bicep should too. It is not possible to download and deploy a certificate resource unless the path is known at deploy time. Only workaround is using a keyvault instead.

Terraform:

resource "azurerm_app_service_certificate" "webapp_certificate" {
    ...
    pfx_blob = filebase64(var.certificate_path) // Works
}

Bicep:

resource certificate 'Microsoft.Web/certificates@2022-09-01' = {
    ...
    properties: {
        pfx_blob: loadFileAsBase64(certificate_path) // Compile error
    }
}

@brwilkinson
Copy link
Collaborator

An additional comment on this... If you test out bicepparam you can create a parameter that uses loadtextcontent() or other loadX() to read the file. By having this in the parameter file instead of the template, you get some degree in flexibility to read different files based on the parameter file you are using.

@jamesSampica
Copy link

Good to know. I dont think a parameter file will work in my case because I want to download a secure file from Azure DevOps and read the contents in.

@brwilkinson
Copy link
Collaborator

brwilkinson commented Jun 21, 2023

Still exploring bicepparam

I like that you can reference other parameters in bicepparam file. E.g. Prefix below.

using '../../bicep/00-ALL-SUB.bicep'

param Global = union(
  loadJsonContent('Global-${Prefix}.json'), // Can reference this file based on the region I am in... clone the file, update the Prefix
  loadJsonContent('Global-Global.json'),
  loadJsonContent('Global-Config.json')
  )

param Prefix = 'AWU3'
param Environment = 'P'
param DeploymentID = '0'

I haven't completed testing all scenarios, however I believe this covers my needs from my previous comment in this thread, where I achieved the above union with PowerShell custom code.

@sohirani
Copy link

+1

@gscorrea
Copy link

gscorrea commented Jul 8, 2023

+1
I am in the middle of defining variables that need to ready yaml/json/text contents from files to build AOSM NFDV for a bicep publisher file.
The thing is that besides these variables the bicep publisher is common to several of my NFs to be published, so I do not want to have to make copies of same files several times to build the NFDV for my NFs, but I want to use params/variables to build the load(Yaml/Json/Text)Content filePath and use same bicep to publish my NFs NFDVs.
Commented out below is how I tried to implement with params on the filePath and did not work, but this is needed.
I understand that at this time the bicep build process happens before the params are read, but I have a need to have the params available at bicep build time.

param aksClusterGroup string
param aksClusterName string
param aosmParametersLocation string
param aosmSchemaLocation string
param aosmSetSetFilesParametersLocation string
param artifactManifestName string
param artifactStore string
param cgsName string
param cgvName string
param cnfName string
param helmPackageName string
param helmPackageVersion string
param helmReleaseName string
param location string = resourceGroup().location
param nfdgroupName string
param nfdvNfName string
param nfdversion string
param nsdGroup string
param nsdvName string
param publisherName string
param releaseNamespace string
param resourceExists bool = false
param siteName string
param snsName string
param valuesLocation string
param valuesParam object
param yamlLocation string
//********************************************
//LOADING ALL VALUES ON allTogether
//********************************************
//var mval = loadYamlContent(valuesLocation'/'cnfName'-values.yaml') //parameters passed to helm chart
//var svals = loadJsonContent(aosmSetSetFilesParametersLocation'/'cnfName'_nf_deployment_set_parameters.json') //file with --set parameters for helm
var mval = loadYamlContent('../values.final/my_cnf-values.yaml') //parameters passed to helm chart
var svals = loadJsonContent('../aosmSetSetFilesParameters.final/my_cnf_nf_deployment_set_parameters.json') //file with --set parameters for helm
var allTogether= union(mval, svals) //joining parameters. change order of list to use correct precedence.

@Afsalmc
Copy link

Afsalmc commented Jul 18, 2023

I am facing the same issue while loading a script for deploymentscript bicep module.

@obriankevin11
Copy link

same here

@lesca
Copy link

lesca commented Nov 7, 2023

+1

4 similar comments
@phrueegsegger
Copy link

+1

@hybridtechie
Copy link

+1

@Saulopv
Copy link

Saulopv commented Mar 26, 2024

+1

@SvenRelijveld1995
Copy link

+1

@dewolfs
Copy link
Contributor

dewolfs commented Apr 5, 2024

Running into the error

Error BCP032: The value must be a compile-time constant.

when trying to import an API into Azure API Management based on the OpenAPI specification.
Our API is private so we need to use format openapi+json.

  • openapi+json: The contents are inline and Content Type is a OpenAPI 3.0 JSON Document.
  • openapi-link: The OpenAPI 3.0 YAML document is hosted on a publicly accessible internet address.

Ref: https://learn.microsoft.com/en-us/javascript/api/@azure/arm-apimanagement/contentformat?view=azure-node-latest

param openApiSpecJSON string

resource apis 'Microsoft.ApiManagement/service/apis@2022-08-01' = {
    ...
    description: api.apiDescription
    format: 'openapi+json'
    value: loadTextContent('${openApiSpecJSON}')
  }
}

@nayanshah
Copy link

nayanshah commented Apr 24, 2024

Repro for triage:

works:

var stringArray = [
  './policies/policy_1.json'
]
  
var policies = [for i in range(0, length(stringArray)): json(loadTextContent(stringArray[i]))]

doesn't work:

var stringArray = [
  './policies/policy_1.json'
  './policies/policy_2.json'
]
  
var policies = [for i in range(0, length(stringArray)): json(loadTextContent(stringArray[i]))]

The currently supported way for making this work involves duplicating loadTextContent calls which gets annoying if additional logic is needed like in #10112. This violates Goal-4 i.e. "Code re-use should not require any 'copy/paste'-ing".

P.S: my workaround for this issue is generating a .bicep file before compilation which isn't ideal but gets the job done - Example: https://gist.github.com/nayanshah/7c86cfe6c9882c9e8133cee6ce17c9bb

@woeterman94
Copy link

Any update on this?
I'd love to be able to do:
value: loadTextContent('policies/${environmentName}.xml')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Needs: Upvote This issue requires more votes to be considered
Projects
None yet
Development

No branches or pull requests