Azure / bicep

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

Support for defining registry modules and template spec modules as variables #4185

Open slavizh opened 3 years ago

slavizh commented 3 years ago

Is your feature request related to a problem? Please describe. If the path to the registry module or the template spec module is a static value this will make this feature very limited. The reason for that is if you want to have solution that uses this feature and you want the solution to be given to multiple different customers the following problems will be present:

Describe the solution you'd like My proposal is in the syntax is to have some default value for bicep registry /template spec and that value to be mandatory. Currently that is the case but also to have some property which if it is null the default value will be used but if it is not it will use the bicep registry / template spec defined into that parameter. That way you can have input parameter for example param bicepRegistry string = ''. If it is empty you put null into that property and if it is not you can do something like '${bicepRegistry}:${version}' where version is static variable. This would allow to have all the VS code goodies while developing as the module will be fetched from the default value and ability to have flexibility when you develop solution that needs to be used by multiple customers.

majastrz commented 3 years ago

The current plan is to add aliases for specific registries to bicepconfig.json in the early iterations of the registry feature. We are also considering enabling usage of string variables in resource type and module ref strings.

We are not keen on using parameters for this because the value of a parameter is not always known to us. If we make the default value on a parameter mandatory only if it's used in a module ref, then the validity of the param declaration becomes tied to places where it's referenced. That's not a behavior that users would expect from Bicep or other programming languages.

In general, we want the registry mappings to be explicit and deterministic in all cases. We also want to avoid fallback rules because they have historically caused problems in other registries and package manager implementations. (Take a look at @SteveLasker's comments on #3283 - he explains it pretty well over there.)

slavizh commented 3 years ago

Ok. That makes it less usable. Is it alias for the whole string or for parts of it? May be with aliases we could achieve this although will be less ideal experience for end users as we are trying to abstract as much as possible so they can think only about the architecture of the services they deploy with Bicep and not so much on the details of the code.

I guess also depends on how these string variables will work in the resource type. Could be parameter with default value instead of variable? Can it be variable that takes value from parameter?

SteveLasker commented 3 years ago

Defining a location of the registry, as a configuration option, is definitely a space I'm hoping we can solve. Most package managers solve this today, it's really container registries (artifact services) that haven't yet solved this.

Admittedly, I haven't had enough time to research the various options and flush out a great recommendation. I'm still in "this is broken, we need to fix it mode, here are some ideas..."

This is the purpose of: https://github.com/SteveLasker/drafts/blob/main/registry-repo-config.md, to incubate some ideas for folks that have more time to think through the solutions.

I'm hoping to incubate some ideas under https://github.com/oras-project/oras-go, but happy to help here as folks iterate on proposals.

majastrz commented 3 years ago

Ok. That makes it less usable. Is it alias for the whole string or for parts of it? May be with aliases we could achieve this although will be less ideal experience for end users as we are trying to abstract as much as possible so they can think only about the architecture of the services they deploy with Bicep and not so much on the details of the code.

The aliases would be for the registry URI and the namespace of the module. The goal of the settings is to make swapping out registries possible without having to modify the Bicep file when you do. We would have the ability to override the aliases via CLI as well. Btw, this is just the current plan and nothing is yet set in stone (so really good timing on this issue 😊).

You said "we are trying to abstract as much as possible". I think I'm missing something here. Can you add more specifics about your use case and how it would suffer if we implemented the bicepconfig.json approach?

I guess also depends on how these string variables will work in the resource type. Could be parameter with default value instead of variable? Can it be variable that takes value from parameter?

The fundamental problem with parameters is that bicep build does not and should not require parameter values to compile a bicep file into JSON and the output of bicep build should not depend on parameter values. Parameters should only be required for deployment.

slavizh commented 3 years ago

Sure. Here is some basic picture of what we do. We develop multiple bicep projects which we call solutions. For example there is a solution for deploying Azure SQL, for deploying Azure VMs, for deploying Azure App services and so on. We manage the versions of those solutions and they are published artifacts in our Azure DevOps org. The solutions have its own configuration (input parameters) that allows you to deploy these services depending on the desired input. Think of that via the input configuration you can change any setting for the deployed service. The scenario we are looking with the registry is the ability to re-use some code between these solutions as there could be some code that is the same for more than one solution. Let's say creation of public IP address. We would than use the registry to have some general module for Public IP address that can be used by some of our solutions. In that scenario of course when we deploy these solutions from our Azure DevOps organization to the tenants of the customers we can use our Bicep registry and refer to the modules there but what if we give these solutions directly to be used by our customers and they do not want to (or it is not possible) to have credentials to our Bicep registry. Instead they want all these referenced modules to be published to their own Bicep registry and when they deploy our solutions they want the solutions to pick the modules from their Bicep registry. If specifying the registry by just changing input for configurations (parameters) would be way easier for them rather modifying some bicepconfig.json file or providing the value in bicep CLI.

I hope this clearly explains why having flexibility to change the registry before usage of bicep without modifying the bicep files is important.

alex-frankel commented 3 years ago

Is the parameters file they fill out a literal ARM template parameters JSON file, or is it some abstraction that eventually gets passed to the deployment command?

If it's the parameters file, then I certainly get the desire for what you want to do, but in practice it is tricky to implement because that parameter value is dynamic. It actually has to be emitted in the ARM template that we generate so it can be overridden. This would prevent us from actually talking to the registry to build the ARM template in the first place. Basically, you have a cyclic dependency.

I think what this issue is getting at is a need to express "parameters for my bicep code" and "parameters for my ARM template deployment". The former does not need to be injected into the template. In fact, you can think of bicepconfig.json as a rough approximation for that. It'd be interesting if both parameter "types" could somehow be declared in the same file. I'm not sure if other transpiled languages have to deal with a similar challenge and have come up with solutions for this type of problem.

slavizh commented 3 years ago

It is both parameters file and abstraction. I will try to explain. It has some structure for defining the subscription you are deploying to and below that structure it is basically the input parameters for the ARM template. This allows us without any modification to read that configuration file and easily convert it to ARM Template parameters file. Mockup you can see below. param1, param2 and param3 are input parameters for main.bicep and those are used to create ARM Template parameters file on the fly before the deployment. Anything below those top level parameters we do not modify.

{
  "subscription name": {
    "param1": {}.
    "param2": "someValue",
    "param3": 5
  }
}

yes, it is tricky. My job is to provide the scenario and give you the requirements of what we need to make it usable. You job is harder as you will have to figure out how to do it exactly so it honers the language's principals and it is usable for the majority of people. I know that at this stage of development there are not a lot of people who will think about these scenarios but as you know not many people do the complex development we do at our company.

Interesting thought on the 3rd comment. At this point I do not have any particular opinion for it. May be it depends what other features bicepconfig.json will contain in the future and if template spec can be bicep file instead of json as template spec is the only path where you have published the bicep code as translated ARM template and cannot be modified further besides parameters.

alex-frankel commented 3 years ago

This allows us without any modification to read that configuration file and easily convert it to ARM Template parameters file.

Do you have a script that takes care of this for you? If so, can you have another "fake" parameter that the script would use to create/modify bicepconfig.json with the right registry settings?

slavizh commented 3 years ago

@alex-frankel I have the script but cannot share it publicly. Problem is if i need to add for example param4 next to param1, param2 andparam3 I need to add this param4 to every single solution that we have build or the script to take care of removing param4 before building the ARM template input parameters file. The latter will create confusion as in our documentation we will have param4 but if you want to use our solutions as template spec outside of our own DevOps CI/CD that parameter will not exist for the template spec. In these cases we will probably opt in of being able to define the aliases via DevOps CI/CD variable which than we build/update bicepconfig.json before deployment/publishing as template spec.

alex-frankel commented 3 years ago

the script to take care of removing param4 before building the ARM template input parameters file. The latter will create confusion as in our documentation

I guess I'm a little confused. Given that the "stan" params file isn't itself a valid ARM Template params file and a conversion has to happen, what is the harm in adding more info to the "stan" params file? It doesn't necessarily need to be a 4th param, but it could be another top level property like so:

{
  "subscription name": {
    "param1": {},
    "param2": "someValue",
    "param3": 5
  },
  "registry_config": {
    "name": "stan.azurecr.io"
   }
}

IOW, why does adding an extra property make this more confusing than the current state in which customers need to know that the "stan" params file is not valid outside of your pipelines?

slavizh commented 3 years ago

Yeah, that will not work either as than I need to change the script to find specifically registry_config as if you have the above example the script will think that registry_config is another subscription name. That is because we can do multiple subscription deployments. Most importantly for us specifically will be best if you can do it inside the bicep language as it will provide best experience for our users but I think also makes it easier to use. Of course the decision is up to you to take. If it is in bicepconfig.json we will find a way to use it, but probably it will not be via our configuration file.

P.S. From certain level below our configuration file is fully identical with ARM template parameters file. That makes it easier if you want to use the solution outside of our CI/CD. We do not have to document something additionally. Somehow adding parameter that if you decide to use our solutions on your own make it a little bit more complex thus I would opt-in of just being DevOps variable in our CI/CD. It is more natural for me to do it that way if the functionality is in bicepconfig.json and not in the code itself.

RokitSalad commented 1 year ago

Is this feature going anywhere? I'm hitting a similar issue, where we are promoting bicep modules from a registry in a non-prod subscription, to a registry in the prod subscription. At the moment, as the registries cannot have the same name, I am prevented from using the same main.bicep file for non-prod and prod, which is added complexity, higher risk, and far from ideal.

Doing something like: module service '${bicepRegistryAddress}/modules/web-app-private-static-ip-prod-direct.bicep:1.0.48' = { ...

seems to be the logical approach. This isn't in a bicep module, it is a bicep file local to the application source, so is not shared. There is no greater risk of injection mistakes than there are with any other parameterisations in devops code.

We decided to head down the path of 'one registry per subscription' because we don't want to enable account access between subscriptions in order to retrieve the module from the dev registry. We can also expect the dev registry to be less reliable than something pushed to the prod environment as we tend to break things in dev occasionally.

It seems an obvious requirement - perhaps I'm missing a trick?

RokitSalad commented 1 year ago

In case anyone else finds this thread and wonders how to allow a single .bicep file to work in different subscriptions: I've found the simplest approach is just to add a powershell step to swap out the url of the dev registry for whichever env's registry is being used. Dirty, but it works.