Feature spec: Dynamic Bicep Extensions Served by the Radius Control Plane#11907
Feature spec: Dynamic Bicep Extensions Served by the Radius Control Plane#11907zachcasper wants to merge 6 commits into
Conversation
Signed-off-by: Zach Casper <zachcasper@microsoft.com>
There was a problem hiding this comment.
Pull request overview
Adds a new design note proposing a shift from release-time/manual publishing of Radius Bicep extensions to a control-plane-served, on-demand extension artifact that the rad CLI downloads to a workstation-global .tgz and references via a single radius alias.
Changes:
- Introduces a proposed control-plane API that generates a Bicep extension artifact from the currently registered resource types.
- Proposes new/updated CLI behaviors (
rad resource-type update, kubecontext-awarerad init, and implicit refresh duringrad deploy/rad run) to keep the local extension current. - Proposes consolidating the four
radius*aliases into a singleradiusalias and removingrad bicep publish-extension.
Signed-off-by: Zach Casper <zachcasper@microsoft.com>
|
|
||
| Developer (steady state, type set changes server-side): | ||
|
|
||
| 1. Run `rad resource-type update` to refresh now, or just run the next `rad deploy` / `rad run`, which refresh `~/.rad/bicep-extension.tgz` as a side effect. |
There was a problem hiding this comment.
I like the idea of having a separate command that imports the server-side type library to developer workstations, but I think it should be separate from the rad resource-type CRUDL command.
rad resource-type update implies that the developer is updating the type schema on the server. In an enterprise setup, platform engineers would typically use RBAC to restrict who can create or update resource type definitions.
I think it would be better to call this something like rad extension update or rad extension import implying what the command is actually doing - pulling the extension from the Radius control plane and updating the local extension configuration on the developer machine.
It also maps to the existing extension radius experience in application definition, so the mental model stays the same .
There was a problem hiding this comment.
Yes, I debated many options here. I'm open to suggestions. Here is what was considered:
rad resource-type update — command-naming debate
Options considered
| # | Candidate | Pros | Cons |
|---|---|---|---|
| 1 | rad bicep extension sync (original) |
Most precise; explicitly names artifact + namespace. | Verbose. Only makes sense if more rad bicep extension <verb> siblings (show, path) are likely. |
| 2 | rad bicep sync |
Shorter; discoverable next to other rad bicep … commands. |
Still ties the verb to the artifact format. Hides that the catalog (not the file) is the real input. |
| 3 | rad sync |
Reads cleanly; leaves room for future syncs. | Vague; future-proofing was speculative (YAGNI). No concrete second target identified. |
| 4 | rad resource-type sync |
Names what is synced (the catalog), not the file format. Mirrors rad resource-type create. Leaves room for non-Bicep outputs (Terraform schema, LSP index) without renaming. |
sync has weak prior art for schema/catalog caches — the ecosystem (helm repo update, apt update, brew update, terraform init -upgrade) overwhelmingly uses update. |
| 5 | rad resource-type refresh |
Semantically precise; no collision. | Less familiar verb than sync or update in modern CLIs. |
| 6 | rad extension update |
Short; uses the established update verb. |
"extension" collides hard with Bicep's own vocabulary (extension keyword, extensions block in bicepconfig.json, "extension types"); ambiguous with a future rad plugin system. Names a file artifact rather than the catalog. |
| 7 | rad resource-type update (selected) |
Names the catalog, not the artifact. Mirrors rad resource-type create (symmetric: platform engineer creates server-side, developer updates client-side). Uses update — the established verb in this ecosystem for "pull fresh catalog from server." Singular form matches existing CLI conventions. Preserves optionality for future outputs (Terraform, LSP) under the same noun. |
Verb collides loosely with the idea of "modify a resource type" — mitigated because schema edits happen via rad resource-type create -f manifest.yaml in practice, not via an update subcommand. |
Why option 7 won
- Catalog, not artifact. The thing being kept fresh is the resource-type catalog. The
.tgzis one current serialization; in the future the same command can emit other formats. - Symmetric with
rad resource-type create. Platform engineer creates types; developer updates the local mirror. Same noun on both sides. updateis the dominant verb in the ecosystem.helm repo update,apt update,brew update,terraform init -upgrade.syncis mostly used for filesystem mirrors (rclone sync,aws s3 sync) and fork-to-upstream (gh repo sync), neither of which matches our shape.- Avoids the overloaded word "extension." Reserves that term for Bicep's own meaning and for a future
radplugin namespace. - Lets
rad initabsorb the first-run case. Becauserad initbecame kubecontext-aware in the same change,rad resource-type updateis only needed for explicit refresh — the everyday developer never has to type it, which lowers the cost of choosing a slightly longer name.
There was a problem hiding this comment.
I changed the command to rad resource-type sync. You're right that update should be reserved for CRUDL control plane operations. Sync infers updating the local environment based on the control plane.
|
|
||
| ## Key dependencies and risks | ||
|
|
||
| * **Dependency – Bicep CLI local-file extension references and parent-directory `bicepconfig.json` resolution.** The integration point is Bicep's existing support for a local-file `extensions` entry plus its existing parent-walk to find `bicepconfig.json`. Bicep's walk reaches `$HOME` for any `.bicep` source under `$HOME`, but does not search `~/.bicep/` or XDG paths. No change to the upstream Bicep CLI or Bicep VS Code extension is required. Risk: if Bicep changes its parent-walk semantics, every workstation breaks. Mitigation: this is documented Bicep behavior; pin Bicep version testing in CI. |
There was a problem hiding this comment.
As I understand here - bicepconfig.json will still be maintained at the repo level and the extensions in .rad/ will be refreshed by rad init or an explicit command to update extensions locally. We maintain the bicepconfig.json outside of .rad/ because of Bicep's config resolution?
There was a problem hiding this comment.
No, rad init will now write bicepconfig.json in the users' home directory. The contents will be:
{
"extensions": {
"radius": ".rad/bicep-extension.tgz"
}
}
The downloaded Bicep extension will be ~/.rad/bicep-extension.tgz.
There was a problem hiding this comment.
Oh I was thinking about this from Repo Radius perspective. How this would be created and maintained for repositories with radius installed? May be some clarity around that would help.
There was a problem hiding this comment.
Please suggest what clarity should be added, if any. If Radius was running in a CD runner, all of the above still applies. The user's home directory would have the bicepconfig.json and any Git repository should be cloned in a directory in the user's home directory, as is typical.
There was a problem hiding this comment.
Today we maintain the bicepconfig.json at each repository level (docs, radius) to run Radius CI tests and in the case of Repo Radius too, we might maintain this config in the ~/.radius folder of a repository to compile and deploy the app.bicep with custom resource ty[es
With this proposal, there is no need to have this file at repo level and the workflows can automatically sync the bicepconfig.json before deploy. Is that correct ?
| * **Dependency – Authentication path `rad` already uses for control-plane calls.** The new extension API reuses the same auth. The Bicep CLI itself never authenticates to the control plane (it only sees the local file). | ||
| * **Risk – Stale `~/.rad/bicep-extension.tgz` on a workstation that has not run `rad` since the control plane changed.** Mitigated by routine `rad` commands (`deploy`, `run`) refreshing as a side-effect and by `rad resource-type update` being a single command. | ||
| * **Risk – `.bicep` files outside `$HOME` are not covered automatically.** Mitigated by documentation; advanced users place a `bicepconfig.json` somewhere in their source tree by hand. | ||
| * **Risk – Devcontainer / Codespaces / remote-SSH workspaces have a different `$HOME`.** `rad init` must be re-run inside the container; the artifact is per-environment. Documented limitation. |
There was a problem hiding this comment.
Would this be a risk if bicepconfig.json is maintained at per repo level ?
There was a problem hiding this comment.
If a is running in a dev container (or another workstation for that matter), they will not have the Bicep extension in their environment. This is obvious and a somewhat silly risk to be documented.
Signed-off-by: Zach Casper <zachcasper@microsoft.com>
|
|
||
| > As a developer, the type set I'm allowed to use is whatever my platform engineer told me to put in my `bicepconfig.json` — and I have no way to confirm it matches what the control plane will actually accept until I try to deploy. Onboarding to a new Radius installation is a docs-following exercise: install `rad`, then log into some company-specific OCI registry, then paste a snippet into `bicepconfig.json` for each of half a dozen aliases. When the platform team changes a type, nobody tells me, and my editor stops being a reliable source of truth. | ||
|
|
||
| ## Desired user experience outcome |
There was a problem hiding this comment.
To clarify - we will overwrite all files in this directory with current (without comparing files) so any changes that are breaking show up immediately and not during deploy time.
|
|
||
| Radius developers describe applications in Bicep. To make a resource type usable from Bicep, the Bicep compiler needs a *Bicep extension* — a gzipped tar containing the schemas of those types, supplied either as a local `.tgz` file or as an OCI artifact in a registry — referenced by alias in `bicepconfig.json`. Today, two parallel pipelines keep extensions in existence: the Radius project rebuilds and publishes a fixed set of `radius*` extensions to `biceptypes.azurecr.io` on each release, and platform engineers manually run `rad bicep publish-extension` and edit every developer's `bicepconfig.json` whenever they add or change a user-defined resource type. The set of types the control plane has registered and the set of types Bicep can type-check against are kept in sync by hand. | ||
|
|
||
| This feature makes the Radius control plane the single source of truth for Bicep extensions. A new `rad resource-type sync` command (also invoked implicitly by `rad init`, `rad deploy`, and `rad run`) fetches the control plane's currently-registered types, packages them into a Bicep extension, and writes it to `~/.rad/bicep-extension.tgz`. `rad init` becomes Kubernetes context aware: if no Radius control plane is detected in the current Kubernetes context, it installs Radius (today's behavior); if one is already installed, it skips the install step. In both cases it creates or merges `~/bicepconfig.json` with a single `radius` extension entry pointing at that file. Bicep's existing parent-directory resolution then picks the configuration up for any `.bicep` file under the home directory that does not have a closer `bicepconfig.json` of its own — no per-repository setup, no external OCI registry, and no separate publish step is required. Repositories that already ship their own `bicepconfig.json` are unaffected by the home-directory file (see edge cases below); the expected migration is for those repos to add the same `radius` entry or remove their local config. |
There was a problem hiding this comment.
should we use 'update' for consistency instead of 'sync'?
There was a problem hiding this comment.
There were a number of verbs considered (see this comment). We landed on the verb sync meaning to download information from the control plane to the local workstation. This is distinct from update which is a control plane CRUDL operation updating the control plane (e.g., the other direction).
Description
Add a design document proposing that the Radius control plane become the single source of truth for Bicep extensions, replacing the current dual-pipeline model (release-time ACR publishes + manual
rad bicep publish-extension).Under the proposal:
radalready uses.rad resource-type updateCLI command fetches that artifact and writes it to~/.rad/bicep-extension.tgz.rad deployandrad runrefresh it as a side effect, closing the staleness window in the normal authoring → deploy loop.rad initbecomes kubecontext-aware: it installs Radius if the current kubecontext has none, otherwise skips the install step. In both cases it writes~/.rad/bicep-extension.tgzand creates or merges~/bicepconfig.jsonwith a singleextensions.radiusentry pointing at that file. Bicep's existing parent-directorybicepconfig.jsonresolution covers every.bicepfile under$HOMEautomatically.radius*aliases (radius,radiusCompute,radiusData,radiusSecurity) are consolidated into a singleradiusalias; legacy entries in an existing~/bicepconfig.jsonare removed byrad initas an intentional forcing function.rad bicep publish-extensionis removed from the CLI. Previously-published artifacts onbiceptypes.azurecr.ioremain readable for users on olderradversions.The document records the four other delivery models that were evaluated (long-running proxy daemon, Ingress + anonymous OCI endpoint, Kubernetes aggregation layer, and control-plane-pushes-to-customer-registry) and why each was rejected for v1.
No code changes; design document only.
Type of change
Fixes: #TBD
Contributor checklist
eng/design-notes/in this repository, if new APIs are being introduced.