farmer icon indicating copy to clipboard operation
farmer copied to clipboard

Extending existing resource with custom fields

Open Yakimych opened this issue 4 years ago • 9 comments

I've looked at Convert from JSON to an F# anonymous record and Migrating to IArmResource, and (correct me if I am wrong) it looks like it does not allow adding fields/properties to existing resources (but rather only creating new resources from scratch). Is there a way to just add custom JSON or anonymous record to e.g. a webApp?

More specifically I am looking to mount Azure Storage as a local share in a container app in App Service by adding the following under "properties" (next to e.g. siteConfig that is already generated by Farmer):

"azureStorageAccounts": {
    "my-storage-mount": {
        "type": "AzureFiles",
        "accountName": "storageAccountName",
        "shareName": "sharename",
        "mountPath": "/data",
        "accessKey": "..."
    }
}

Would I have to recreate the whole webApp or Microsoft.Web/sites/config JSON manually in order to add this section, or is there a way to just "jack in" a custom field for azureStorageAccounts?

Yakimych avatar Jul 25 '21 10:07 Yakimych

@Yakimych great question. For the moment, it's unfortunately the former - you can only add in entirely new resources, rather than modifying an object. I've been thinking about ways of letting you "plug in" to the very bottom of the pipeline though to modify the JSON that is output as well, but unfortunately nothing that's ready for testing out.

isaacabraham avatar Jul 26 '21 08:07 isaacabraham

Ok, good to know - closing this then, thanks @isaacabraham! Feel free to ping me in case you would like to test out a PoC or share ideas on how to do this going forward.

Yakimych avatar Jul 26 '21 10:07 Yakimych

Feel free to create an issue specifically on that so we don't forget!

isaacabraham avatar Jul 26 '21 14:07 isaacabraham

Oh, in that case, is it ok to just reopen this one? What kind of API do you have in mind? I am thinking naively, something like a customJson or mergeWithCustomJson field on a webSite builder that would take the following:

$"""{{
    "properties": {{
        "azureStorageAccounts": {{
            "my-storage-mount": {{
                "type": "AzureFiles",
                "accountName": "storageAccountName",
                "shareName": "shareName",
                "mountPath": "/data",
                "accessKey": "..."
            }}
        }}
    }}
}}"""
|> Resource.ofJson

But I assume the above would have to be "merged" with the JSON structure that the webApp builder generates, since it might already have a properties section with other fields?

Yakimych avatar Jul 26 '21 22:07 Yakimych

@Yakimych exactly right. We'd have to have some way of splicing the JSON together. A crude option would be that we give you access to the final JSON as a JObject (or whatever the System Text JSON equivalent of that is) and it's your responsibility to return back some valid JSON.

isaacabraham avatar Aug 13 '21 18:08 isaacabraham

@Yakimych I know it's been a while, but I actually had a bash at this this evening and quite quickly had something that seems like it could be workable:

  1. Given some ARM resources e.g. a storage account:
let store = storageAccount {
    name "storage"
}
  1. Standard arm { } block, except notice the extra rewriter keyword:
let x = arm {
    location Location.NorthEurope
    add_resources [
        store
    ]
    rewriter mapper // rewrite JSON using a function called mapper (see below).
}
  1. mapper above is a function that has the following signature: IArmResource -> obj option. In other words, the function takes in every resource that has been created and it's your responsibility to either rewrite the JSON however you want, or to do nothing (return None).

Here's an example rewriter that looks for Storage Accounts called "store" and adds a new field into the JSON:

let mapper (resource:IArmResource) =
    match resource with
    | ResourceType storageAccounts & ResourceName "storage" ->
        // Convert the object into JSON first...
        let json = resource.JsonModel |> JsonSerializer.Serialize |> JsonNode.Parse

        // Make any required changes
        json["properties"]["defaultToOAuthAuthentication"] <- true

        // Return back out as a .NET object
        Some (JsonSerializer.Deserialize json)
    | _ ->
        None

In this prototype I've made a couple of active patterns so that you can more easily find the resource that you want to modify (based on type and name).

This is fairly low-level but it is a complete escape-hatch, you can modify the JSON however you want.

What do you think?

cc @ninjarobot

isaacabraham avatar May 06 '22 23:05 isaacabraham

Would it be possible to map them in the context of the resource being added rather than the whole deployment if there was some new interface like IMappedResource that holds a specific resource and a mapping function that will be able to operate on the JSONModel record for the specific resource? It might make it easier to use without having to dig into lists of resources (could also be in nested templates).

ninjarobot avatar May 07 '22 11:05 ninjarobot

@isaacabraham - nice! It's been a while indeed, but next week I'll try to dig up the use-case I had and double-check if it is solvable this way (but I can't see why it wouldn't).

Yakimych avatar May 08 '22 15:05 Yakimych