Support threading arbitrary arguments through to IBuilder.
Currently, IBuilder only takes in the Location as an argument. I proposed adding a Context object of some kind that can be used to thread global (immutable!) state through to all builders. This would permit scenarios as follows:
let myBuilder = foo {
doSomething // can access "name" set below with value "dave"
}
let template = arm {
set_context "name" "dave"
}
Linked to #592 - this would allow us to globally turn on or off default switches such as default identity etc.
Thoughts @ninjarobot ?
Also adding @MNie @MoeJw @TheRSP - would you find this capability useful?
I've bashed together a prototype - here's how you could consume it to create customised behaviours (of course, specific builders would need to support it, at least for right now - we could look at a more fully featured extensibility model as well that could allow custom "modifiers" to be applied through the Farmer pipeline to any resource):
// A custom keyword that wraps a generic statebag method
type ArmBuilder with
[<CustomOperation "environment_name">]
member this.EnvironmentName (state, value) = this.AddCustomState(state, "postfix", value)
let storageAccount = storageAccount { name "prashlovesfarmer" }
let wa = webApp { name "blaha" }
let deployment = arm {
location Location.NorthEurope
environment_name "dev"
add_resources [ storageAccount; wa ]
}
and in the web app:
member this.BuildResources ctx = [
// shadow this with a new Name, based on some custom logic to set the based name for the builder based on the context.
let this =
{ this with
Name =
match postfix ctx.State with
| Some postfix -> this.Name-postfix
| None -> this.Name }
In the JSON:
"apiVersion": "2020-06-01",
"dependsOn": [
"[resourceId('Microsoft.Insights/components', 'blaha-dev-ai')]",
"[resourceId('Microsoft.Web/serverfarms', 'blaha-dev-farm')]"
],
"identity": {
"type": "None"
},
"kind": "app",
"location": "northeurope",
"name": "blaha-dev",
"properties": {
"httpsOnly": false,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', 'blaha-dev-farm')]",
"siteConfig": {
"alwaysOn": false,
"appSettings": [
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(resourceId('Microsoft.Insights/components', 'blaha-dev-ai'), '2014-04-01').InstrumentationKey]"
},
{
"name": "APPINSIGHTS_PROFILERFEATURE_VERSION",
etc.
@isaacabraham I think this would be useful. We currently have env parameter which we pass through to all builders. One thing I don't quite understand is how we would make use of the parameter. Would we need to copy the whole BuildResources method?
I think this might be also handy when configuring Common things between resources like Diagnostic -setting and alerts etc. enable them for all resources we deploy instead of just creating one for every resource.. but this might not be an easy thing to do
@TheRSP I have no idea. At the moment, the prototype I have isn't truly extensible - the BuildResources method would need to be rewritten, as you say. A truly extensible option would be to allow you to provide a plugin - some kind of interface - which gets called after each builder with the context and all the resources that it generated; you could then do whatever you wanted to the ARM resources.
It would be fairly low-level though - you'd be dealing with IArmResource and things like that.
The idea is nice, as Moe and Richard mentioned it could be pretty handy when doing some repeatable configuration for some resources.
As I understand it's gonna work as some kind of "applier" on IArmResource. The only concern I have right now is. What if we would want to extend only some parts of resources. I mean for example add function "a" to function/service bus/documentdb but not to the web app, etc. Is it a valid use case, or we don't want to extend it in such way?
@MNie The way it would probably end up looking like would be that you simply get a collection of IArmTemplates (maybe with an Resource ID or similar) and then have to pattern match over them to "pick" the ones that you want - and then do whatever you want with them.
Alternatively, perhaps we could inject it earlier in the pipeline i.e. before the BuildResource method runs.
I haven't thought this through fully so may be completely infeasible but could it be done such that you pass in a function to a builder saying how to use the context?
let storage = storageAccount{
name "storage"
use_context (fun res ctx -> {res with Name = ctx.ResourcePrefix + res.Name})
}
arm {
set_context {| ResourcePrefix = "companydev" |}
add_resource storage
}