-
Notifications
You must be signed in to change notification settings - Fork 539
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
Consider adding a bicep resource type #1941
Comments
What does this do at F5/ |
If we were modeling this similar to what we do with other resources, it would explode when |
But you could add the connection string to the AppHost's config (i.e. user secrets), and it would still get passed through to the app. Will that still work here? |
That's the idea, though it might not just be a single connection string. It requires knowing the shape of the outputs from the bicep. |
If there is a change to the provisioning template, then after pushing F5 you have to wait till the resources are allocated. At least in AWS when we are talking about application level resources like S3 buckets, queues and topics provisioning is pretty quick. For the sample app in my PR which has a topic and queue subscribed to the topic if nothing is provisioning then it is about 20 seconds. If there is no change to the provisioning template, which I assume is the majority of the F5 cases, then it is essentially a noop. |
Will be nice, I think, if we allow a I assume expressions can access and output from the bicep by using the I suppose Does that seem workable and align with what you were thinking, @tg-msft? |
This is going to look a little different than integrating a CDK with a "generic" Azure deployment template. Using a prototype CDK I can automatically infer all the params/outputs and link them together using resource specific context (i.e., if I know it's a Storage account that you're trying to reference with managed identity, I can make the bicep generate outputs for the endpoint and Entra client id). Doing that for an arbitrary bicep template is going to be more of a challenge when wiring everything up. We could explore asking folks to specify their params/outputs when declaring a bicep template so we have objects to glue together. Maybe something like: // Create a Storage account
var storage = builder.AddAzureStorage("storage");
// Create a user assigned managed identity and grant it read access to my Storage account with a custom bicep template
var bicep = builder.AddBicep("appident", "appident.bicep")
.WithParam("storageName", storage.Resource.Name) // This assumes a CDK so we can glue template outputs in the manifest
var principalId = bicep.WithOutput("principalId");
var clientId = bicep.WithOutput("clientId");
// Give my ApiService container that managed identity and a reference to my Storage account
builder.AddProject<Projects.HelloAspire_ApiService>("api")
.WithUserAssignedIdentity(principalId, clientId)
.WithReference(storage); and turn that into a manifest kind of like: {
"resources": {
"storage": {
"type": "azure.template.v0",
"params": { },
"outputs": {
"storage_name": {},
"storage_properties_primaryEndpoints_blob": {}
},
"path": "storage.bicep"
},
"appident": {
"type": "azure.template.v0",
"params": {
"storageName": "{storage.outputs.storage_name}"
},
"outputs": {
"principalId": {},
"clientId": {}
},
"path": "appident.bicep"
},
"api": {
"type": "project.v0",
"path": "HelloAspire.ApiService/HelloAspire.ApiService.csproj",
"env": {
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
"AZURE_CLIENT_ID": "{appident.outputs.clientId}",
"ConnectionStrings__blobs": "Uri={storage.outputs.storage_properties_primaryEndpoints_blob}"
},
"metadata": {
"azd:userAssignedIdentity": "{appident.outputs.principalId}"
}
}
}
} Failing that, maybe we can rely on conventions like we've talked about for outputs of unreified azd resources like a container's ID. So if the bicep template has a |
Do outputs need to be defined in the manifest to be referenced? Those could be implicit properties of the "azure.template.v0" schema. The tool that executes the manifest has to evaluate the bicep anyways right? |
At the moment I'm generating the outputs as empty objects where I can potentially stash extra metadata in the near future - like whether or not it represents a @ellismg - can you tell whether a deployment output is meant to be secure from the response? https://learn.microsoft.com/en-us/rest/api/resources/deployments/create-or-update?view=rest-resources-2021-04-01&tabs=HTTP#deploymentpropertiesextended makes me think it's just key/value pairs. Bicep decorators only apply to params as well. I'm curious how you'd automatically insert secure values into KV when they're used in manifest binding expressions if we're not generating a manifest entry with hints. |
I do not think that you can (in fact I think that secure outputs don't come back in a majority of the cases where you fetch a deployment). We can however detect this from the bicep/arm template itself, but examining the I expected that if there was not information in the manifest about if the values were secure or not that |
I have a branch where I started playing with this: |
Open draft PR so we can riff on something more concrete #2056 |
So @ellismg one thing I am realizing is that it's going to be problematic to design this generic resource type if any imperative code needs to run after outputs are produced in order to produce values that are consumable by other resources (like our fake "connectionString" property), we can't represent it in the manifest.
The connection string in this case is that template and azd can handle this. What about this logic: aspire/src/Aspire.Hosting.Azure.Provisioning/Provisioners/ServiceBusProvisioner.cs Line 83 in fb2bf77
All of the logic inside of these template functions can't be expressed in this declarative manifest: So, what does a manifest look like for SQL server that needs a connection string? Here's the hardcoding of a template that is not great 😢, but this logic needs to live somewhere. aspire/playground/azuresql/AzureSql.AppHost/aspire-manifest.json Lines 2 to 27 in fb2bf77
|
Yeah - this is tricky. We already had to add some primitives inside A few thoughts on things we might be able to do (spit-balling here before vacation) We do have a way to put metadata on outputs in a bicep file (a feature I added a while back). You could imagine something like this: @metdata({ azd: { type: 'template', context: '<some-output-name>' })
output connectionString string = "{{ (urlParse .ServiceBusEndpoint.Data.ServiceBusEndpoint).host }}" The above would mean that when a thing is stitching these things together, it should treat the value as You could image different shapes as well: @metdata({ azd: { type: 'template' })
output connectionString object = {
template: "{{ (urlParse .ServiceBusEndpoint.Data.ServiceBusEndpoint).host }}"
context: { ... }
} Which might be more ergonomic and require fewer outputs, so I sort of like that more. I proposed go text/templating because it is easy for me and it is "Cloud Native". But I could image instead an expression language that looks closer to what arm has (and maybe over time they can add more of these primitives so they can just evaluate them? They already allow some level of data transformation, perhaps we just need a few more primitives. The other option is you could always try to build something using deployment scripts which would allow you to inject arbitrary computation via PowerShell scripts during deployment and do whatever sort of munging you wanted. We do have a user assigned identity that we could run all this stuff under and it would in theory have access to all the resources, so if you want to code spit a bunch of PowerShell, maybe that's a path forward since you could do whatever you needed to do and then set an output of the deployment script which your module then exports. Feels like a pretty big and scary hammer, however, so maybe something more limited like the replacement above is a thing to codify first. It is likely less scary operationally during deployment than running arbitrary PowerShell. |
OK I made lots of progress on this learning way more about bicep than I'd like. I was able to replat the AzureSql resource on top of this primitive: This is the sql resource type: This is the bicep file: This is the manifest: A couple of thoughts:
Random follow up:
|
I think we will need some special case logic for insecure connection strings so that they are never flowed via outputs based on what @ellismg said above. Right now, we happen to be using connection strings for cosmos because it works with the emulator and when deployed. Right now, azd emits the resource name from the bicep and does an explicit API call to get the connection string. This way it's never stored in the deployment logs/ouputs. For cases where we are using managed identity, it's much nicer as we don't need to do any transformation outside of the template. The other alternative is to define some syntax in the manifest itself that azd understands to do this transform. That would keep the metadata out of the bicep template. I'll migrate cosmos to this new model so I can try out the approach. |
OK got cosmosdb working and would love some feedback on this hacky model. Here's the manifest output: aspire/playground/bicep/BicepSample.AppHost/aspire-manifest.json Lines 57 to 68 in 8628a30
The connection string template calls a 'keys' method to indicate that the orchestrator should grab the keys for this resource type. The azure resource type is specified here. |
Updated the issue with a more detailed design of what's implemented in the pull request. |
I'm curious about a few aspects of this approach. It's mentioned that the bicep files would be in the app host directory, yet they're usually in an ./infra/ folder at the root, not the end of the world but would that change in the future? If an output name in a bicep file changes, that requires an app host code change then too - right, I'd assume so? I was under the impression that the Azure Provisioning bits relied on the resource management SDKs, is that true?
Is that an accurate way to think about this? So really, this proposal is just a way of providing arguments as |
Issue here for secure output support: Azure/bicep#2163 |
The bicep files are relative to the apphost directory, but they can be anywhere on disk.
The caching will take into account the inputs, and the bicep file content (a checksum is generated based on that).
They still do, but there's a new BicepProvisoner that uses the resource management SDK to deploy the bicep template (well ARM template).
Yep! That's the primitive being introduced.
Future, yea, can easily be built (probably using attributes etc). |
The initial change is in, we'll iterate and file follow up issues as azd attempts to implement this 😃 |
Design
AzureBicepResource
allows for defining a piece of piece as either a file on disk, a inline string, or an embedded resource. This is the primitive that allows developers to express a piece of bicep and allows them to capture outputs for referencing in other resources. Bicep resources have parameters and outputs. These are defined in the bicep and are used to marshal data into and out of the template after execution.Hello World:
As a fake example, here's a hello world example:
test.bicep (in the apphost directory)
Azure provisioning will submit this deployment, query the outputs and the environment variable
bicepValue_test
will be set to "hello world".The manifest for this looks like:
Not too surprising. There's a new resource type azure.bicep.v0 that describes the path to the bicep file, the parameters and their values. In the api's section, the
bicepValue_test
env variable references the bicep outputs directly. This isn't expressed in the manifest but after the execution engine runs, it get grab the outputs and fill in the values appropriately (as @tg-msft described, if we need to do something special here than we need to be able to mark specific outputs as "please put this in a secret".Connection Strings
The low-level bicep primitive is great and allows users to define custom resources not supported by aspire out of the box. In order to make
WithReference
work withIResourceWithConnectionString
, bicep resources can define a connection string template (much like container resources). For example, here's the service bus namespace resource in the manifest:This defines a connection string property that resolves the
serviceBusEndpoint
coming from the output of theaspire.hosting.azure.bicep.servicebus.bicep
file.Note: Aspire uses connection strings to mean connection information. These may or may not contain secrets.
When the bicep file wants to output a secret for consumption in another resource outside of the bicep universe (where listKeys can be used to avoid this problem), we need a way to instruct azd to get the keys securely outside of this context. We will pass a key vault name to bicep files that request it so that secrets can be written to this keyvault. This will be referrred to in the manifest via "secretOutputs".
e.g.
This is the cosmos bicep and writes the connection string to the host provided keyvault.
https://github.com/dotnet/aspire/blob/b0570889d78d792407caf7667acb2056732e9266/src/Aspire.Hosting.Azure/Bicep/cosmosdb.bicep
This is how it is referenced in the manifest:
aspire/playground/bicep/BicepSample.AppHost/aspire-manifest.json
Lines 122 to 132 in b057088
The connection string template for a bicep resource can contain akeys
function with the resource name. This instructs azd to get the keys and fill it into this part of the connection string, without doing any crazy keyvault shenanigans 😄:Example redis:
Thekeys
method takes the full resource name (including the outputs of the bicep). This should be enough to resolve the primary key (do we need to default to one?).Hopefully, as we default to more managed identities, this will be less of a problem.
Ambient context
When these bicep files get run in either azd of AzureProvisioning, we need to supply context about the current user (when running locally there's no user assigned managed identity for e.g.). Bicep resources can opt into this information by declaring well known parameter names. This could instead be identified by parameter metadata attribute (to avoid the naming matching). These also show up in the manifest as empty strings but it would be more useful to flow that metadata via the manifest.
Examples of these:
Original Issue
This is the "break glass" option for resources not defined in aspire directly and not modeled in C#. The idea inspired by the AWS PR is to expose a low level "stringly typed" bicep resource that points to a bicep file and allows you to map the outputs of that bicep file to environment variables in any target resource.
This would go into the manifest as a bicep.v0 resource with a path to the file:
As with any interop system there will be problems:
cc @ellismg @tg-msft for thoughts
The text was updated successfully, but these errors were encountered: