feat: add azure.provisioning extension (C# CDK importer)#7463
feat: add azure.provisioning extension (C# CDK importer)#7463m-nash wants to merge 1 commit intoAzure:ext-importer-supportfrom
Conversation
Add an azd extension that enables defining Azure infrastructure in C# using Azure.Provisioning. The extension implements the importer-provider capability to compile C# to Bicep, which azd's Bicep provider then deploys. Supports: - .NET 10 single-file apps (infra.cs with #:package directives) - Traditional .csproj multi-file projects - Options forwarding from azure.yaml to C# program args - Preview, provision, and destroy via Bicep delegation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jongio
left a comment
There was a problem hiding this comment.
Issues to address:
- importer_csharp.go:253 - readGeneratedFiles only reads top-level; nested Bicep modules get silently dropped
- commands.go:14 - root command missing standard extension conventions (SilenceUsage, SilenceErrors, --debug, --no-prompt, metadata/version commands)
- commands.go:23 - listen command should be Hidden: true
- importer_csharp.go:156 - hasCSharpInfra is dead code, unused linter will flag
- importer_csharp.go:210 - use slices.Sorted(maps.Keys(...)) per Go 1.26 conventions
- importer_csharp.go:56,111 - ProjectInfrastructure and GenerateAllInfrastructure duplicate the resolve-compile-read pipeline
Missing from PR: go.mod, go.sum, build scripts, version.txt, CHANGELOG.md, .golangci.yaml.
| } | ||
|
|
||
| // readGeneratedFiles reads all .bicep and .json files from a directory. | ||
| func readGeneratedFiles(dir string) ([]*azdext.GeneratedFile, error) { |
There was a problem hiding this comment.
[HIGH] This only reads top-level entries. If the C# program generates nested Bicep modules (e.g., modules/storage.bicep), they're silently dropped and deployment fails with missing module references.
Use filepath.WalkDir to recurse subdirectories and preserve relative paths with filepath.ToSlash.
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func NewRootCommand() *cobra.Command { |
There was a problem hiding this comment.
[HIGH] Missing standard extension conventions: SilenceUsage: true, SilenceErrors: true, CompletionOptions, hidden help command, --debug/--no-prompt persistent flags, and metadata/version subcommands. See azure.appservice root.go for the pattern.
| return root | ||
| } | ||
|
|
||
| func newListenCommand() *cobra.Command { |
There was a problem hiding this comment.
[HIGH] Should be Hidden: true - this is an internal protocol command, not user-facing. Every other extension hides it.
| } | ||
|
|
||
| // hasCSharpInfra checks if a directory contains .cs or .csproj files. | ||
| func hasCSharpInfra(path string) bool { |
There was a problem hiding this comment.
[MEDIUM] Dead code - not called anywhere. The unused linter will flag this. Remove it, or if auto-detection is planned, wire it into CanImport when that's ready.
|
|
||
| // optionsToArgs converts the importer options map to --key value CLI args, | ||
| // excluding the "path" key which is used for directory resolution. | ||
| func optionsToArgs(options map[string]string) []string { |
There was a problem hiding this comment.
[MEDIUM] Go 1.26 convention: use slices.Sorted(maps.Keys(options)) instead of manual key collection + sort.Strings. Drops the sort import entirely.
| } | ||
|
|
||
| // ProjectInfrastructure compiles C# Azure.Provisioning code to Bicep for `azd provision`. | ||
| func (p *CSharpImporterProvider) ProjectInfrastructure( |
There was a problem hiding this comment.
[MEDIUM] ProjectInfrastructure and GenerateAllInfrastructure duplicate the resolve-compile-read pipeline. Extract a shared helper like compileCSharpToBicep(ctx, projectPath, options) to avoid divergence.
Summary
Adds an azd extension (
azure.provisioning) that enables defining Azure infrastructure in C# using Azure.Provisioning instead of Bicep. Built on the newimporter-providercapability from #7452.The extension compiles C# to Bicep as an intermediate step — azd's built-in Bicep provider handles deployment, preview, state tracking, and destroy. Zero changes to azd core.
How it works
The user writes
infra/infra.cs(or a.csprojproject) using Azure.Provisioning, then runsazd upas normal. The extension:.csfile or.csprojproject)dotnet runto compile C# to BicepTesting
All scenarios verified E2E against live Azure:
.csfile provision.csprojproject provisionazd provision --previewazd downazd init --templateazure.yamlSample repo: m-nash/todo-csharp-cosmos-sql-dotnet — full todo app with Cosmos DB, App Service, Key Vault, App Insights, all defined in a single
infra.csfile.Gaps to consider
1. Auto-detection —
infra.importerconfig is requiredToday, azd auto-detects
.bicep→ Bicep and.tf→ Terraform without anyinfra:config inazure.yaml. The same should work for C# — a user should be able to drop aninfra.csinto theinfra/folder and runazd upwithout any yaml changes.Currently this requires the explicit
infra.importerblock because the auto-detection path does not consult extension importers. Ideally, when no provider is detected from built-in file checks, azd would ask each installed extension importer if it can handle the current infra directory, and let the extension decide based on whatever criteria makes sense (file types, markers, etc.). This keeps azd core language-agnostic while letting extensions opt in to auto-detection.2. CLI argument passthrough
The extension forwards
infra.importer.optionsfromazure.yamlas--key valueargs to the C# program. This works for static configuration but there is no way to pass runtime arguments from the CLI (e.g.azd provision -- --region westus3).We would strongly encourage azd to support CLI arg passthrough to importer extensions. Code-based CDK programs are written in full programming languages — C#, TypeScript, Java, Python — and developers will naturally use command-line arguments to parameterize their infrastructure. If a standalone
dotnet run infra.cs -- --region westus3(ornpx ts-node infra.ts -- --region westus3) works but the same program requires manual wiring throughazure.yamloptions when run via azd, that friction becomes a real adoption blocker. Users should not have to restructure their code to work with azd.