From 338722bf047ba193f1c3a168d9b71b94f5986fa8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:25:56 +0000 Subject: [PATCH 01/11] Add cross-platform E2E demo for private Terraform/Bicep registry config resource types --- .../private-registries-recipes/README.md | 381 ++++++++++++++++++ .../bicep/bicep-private-registry.bicep | 119 ++++++ .../bicep/combined.bicep | 168 ++++++++ .../bicep/terraform-private-registry.bicep | 133 ++++++ .../recipes/redis-recipe.bicep | 88 ++++ 5 files changed, 889 insertions(+) create mode 100644 docs/demos/private-registries-recipes/README.md create mode 100644 docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep create mode 100644 docs/demos/private-registries-recipes/bicep/combined.bicep create mode 100644 docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep create mode 100644 docs/demos/private-registries-recipes/recipes/redis-recipe.bicep diff --git a/docs/demos/private-registries-recipes/README.md b/docs/demos/private-registries-recipes/README.md new file mode 100644 index 00000000000..887bf8aeb37 --- /dev/null +++ b/docs/demos/private-registries-recipes/README.md @@ -0,0 +1,381 @@ +# E2E Demo: Private Registries & Repositories with Terraform and Bicep config resource types + +This demo validates the work delivered in +[radius-project/radius#11798 — *Private registries and repositories support for +compute extensibility*](https://github.com/radius-project/radius/issues/11798). + +With compute extensibility, Radius introduced the `Radius.Core/environments` +resource. Private recipe registry authentication — which used to live inline on +`Applications.Core/environments` under `recipeConfig` — is now expressed as +**standalone, reusable resources**: + +| Resource | Purpose | +| --- | --- | +| `Radius.Core/terraformConfigs` | Private Terraform registry credentials, provider installation (mirror/direct), and Terraform env vars. Radius renders a `.terraformrc` from it at recipe-execution time. | +| `Radius.Core/bicepConfigs` | Private Bicep (OCI) registry authentication, keyed by registry hostname. | + +A `Radius.Core/environments` resource references these by resource ID, so a +platform team can define registry authentication **once** and reuse it across +many environments. + +This walkthrough proves the feature works end-to-end against **real private +registries in the cloud**, across three scenarios: + +1. **Private Bicep recipe registry** (OCI, e.g. Azure Container Registry) — `Radius.Core/bicepConfigs` with `BasicAuth`. +2. **Private Terraform module registry/repository** — `Radius.Core/terraformConfigs` with a `credentials` token. +3. **Combined** — a single environment that references *both* config resources. + +> Design references: +> [Terraform & Bicep settings feature spec — User Story 8 (Private Bicep Recipes)](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md#user-story-8--private-bicep-recipes), +> [Available Terraform settings](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md#available-terraform-settings), +> [Reusable Terraform and Bicep config architecture doc](../../architecture/terraform-bicep-config.md). +> +> Product docs for the equivalent (legacy) feature, for additional context: +> [Private Terraform registry](https://docs.radapp.io/guides/recipes/terraform/howto-private-registry/), +> [Private Bicep registry](https://docs.radapp.io/guides/recipes/howto-private-bicep-registry/). + +--- + +## Cross-platform note (Linux / macOS / Windows) + +Every `rad`, `kubectl`, and `git` command in this guide is **identical on all +platforms**. Only the way you set shell variables differs. Where a step needs a +variable, both forms are shown: + +- **Linux / macOS (bash / zsh):** + ```bash + export REGISTRY="myregistry.azurecr.io" + ``` +- **Windows (PowerShell):** + ```powershell + $env:REGISTRY = "myregistry.azurecr.io" + ``` + +In commands below, `$REGISTRY` (bash) and `$env:REGISTRY` (PowerShell) refer to +the same value. Substitute your own values throughout. + +> The generated `.terraformrc` is written **inside the Radius control plane** and +> selected via `TF_CLI_CONFIG_FILE`, so Terraform behavior is identical +> regardless of the OS you run `rad` from. You do **not** need a local +> `.terraformrc` / `terraform.rc`. + +--- + +## Prerequisites + +1. A Kubernetes cluster and `kubectl` configured to talk to it. +2. The [`rad` CLI](https://docs.radapp.io/installation/) installed. +3. Radius installed on the cluster with the `Radius.Core` resource types + available (`rad install kubernetes`). +4. At least one **private** registry you control: + - For Scenario 1: a private **OCI** registry that can host Bicep artifacts + (e.g. Azure Container Registry, GitHub Container Registry, Amazon ECR). + - For Scenario 2: a private **Terraform** module registry or repository that + authenticates with a token (e.g. Terraform Cloud `app.terraform.io`, or a + self-hosted private module registry). +5. The Bicep tooling for `rad bicep publish` (bundled with `rad`). + +Verify your setup: + +```bash +rad version +kubectl get nodes +rad group create demo-private-registries +rad group switch demo-private-registries +``` + +Create the namespaces the environments deploy into (the `Radius.Core/environments` +resource requires the target namespace to already exist): + +```bash +kubectl create namespace private-bicep-demo +kubectl create namespace private-tf-demo +kubectl create namespace private-combined-demo +``` + +All Bicep templates referenced below live in [`./bicep`](./bicep) and the sample +recipe in [`./recipes`](./recipes). Run the commands from this demo directory. + +--- + +## Scenario 1 — Private Bicep recipe registry (OCI) + +**Goal:** publish a Bicep recipe to a *private* OCI registry and have Radius pull +it using credentials supplied through a `Radius.Core/bicepConfigs` resource. + +### 1.1 Publish the sample recipe to your private registry + +Set your registry details. + +- **Linux / macOS:** + ```bash + export BICEP_REGISTRY="myregistry.azurecr.io" + export BICEP_RECIPE="${BICEP_REGISTRY}/recipes/redis:latest" + ``` +- **Windows (PowerShell):** + ```powershell + $env:BICEP_REGISTRY = "myregistry.azurecr.io" + $env:BICEP_RECIPE = "$($env:BICEP_REGISTRY)/recipes/redis:latest" + ``` + +Authenticate your local tooling to the registry once so you can push the +artifact (example for Azure Container Registry — use the equivalent for your +provider): + +```bash +az acr login --name myregistry +``` + +Publish the recipe: + +- **Linux / macOS:** + ```bash + rad bicep publish --file ./recipes/redis-recipe.bicep --target "br:${BICEP_RECIPE}" + ``` +- **Windows (PowerShell):** + ```powershell + rad bicep publish --file ./recipes/redis-recipe.bicep --target "br:$($env:BICEP_RECIPE)" + ``` + +### 1.2 Create registry credentials + +For `BasicAuth`, you need a username and password the cluster can use to pull +from the registry. For ACR this can be an +[ACR token](https://learn.microsoft.com/azure/container-registry/container-registry-repository-scoped-permissions) +or a service principal. Capture them: + +- **Linux / macOS:** + ```bash + export BICEP_REGISTRY_USERNAME="" + export BICEP_REGISTRY_PASSWORD="" + ``` +- **Windows (PowerShell):** + ```powershell + $env:BICEP_REGISTRY_USERNAME = "" + $env:BICEP_REGISTRY_PASSWORD = "" + ``` + +### 1.3 Deploy the environment + app + +The template [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) +creates the `SecretStore`, the `Radius.Core/bicepConfigs`, a `Radius.Core/recipePacks` +pointing at the private recipe, the `Radius.Core/environments` that references the +bicepConfig, and an app that runs the recipe. + +- **Linux / macOS:** + ```bash + rad deploy ./bicep/bicep-private-registry.bicep \ + --parameters registryHostname="$BICEP_REGISTRY" \ + --parameters recipeLocation="$BICEP_RECIPE" \ + --parameters registryUsername="$BICEP_REGISTRY_USERNAME" \ + --parameters registryPassword="$BICEP_REGISTRY_PASSWORD" + ``` +- **Windows (PowerShell):** + ```powershell + rad deploy ./bicep/bicep-private-registry.bicep ` + --parameters registryHostname="$env:BICEP_REGISTRY" ` + --parameters recipeLocation="$env:BICEP_RECIPE" ` + --parameters registryUsername="$env:BICEP_REGISTRY_USERNAME" ` + --parameters registryPassword="$env:BICEP_REGISTRY_PASSWORD" + ``` + +### 1.4 Verify + +```bash +rad resource list Applications.Core/extenders +kubectl get pods -n private-bicep-demo +``` + +A successful deployment means Radius authenticated to your **private** OCI +registry, pulled the Bicep recipe, and executed it — the Redis pod should be +`Running` in `private-bicep-demo`. + +--- + +## Scenario 2 — Private Terraform module registry / repository + +**Goal:** authenticate to a *private* Terraform module source via a token carried +by a `Radius.Core/terraformConfigs` resource. Radius renders a `.terraformrc` +`credentials` block for that registry at execution time. + +### 2.1 Gather registry details and token + +- **Linux / macOS:** + ```bash + export TF_REGISTRY_HOST="app.terraform.io" + export TF_RECIPE_LOCATION="app.terraform.io/my-org/redis/kubernetes" + export TF_REGISTRY_TOKEN="" + ``` +- **Windows (PowerShell):** + ```powershell + $env:TF_REGISTRY_HOST = "app.terraform.io" + $env:TF_RECIPE_LOCATION = "app.terraform.io/my-org/redis/kubernetes" + $env:TF_REGISTRY_TOKEN = "" + ``` + +> `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at — +> a private registry module address or an HTTP module archive URL. + +### 2.2 Deploy the environment + app + +The template [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) +creates a `SecretStore` (with a `token` key), a `Radius.Core/terraformConfigs` +that references it under `terraformrc.credentials` (and raises `TF_LOG`), a recipe +pack pointing at the private module, the environment, and an app. + +- **Linux / macOS:** + ```bash + rad deploy ./bicep/terraform-private-registry.bicep \ + --parameters terraformRegistryHostname="$TF_REGISTRY_HOST" \ + --parameters recipeLocation="$TF_RECIPE_LOCATION" \ + --parameters terraformRegistryToken="$TF_REGISTRY_TOKEN" + ``` +- **Windows (PowerShell):** + ```powershell + rad deploy ./bicep/terraform-private-registry.bicep ` + --parameters terraformRegistryHostname="$env:TF_REGISTRY_HOST" ` + --parameters recipeLocation="$env:TF_RECIPE_LOCATION" ` + --parameters terraformRegistryToken="$env:TF_REGISTRY_TOKEN" + ``` + +### 2.3 Verify + +```bash +rad resource list Applications.Core/extenders +kubectl get pods -n private-tf-demo +``` + +A successful deployment means Radius rendered a `.terraformrc` with your token, +authenticated to the **private** Terraform registry, downloaded the module, and +ran `terraform apply`. + +> **Tip — confirm the credentials were applied.** Because the config sets +> `TF_LOG: INFO`, the recipe execution logs include the Terraform run. Tail the +> recipe engine logs to observe the module download from your private host: +> ```bash +> kubectl logs -n radius-system deploy/applications-rp -f +> ``` + +--- + +## Scenario 3 — Combined (one environment, both private registries) + +**Goal:** demonstrate the reusability goal of #11798 — a single +`Radius.Core/environments` that references **both** a `terraformConfigs` and a +`bicepConfigs` resource. + +The template [`bicep/combined.bicep`](./bicep/combined.bicep) wires both configs +into one environment. + +- **Linux / macOS:** + ```bash + rad deploy ./bicep/combined.bicep \ + --parameters terraformRegistryHostname="$TF_REGISTRY_HOST" \ + --parameters terraformRecipeLocation="$TF_RECIPE_LOCATION" \ + --parameters terraformRegistryToken="$TF_REGISTRY_TOKEN" \ + --parameters bicepRegistryHostname="$BICEP_REGISTRY" \ + --parameters bicepRegistryUsername="$BICEP_REGISTRY_USERNAME" \ + --parameters bicepRegistryPassword="$BICEP_REGISTRY_PASSWORD" + ``` +- **Windows (PowerShell):** + ```powershell + rad deploy ./bicep/combined.bicep ` + --parameters terraformRegistryHostname="$env:TF_REGISTRY_HOST" ` + --parameters terraformRecipeLocation="$env:TF_RECIPE_LOCATION" ` + --parameters terraformRegistryToken="$env:TF_REGISTRY_TOKEN" ` + --parameters bicepRegistryHostname="$env:BICEP_REGISTRY" ` + --parameters bicepRegistryUsername="$env:BICEP_REGISTRY_USERNAME" ` + --parameters bicepRegistryPassword="$env:BICEP_REGISTRY_PASSWORD" + ``` + +Inspect the environment to confirm both config references resolved: + +```bash +rad resource show Radius.Core/environments combined-env +rad resource list Radius.Core/terraformConfigs +rad resource list Radius.Core/bicepConfigs +``` + +--- + +## How it maps to the new resource types + +```mermaid +graph LR + subgraph "Platform team (rad deploy)" + BC["bicepConfigs
(registryAuthentications)"] + TC["terraformConfigs
(terraformrc.credentials + env)"] + SS["SecretStore(s)"] + end + ENV["Radius.Core/environments
terraformConfig / bicepConfig refs"] + DRV["Terraform / Bicep recipe driver"] + + SS --> BC + SS --> TC + BC -->|resource ID| ENV + TC -->|resource ID| ENV + ENV -->|resolved at execution time| DRV + DRV -->|authenticated pull| REG["Private registries"] +``` + +- **PUT-time validation:** the environment controller validates the referenced + `terraformConfig` / `bicepConfig` IDs exist and returns `400 Bad Request` if not. +- **Execution-time resolution:** the config loader fetches the referenced configs + and bridges them into the shared `RecipeConfig` the existing drivers already + consume, so legacy `Applications.Core/environments` users are unaffected. +- **Secrets are never persisted** in the config resources; the driver resolves + `SecretStore` references at execution time. + +### Property mapping (legacy → new) + +| Legacy `Applications.Core/environments.recipeConfig` | New resource | +| --- | --- | +| `terraform.authentication` (registry token) | `Radius.Core/terraformConfigs` → `terraformrc.credentials` | +| `terraform.providers` provider config | `Radius.Core/terraformConfigs` → `terraformrc.providerInstallation` | +| `env` (Terraform env vars) | `Radius.Core/terraformConfigs` → `env` | +| `bicep.authentication..secret` | `Radius.Core/bicepConfigs` → `registryAuthentications.` | + +--- + +## Known limitations (as of this demo) + +These are intentional follow-ups tracked in the +[architecture doc](../../architecture/terraform-bicep-config.md#status-and-known-limitations); +the demo focuses on the paths that are wired end-to-end: + +- **Bicep auth method:** only `BasicAuth` (`basicAuthSecretId`) is threaded into + the driver today. `AzureWI` and `AwsIrsa` are accepted by the API and validated, + but are no-ops at execution time until the corresponding driver work lands. +- **Terraform `credentials`** is for HTTP-based Terraform CLI registry auth + (rendered as native `credentials "host" {}` blocks with a `token`). Git module + source PAT auth from `Radius.Core/environments` is a separate follow-up; for + Git PAT auth today, use the legacy `Applications.Core/environments` + `recipeConfig` path. +- **Delete protection / `referencedBy`** are not yet enforced/populated for these + config resources. + +--- + +## Cleanup + +```bash +rad app delete private-bicep-demo --yes +rad app delete private-tf-demo --yes +rad app delete private-combined-demo --yes + +rad group switch default +rad group delete demo-private-registries --yes + +kubectl delete namespace private-bicep-demo private-tf-demo private-combined-demo +``` + +--- + +## Files in this demo + +| Path | Description | +| --- | --- | +| [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) | Scenario 1 — private Bicep (OCI) registry via `bicepConfigs` (BasicAuth). | +| [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) | Scenario 2 — private Terraform registry via `terraformConfigs` (credentials token). | +| [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 — one environment referencing both configs. | +| [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) | Sample Bicep recipe to publish to your private OCI registry for Scenario 1. | diff --git a/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep new file mode 100644 index 00000000000..a6a2e637215 --- /dev/null +++ b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep @@ -0,0 +1,119 @@ +// ----------------------------------------------------------------------------- +// Scenario 1 - Private Bicep recipe registry (OCI, e.g. Azure Container Registry) +// +// Demonstrates pulling a Bicep Recipe from a *private* OCI registry by attaching +// a Radius.Core/bicepConfigs resource (BasicAuth) to a Radius.Core/environments. +// +// Replace the parameters below with the details of your own private registry. +// See ../README.md for the full step-by-step walkthrough. +// ----------------------------------------------------------------------------- + +extension radius + +@description('Name of the Radius Application to create.') +param appName string = 'private-bicep-demo' + +@description('Kubernetes namespace the environment deploys into. Must already exist.') +param kubernetesNamespace string = 'private-bicep-demo' + +@description('Hostname of the private OCI registry that stores the Bicep recipe, e.g. "myregistry.azurecr.io".') +param registryHostname string + +@description('Full OCI path to the published Bicep recipe, e.g. "myregistry.azurecr.io/recipes/myredis:latest".') +param recipeLocation string + +@description('Username used for BasicAuth against the private registry (for ACR this can be an ACR token name or a service principal app ID).') +@secure() +param registryUsername string + +@description('Password used for BasicAuth against the private registry (for ACR this can be an ACR token password or a service principal secret).') +@secure() +param registryPassword string + +// SecretStore holding the username/password used to authenticate to the private +// OCI registry. For BasicAuth the secret store must expose 'username' and 'password'. +resource registrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { + name: 'private-bicep-registry-secret' + location: 'global' + properties: { + resource: '${kubernetesNamespace}/private-bicep-registry-secret' + type: 'generic' + data: { + username: { + value: registryUsername + } + password: { + value: registryPassword + } + } + } +} + +// BicepConfig carrying the registry authentication. The map key is the registry +// hostname; the driver uses these credentials when pulling Bicep recipes hosted +// on that registry. +resource bicepConfig 'Radius.Core/bicepConfigs@2025-08-01-preview' = { + name: 'private-bicep-config' + location: 'global' + properties: { + registryAuthentications: { + '${registryHostname}': { + authenticationMethod: 'BasicAuth' + basicAuthSecretId: registrySecret.id + } + } + } +} + +// RecipePack pointing at the *private* Bicep recipe. Because the environment +// references the bicepConfig above, Radius authenticates to the registry when +// the recipe is executed. +resource recipePack 'Radius.Core/recipePacks@2025-08-01-preview' = { + name: 'private-bicep-recipe-pack' + location: 'global' + properties: { + recipes: { + 'Applications.Core/extenders': { + recipeKind: 'bicep' + recipeLocation: recipeLocation + } + } + } +} + +// Environment that references both the recipe pack and the bicep registry config. +resource env 'Radius.Core/environments@2025-08-01-preview' = { + name: 'private-bicep-env' + location: 'global' + properties: { + recipePacks: [ + recipePack.id + ] + providers: { + kubernetes: { + namespace: kubernetesNamespace + } + } + bicepConfig: bicepConfig.id + } +} + +resource app 'Applications.Core/applications@2023-10-01-preview' = { + name: appName + location: 'global' + properties: { + environment: env.id + } +} + +// Application resource that triggers the private Bicep recipe. +resource demo 'Applications.Core/extenders@2023-10-01-preview' = { + name: '${appName}-resource' + properties: { + application: app.id + environment: env.id + recipe: { + name: 'default' + } + } +} diff --git a/docs/demos/private-registries-recipes/bicep/combined.bicep b/docs/demos/private-registries-recipes/bicep/combined.bicep new file mode 100644 index 00000000000..d8491ff7ef4 --- /dev/null +++ b/docs/demos/private-registries-recipes/bicep/combined.bicep @@ -0,0 +1,168 @@ +// ----------------------------------------------------------------------------- +// Scenario 3 - Combined: one environment, private Terraform AND private Bicep +// +// A single Radius.Core/environments references BOTH a Radius.Core/terraformConfigs +// (private Terraform registry credentials + env vars) and a Radius.Core/bicepConfigs +// (private OCI/Bicep registry BasicAuth). This mirrors a platform team that +// defines registry authentication once and reuses it across recipe kinds. +// +// Replace the parameters below with the details of your own private registries. +// See ../README.md for the full step-by-step walkthrough. +// ----------------------------------------------------------------------------- + +extension radius + +@description('Name of the Radius Application to create.') +param appName string = 'private-combined-demo' + +@description('Kubernetes namespace the environment deploys into. Must already exist.') +param kubernetesNamespace string = 'private-combined-demo' + +// --- Private Terraform registry inputs --- +@description('Hostname of the private Terraform registry that requires a token.') +param terraformRegistryHostname string + +@description('URL of the private Terraform module source (recipeLocation).') +param terraformRecipeLocation string + +@description('Token used to authenticate to the private Terraform registry.') +@secure() +param terraformRegistryToken string + +@description('Name of the Redis cache the example Terraform recipe provisions.') +param redisCacheName string = 'tf-combined-redis' + +// --- Private Bicep (OCI) registry inputs --- +@description('Hostname of the private OCI registry that stores Bicep recipes.') +param bicepRegistryHostname string + +@description('Username used for BasicAuth against the private OCI registry.') +@secure() +param bicepRegistryUsername string + +@description('Password used for BasicAuth against the private OCI registry.') +@secure() +param bicepRegistryPassword string + +// SecretStore for the Terraform registry token (key: token). +resource terraformTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { + name: 'combined-tf-token-secret' + location: 'global' + properties: { + resource: '${kubernetesNamespace}/combined-tf-token-secret' + type: 'generic' + data: { + token: { + value: terraformRegistryToken + } + } + } +} + +// SecretStore for the Bicep OCI registry BasicAuth (keys: username, password). +resource bicepRegistrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { + name: 'combined-bicep-registry-secret' + location: 'global' + properties: { + resource: '${kubernetesNamespace}/combined-bicep-registry-secret' + type: 'generic' + data: { + username: { + value: bicepRegistryUsername + } + password: { + value: bicepRegistryPassword + } + } + } +} + +resource tfConfig 'Radius.Core/terraformConfigs@2025-08-01-preview' = { + name: 'combined-tf-config' + location: 'global' + properties: { + terraformrc: { + providerInstallation: { + direct: { + include: [ + '*/*' + ] + } + } + credentials: { + '${terraformRegistryHostname}': { + secret: terraformTokenSecret.id + } + } + } + env: { + TF_LOG: 'INFO' + } + } +} + +resource bicepConfig 'Radius.Core/bicepConfigs@2025-08-01-preview' = { + name: 'combined-bicep-config' + location: 'global' + properties: { + registryAuthentications: { + '${bicepRegistryHostname}': { + authenticationMethod: 'BasicAuth' + basicAuthSecretId: bicepRegistrySecret.id + } + } + } +} + +resource recipePack 'Radius.Core/recipePacks@2025-08-01-preview' = { + name: 'combined-recipe-pack' + location: 'global' + properties: { + recipes: { + 'Applications.Core/extenders': { + recipeKind: 'terraform' + recipeLocation: terraformRecipeLocation + } + } + } +} + +// One environment, two config resources, both reused from a single definition. +resource env 'Radius.Core/environments@2025-08-01-preview' = { + name: 'combined-env' + location: 'global' + properties: { + recipePacks: [ + recipePack.id + ] + providers: { + kubernetes: { + namespace: kubernetesNamespace + } + } + terraformConfig: tfConfig.id + bicepConfig: bicepConfig.id + } +} + +resource app 'Applications.Core/applications@2023-10-01-preview' = { + name: appName + location: 'global' + properties: { + environment: env.id + } +} + +resource demo 'Applications.Core/extenders@2023-10-01-preview' = { + name: '${appName}-resource' + properties: { + application: app.id + environment: env.id + recipe: { + name: 'default' + parameters: { + redis_cache_name: redisCacheName + } + } + } +} diff --git a/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep new file mode 100644 index 00000000000..6d8f6e7cbbe --- /dev/null +++ b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep @@ -0,0 +1,133 @@ +// ----------------------------------------------------------------------------- +// Scenario 2 - Private Terraform module registry / repository +// +// Demonstrates authenticating to a *private* Terraform module source by attaching +// a Radius.Core/terraformConfigs resource to a Radius.Core/environments. The +// terraformConfig renders a .terraformrc (credentials + provider_installation) +// at recipe execution time and points Terraform at it via TF_CLI_CONFIG_FILE. +// +// Replace the parameters below with the details of your own private registry. +// See ../README.md for the full step-by-step walkthrough. +// ----------------------------------------------------------------------------- + +extension radius + +@description('Name of the Radius Application to create.') +param appName string = 'private-tf-demo' + +@description('Kubernetes namespace the environment deploys into. Must already exist.') +param kubernetesNamespace string = 'private-tf-demo' + +@description('Hostname of the private Terraform registry that requires a token, e.g. "app.terraform.io" or "registry.mycompany.com".') +param terraformRegistryHostname string + +@description('URL of the private Terraform module source (recipeLocation). For example a module published to a private registry or an HTTP module archive.') +param recipeLocation string + +@description('Token used to authenticate to the private Terraform registry.') +@secure() +param terraformRegistryToken string + +@description('Name of the Redis cache the example Terraform recipe provisions.') +param redisCacheName string = 'tf-private-redis' + +// SecretStore holding the Terraform registry token. The terraformConfig +// credentials block references this; the secret store must expose a 'token' key. +resource registryTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { + name: 'private-tf-token-secret' + location: 'global' + properties: { + resource: '${kubernetesNamespace}/private-tf-token-secret' + type: 'generic' + data: { + token: { + value: terraformRegistryToken + } + } + } +} + +// TerraformConfig that: +// * authenticates to the private Terraform registry (credentials block), and +// * keeps provider installation on the default "direct" path (fetch providers +// from the public registry). Swap "direct" for "networkMirror" if your +// providers also live behind a private mirror. +// * injects env vars into the Terraform process (here, raise the log level). +resource tfConfig 'Radius.Core/terraformConfigs@2025-08-01-preview' = { + name: 'private-tf-config' + location: 'global' + properties: { + terraformrc: { + providerInstallation: { + direct: { + include: [ + '*/*' + ] + } + } + credentials: { + '${terraformRegistryHostname}': { + secret: registryTokenSecret.id + } + } + } + env: { + TF_LOG: 'INFO' + TF_REGISTRY_CLIENT_TIMEOUT: '15' + } + } +} + +// RecipePack pointing at the Terraform module hosted in the private registry. +resource recipePack 'Radius.Core/recipePacks@2025-08-01-preview' = { + name: 'private-tf-recipe-pack' + location: 'global' + properties: { + recipes: { + 'Applications.Core/extenders': { + recipeKind: 'terraform' + recipeLocation: recipeLocation + } + } + } +} + +// Environment that references both the recipe pack and the Terraform config. +resource env 'Radius.Core/environments@2025-08-01-preview' = { + name: 'private-tf-env' + location: 'global' + properties: { + recipePacks: [ + recipePack.id + ] + providers: { + kubernetes: { + namespace: kubernetesNamespace + } + } + terraformConfig: tfConfig.id + } +} + +resource app 'Applications.Core/applications@2023-10-01-preview' = { + name: appName + location: 'global' + properties: { + environment: env.id + } +} + +// Application resource that triggers the private Terraform recipe. +resource demo 'Applications.Core/extenders@2023-10-01-preview' = { + name: '${appName}-resource' + properties: { + application: app.id + environment: env.id + recipe: { + name: 'default' + parameters: { + redis_cache_name: redisCacheName + } + } + } +} diff --git a/docs/demos/private-registries-recipes/recipes/redis-recipe.bicep b/docs/demos/private-registries-recipes/recipes/redis-recipe.bicep new file mode 100644 index 00000000000..55f394cd104 --- /dev/null +++ b/docs/demos/private-registries-recipes/recipes/redis-recipe.bicep @@ -0,0 +1,88 @@ +// ----------------------------------------------------------------------------- +// Sample Bicep recipe for the private-registry demo (Scenario 1). +// +// Publish this to your *private* OCI registry with `rad bicep publish`, then +// point the RecipePack's recipeLocation at the published artifact. See +// ../README.md for the publishing commands. +// +// The recipe provisions a small Redis Deployment + Service on Kubernetes and +// returns its connection values, so the consuming Applications.Core/extenders +// resource gets a working result back. +// ----------------------------------------------------------------------------- + +extension kubernetes with { + kubeConfig: '' + namespace: context.runtime.kubernetes.namespace +} as kubernetes + +@description('Information about the resource calling the Recipe. Provided by Radius.') +param context object + +@description('Container image to run for the Redis cache.') +param image string = 'redis:7' + +resource redis 'apps/Deployment@v1' = { + metadata: { + name: 'redis-${uniqueString(context.resource.id)}' + } + spec: { + selector: { + matchLabels: { + app: 'redis' + resource: context.resource.name + } + } + template: { + metadata: { + labels: { + app: 'redis' + resource: context.resource.name + } + } + spec: { + containers: [ + { + name: 'redis' + image: image + ports: [ + { + containerPort: 6379 + } + ] + } + ] + } + } + } +} + +resource svc 'core/Service@v1' = { + metadata: { + name: 'redis-${uniqueString(context.resource.id)}' + } + spec: { + type: 'ClusterIP' + selector: { + app: 'redis' + resource: context.resource.name + } + ports: [ + { + port: 6379 + } + ] + } +} + +output result object = { + // The deployment engine omits Kubernetes resources from its output, so we + // surface them explicitly here. + resources: [ + '/planes/kubernetes/local/namespaces/${svc.metadata.namespace}/providers/core/Service/${svc.metadata.name}' + '/planes/kubernetes/local/namespaces/${redis.metadata.namespace}/providers/apps/Deployment/${redis.metadata.name}' + ] + values: { + host: '${svc.metadata.name}.${svc.metadata.namespace}.svc.cluster.local' + port: 6379 + } +} From e2d3a2eccf24b3197202539a013177049668bbc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:58:36 +0000 Subject: [PATCH 02/11] Add cross-platform E2E execution scripts for private-registries demo --- .../private-registries-recipes/README.md | 58 ++++ .../scripts/run-e2e.ps1 | 214 +++++++++++++++ .../scripts/run-e2e.sh | 247 ++++++++++++++++++ 3 files changed, 519 insertions(+) create mode 100644 docs/demos/private-registries-recipes/scripts/run-e2e.ps1 create mode 100755 docs/demos/private-registries-recipes/scripts/run-e2e.sh diff --git a/docs/demos/private-registries-recipes/README.md b/docs/demos/private-registries-recipes/README.md index 887bf8aeb37..5fdc0e0db89 100644 --- a/docs/demos/private-registries-recipes/README.md +++ b/docs/demos/private-registries-recipes/README.md @@ -96,6 +96,12 @@ kubectl create namespace private-combined-demo All Bicep templates referenced below live in [`./bicep`](./bicep) and the sample recipe in [`./recipes`](./recipes). Run the commands from this demo directory. +> **Prefer to automate it?** The [`./scripts`](./scripts) folder contains an +> end-to-end runner that performs every step below (group + namespace setup, +> recipe publish, deploy, and verify) for a chosen scenario. See +> [Automated E2E runner](#automated-e2e-runner). The manual steps that follow +> document exactly what those scripts do. + --- ## Scenario 1 — Private Bicep recipe registry (OCI) @@ -356,6 +362,56 @@ the demo focuses on the paths that are wired end-to-end: --- +## Automated E2E runner + +The [`./scripts`](./scripts) folder provides a cross-platform runner that +executes the whole walkthrough non-interactively — ideal for E2E validation: + +| Platform | Script | +| --- | --- | +| Linux / macOS | [`scripts/run-e2e.sh`](./scripts/run-e2e.sh) | +| Windows (PowerShell) | [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | + +Both scripts read the **same environment variables** documented in the scenarios +above, create the Radius group and namespaces, publish the sample Bicep recipe +(for the Bicep/combined scenarios), deploy the selected scenario, and verify it. + +Set the variables for the scenario(s) you want, then run: + +- **Linux / macOS:** + ```bash + export BICEP_REGISTRY="myregistry.azurecr.io" + export BICEP_RECIPE="${BICEP_REGISTRY}/recipes/redis:latest" + export BICEP_REGISTRY_USERNAME="" + export BICEP_REGISTRY_PASSWORD="" + export TF_REGISTRY_HOST="app.terraform.io" + export TF_RECIPE_LOCATION="app.terraform.io/my-org/redis/kubernetes" + export TF_REGISTRY_TOKEN="" + + ./scripts/run-e2e.sh --scenario bicep # or terraform | combined | all + ./scripts/run-e2e.sh --cleanup # tear everything down + ``` +- **Windows (PowerShell):** + ```powershell + $env:BICEP_REGISTRY = "myregistry.azurecr.io" + $env:BICEP_RECIPE = "$($env:BICEP_REGISTRY)/recipes/redis:latest" + $env:BICEP_REGISTRY_USERNAME = "" + $env:BICEP_REGISTRY_PASSWORD = "" + $env:TF_REGISTRY_HOST = "app.terraform.io" + $env:TF_RECIPE_LOCATION = "app.terraform.io/my-org/redis/kubernetes" + $env:TF_REGISTRY_TOKEN = "" + + .\scripts\run-e2e.ps1 -Scenario bicep # or terraform | combined | all + .\scripts\run-e2e.ps1 -Cleanup # tear everything down + ``` + +Useful flags: `--scenario` / `-Scenario` selects `bicep`, `terraform`, +`combined`, or `all`; `--skip-publish` / `-SkipPublish` reuses an already-pushed +recipe; `--cleanup` / `-Cleanup` deletes all demo resources. Pass `--help` (bash) +or `Get-Help .\scripts\run-e2e.ps1` (PowerShell) for full details. + +--- + ## Cleanup ```bash @@ -379,3 +435,5 @@ kubectl delete namespace private-bicep-demo private-tf-demo private-combined-dem | [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) | Scenario 2 — private Terraform registry via `terraformConfigs` (credentials token). | | [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 — one environment referencing both configs. | | [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) | Sample Bicep recipe to publish to your private OCI registry for Scenario 1. | +| [`scripts/run-e2e.sh`](./scripts/run-e2e.sh) | Linux / macOS E2E runner that automates the full walkthrough. | +| [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | Windows (PowerShell) E2E runner that automates the full walkthrough. | diff --git a/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 new file mode 100644 index 00000000000..04e4ebe51c3 --- /dev/null +++ b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 @@ -0,0 +1,214 @@ +#Requires -Version 5.1 + +<# +.SYNOPSIS + E2E runner for the "Private Registries & Repositories" demo (Windows). + +.DESCRIPTION + Automates the manual walkthrough in ..\README.md: creates the Radius group + and namespaces, optionally publishes the sample Bicep recipe, deploys the + environment + app for the selected scenario, and verifies the result. + + Configuration is supplied through environment variables (see .PARAMETER notes + and ..\README.md) so secrets stay off the command line. + +.PARAMETER Scenario + Scenario to run: bicep, terraform, combined, or all. Default: all. + +.PARAMETER SkipPublish + Skip 'rad bicep publish' (recipe already pushed). + +.PARAMETER Cleanup + Delete all demo resources and exit. + +.EXAMPLE + .\run-e2e.ps1 -Scenario bicep + +.EXAMPLE + .\run-e2e.ps1 -Scenario terraform -SkipPublish + +.EXAMPLE + .\run-e2e.ps1 -Cleanup + +.NOTES + Environment variables: + Scenario 1 (bicep / combined): + BICEP_REGISTRY, BICEP_RECIPE, + BICEP_REGISTRY_USERNAME, BICEP_REGISTRY_PASSWORD + Scenario 2 (terraform / combined): + TF_REGISTRY_HOST, TF_RECIPE_LOCATION, TF_REGISTRY_TOKEN +#> + +[CmdletBinding()] +param( + [ValidateSet('bicep', 'terraform', 'combined', 'all')] + [string]$Scenario = 'all', + + [switch]$SkipPublish, + + [switch]$Cleanup +) + +$ErrorActionPreference = 'Stop' + +$DemoDir = Split-Path -Parent $PSScriptRoot +$BicepDir = Join-Path $DemoDir 'bicep' +$RecipesDir = Join-Path $DemoDir 'recipes' + +$RadGroup = 'demo-private-registries' +$BicepNamespace = 'private-bicep-demo' +$TfNamespace = 'private-tf-demo' +$CombinedNamespace = 'private-combined-demo' + +function Test-Tools { + foreach ($tool in @('rad', 'kubectl')) { + if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { + throw "Required tool '$tool' not found on PATH" + } + } +} + +function Assert-Vars { + param([string[]]$Names) + $missing = @() + foreach ($name in $Names) { + $value = [Environment]::GetEnvironmentVariable($name) + if ([string]::IsNullOrEmpty($value)) { + $missing += $name + } + } + if ($missing.Count -gt 0) { + throw "Missing required variables: $($missing -join ', ')" + } +} + +function Get-Var { + param([string]$Name) + return [Environment]::GetEnvironmentVariable($Name) +} + +function Confirm-Namespace { + param([string]$Namespace) + kubectl get namespace $Namespace 2>$null | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host "Creating namespace $Namespace" + kubectl create namespace $Namespace + } +} + +function Initialize-Group { + rad group create $RadGroup 2>$null | Out-Null + rad group switch $RadGroup +} + +function Invoke-Bicep { + Assert-Vars @('BICEP_REGISTRY', 'BICEP_RECIPE', + 'BICEP_REGISTRY_USERNAME', 'BICEP_REGISTRY_PASSWORD') + Confirm-Namespace $BicepNamespace + + if (-not $SkipPublish) { + Write-Host "Publishing Bicep recipe to $(Get-Var 'BICEP_RECIPE')" + rad bicep publish ` + --file (Join-Path $RecipesDir 'redis-recipe.bicep') ` + --target "br:$(Get-Var 'BICEP_RECIPE')" + } + + Write-Host 'Deploying Scenario 1 (private Bicep registry)' + rad deploy (Join-Path $BicepDir 'bicep-private-registry.bicep') ` + --parameters registryHostname="$(Get-Var 'BICEP_REGISTRY')" ` + --parameters recipeLocation="$(Get-Var 'BICEP_RECIPE')" ` + --parameters registryUsername="$(Get-Var 'BICEP_REGISTRY_USERNAME')" ` + --parameters registryPassword="$(Get-Var 'BICEP_REGISTRY_PASSWORD')" + + Write-Host 'Verifying Scenario 1' + rad resource list Applications.Core/extenders + kubectl get pods -n $BicepNamespace +} + +function Invoke-Terraform { + Assert-Vars @('TF_REGISTRY_HOST', 'TF_RECIPE_LOCATION', 'TF_REGISTRY_TOKEN') + Confirm-Namespace $TfNamespace + + Write-Host 'Deploying Scenario 2 (private Terraform registry)' + rad deploy (Join-Path $BicepDir 'terraform-private-registry.bicep') ` + --parameters terraformRegistryHostname="$(Get-Var 'TF_REGISTRY_HOST')" ` + --parameters recipeLocation="$(Get-Var 'TF_RECIPE_LOCATION')" ` + --parameters terraformRegistryToken="$(Get-Var 'TF_REGISTRY_TOKEN')" + + Write-Host 'Verifying Scenario 2' + rad resource list Applications.Core/extenders + kubectl get pods -n $TfNamespace +} + +function Invoke-Combined { + Assert-Vars @('BICEP_REGISTRY', 'BICEP_RECIPE', + 'BICEP_REGISTRY_USERNAME', 'BICEP_REGISTRY_PASSWORD', + 'TF_REGISTRY_HOST', 'TF_RECIPE_LOCATION', 'TF_REGISTRY_TOKEN') + Confirm-Namespace $CombinedNamespace + + if (-not $SkipPublish) { + Write-Host "Publishing Bicep recipe to $(Get-Var 'BICEP_RECIPE')" + rad bicep publish ` + --file (Join-Path $RecipesDir 'redis-recipe.bicep') ` + --target "br:$(Get-Var 'BICEP_RECIPE')" + } + + Write-Host 'Deploying Scenario 3 (combined)' + rad deploy (Join-Path $BicepDir 'combined.bicep') ` + --parameters terraformRegistryHostname="$(Get-Var 'TF_REGISTRY_HOST')" ` + --parameters terraformRecipeLocation="$(Get-Var 'TF_RECIPE_LOCATION')" ` + --parameters terraformRegistryToken="$(Get-Var 'TF_REGISTRY_TOKEN')" ` + --parameters bicepRegistryHostname="$(Get-Var 'BICEP_REGISTRY')" ` + --parameters bicepRegistryUsername="$(Get-Var 'BICEP_REGISTRY_USERNAME')" ` + --parameters bicepRegistryPassword="$(Get-Var 'BICEP_REGISTRY_PASSWORD')" + + Write-Host 'Verifying Scenario 3' + rad resource show Radius.Core/environments combined-env + rad resource list Radius.Core/terraformConfigs + rad resource list Radius.Core/bicepConfigs +} + +function Remove-Demo { + Write-Host 'Cleaning up demo resources' + rad group switch $RadGroup 2>$null + rad app delete $BicepNamespace --yes 2>$null + rad app delete $TfNamespace --yes 2>$null + rad app delete $CombinedNamespace --yes 2>$null + rad group switch default 2>$null + rad group delete $RadGroup --yes 2>$null + kubectl delete namespace ` + $BicepNamespace $TfNamespace $CombinedNamespace --ignore-not-found + Write-Host 'Cleanup complete' +} + +function Main { + Test-Tools + + if ($Cleanup) { + Remove-Demo + return + } + + Write-Host '============================================================' + Write-Host "Private registries E2E demo - scenario: $Scenario" + Write-Host '============================================================' + + Initialize-Group + + switch ($Scenario) { + 'bicep' { Invoke-Bicep } + 'terraform' { Invoke-Terraform } + 'combined' { Invoke-Combined } + 'all' { + Invoke-Bicep + Invoke-Terraform + Invoke-Combined + } + } + + Write-Host '============================================================' + Write-Host "Done. Run '.\run-e2e.ps1 -Cleanup' to remove demo resources." + Write-Host '============================================================' +} + +Main diff --git a/docs/demos/private-registries-recipes/scripts/run-e2e.sh b/docs/demos/private-registries-recipes/scripts/run-e2e.sh new file mode 100755 index 00000000000..3c61d84a9fb --- /dev/null +++ b/docs/demos/private-registries-recipes/scripts/run-e2e.sh @@ -0,0 +1,247 @@ +#!/bin/bash + +# ============================================================================ +# E2E runner for the "Private Registries & Repositories" demo (Linux / macOS). +# +# Automates the manual walkthrough in ../README.md: it creates the Radius group +# and namespaces, optionally publishes the sample Bicep recipe, deploys the +# environment + app for the selected scenario, and verifies the result. +# +# Configuration is supplied through environment variables (see usage). This +# keeps secrets out of the command line and mirrors the variables used in the +# README. +# ============================================================================ + +set -euo pipefail + +SCRIPT_NAME="$(basename "$0")" +readonly SCRIPT_NAME +# Demo root is the parent of the directory holding this script. +DEMO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +readonly DEMO_DIR +readonly BICEP_DIR="${DEMO_DIR}/bicep" +readonly RECIPES_DIR="${DEMO_DIR}/recipes" + +readonly RAD_GROUP="demo-private-registries" +readonly BICEP_NAMESPACE="private-bicep-demo" +readonly TF_NAMESPACE="private-tf-demo" +readonly COMBINED_NAMESPACE="private-combined-demo" + +# Default values +SCENARIO="all" +DO_CLEANUP="false" +PUBLISH_RECIPE="true" + +usage() { + cat < Scenario to run: bicep | terraform | combined | all + (default: all) + --skip-publish Skip 'rad bicep publish' (recipe already pushed) + -c, --cleanup Delete all demo resources and exit + -h, --help Show this help + +Environment variables: + Scenario 1 (bicep / combined): + BICEP_REGISTRY Private OCI registry host, e.g. myreg.azurecr.io + BICEP_RECIPE Full OCI path to the recipe, e.g. + myreg.azurecr.io/recipes/redis:latest + BICEP_REGISTRY_USERNAME BasicAuth username + BICEP_REGISTRY_PASSWORD BasicAuth password + + Scenario 2 (terraform / combined): + TF_REGISTRY_HOST Private Terraform registry host + TF_RECIPE_LOCATION Terraform module source (recipeLocation) + TF_REGISTRY_TOKEN Terraform registry token + +Examples: + ${SCRIPT_NAME} --scenario bicep + ${SCRIPT_NAME} --scenario terraform --skip-publish + ${SCRIPT_NAME} --cleanup +EOF + exit 0 +} + +require_tools() { + local tool + for tool in rad kubectl; do + if ! command -v "${tool}" >/dev/null 2>&1; then + echo "Error: required tool '${tool}' not found on PATH" >&2 + exit 1 + fi + done +} + +require_vars() { + local name + local missing=() + for name in "$@"; do + if [[ -z "${!name:-}" ]]; then + missing+=("${name}") + fi + done + if [[ ${#missing[@]} -gt 0 ]]; then + echo "Error: missing required variables: ${missing[*]}" >&2 + exit 1 + fi +} + +ensure_namespace() { + local ns="$1" + if ! kubectl get namespace "${ns}" >/dev/null 2>&1; then + echo "Creating namespace ${ns}" + kubectl create namespace "${ns}" + fi +} + +setup_group() { + rad group create "${RAD_GROUP}" >/dev/null 2>&1 || true + rad group switch "${RAD_GROUP}" +} + +run_bicep() { + require_vars BICEP_REGISTRY BICEP_RECIPE \ + BICEP_REGISTRY_USERNAME BICEP_REGISTRY_PASSWORD + ensure_namespace "${BICEP_NAMESPACE}" + + if [[ "${PUBLISH_RECIPE}" == "true" ]]; then + echo "Publishing Bicep recipe to ${BICEP_RECIPE}" + rad bicep publish \ + --file "${RECIPES_DIR}/redis-recipe.bicep" \ + --target "br:${BICEP_RECIPE}" + fi + + echo "Deploying Scenario 1 (private Bicep registry)" + rad deploy "${BICEP_DIR}/bicep-private-registry.bicep" \ + --parameters registryHostname="${BICEP_REGISTRY}" \ + --parameters recipeLocation="${BICEP_RECIPE}" \ + --parameters registryUsername="${BICEP_REGISTRY_USERNAME}" \ + --parameters registryPassword="${BICEP_REGISTRY_PASSWORD}" + + echo "Verifying Scenario 1" + rad resource list Applications.Core/extenders + kubectl get pods -n "${BICEP_NAMESPACE}" +} + +run_terraform() { + require_vars TF_REGISTRY_HOST TF_RECIPE_LOCATION TF_REGISTRY_TOKEN + ensure_namespace "${TF_NAMESPACE}" + + echo "Deploying Scenario 2 (private Terraform registry)" + rad deploy "${BICEP_DIR}/terraform-private-registry.bicep" \ + --parameters terraformRegistryHostname="${TF_REGISTRY_HOST}" \ + --parameters recipeLocation="${TF_RECIPE_LOCATION}" \ + --parameters terraformRegistryToken="${TF_REGISTRY_TOKEN}" + + echo "Verifying Scenario 2" + rad resource list Applications.Core/extenders + kubectl get pods -n "${TF_NAMESPACE}" +} + +run_combined() { + require_vars BICEP_REGISTRY BICEP_RECIPE \ + BICEP_REGISTRY_USERNAME BICEP_REGISTRY_PASSWORD \ + TF_REGISTRY_HOST TF_RECIPE_LOCATION TF_REGISTRY_TOKEN + ensure_namespace "${COMBINED_NAMESPACE}" + + if [[ "${PUBLISH_RECIPE}" == "true" ]]; then + echo "Publishing Bicep recipe to ${BICEP_RECIPE}" + rad bicep publish \ + --file "${RECIPES_DIR}/redis-recipe.bicep" \ + --target "br:${BICEP_RECIPE}" + fi + + echo "Deploying Scenario 3 (combined)" + rad deploy "${BICEP_DIR}/combined.bicep" \ + --parameters terraformRegistryHostname="${TF_REGISTRY_HOST}" \ + --parameters terraformRecipeLocation="${TF_RECIPE_LOCATION}" \ + --parameters terraformRegistryToken="${TF_REGISTRY_TOKEN}" \ + --parameters bicepRegistryHostname="${BICEP_REGISTRY}" \ + --parameters bicepRegistryUsername="${BICEP_REGISTRY_USERNAME}" \ + --parameters bicepRegistryPassword="${BICEP_REGISTRY_PASSWORD}" + + echo "Verifying Scenario 3" + rad resource show Radius.Core/environments combined-env || true + rad resource list Radius.Core/terraformConfigs + rad resource list Radius.Core/bicepConfigs +} + +cleanup_demo() { + echo "Cleaning up demo resources" + rad group switch "${RAD_GROUP}" 2>/dev/null || true + rad app delete "${BICEP_NAMESPACE}" --yes 2>/dev/null || true + rad app delete "${TF_NAMESPACE}" --yes 2>/dev/null || true + rad app delete "${COMBINED_NAMESPACE}" --yes 2>/dev/null || true + rad group switch default 2>/dev/null || true + rad group delete "${RAD_GROUP}" --yes 2>/dev/null || true + kubectl delete namespace \ + "${BICEP_NAMESPACE}" "${TF_NAMESPACE}" "${COMBINED_NAMESPACE}" \ + --ignore-not-found + echo "Cleanup complete" +} + +main() { + require_tools + + if [[ "${DO_CLEANUP}" == "true" ]]; then + cleanup_demo + exit 0 + fi + + echo "============================================================" + echo "Private registries E2E demo - scenario: ${SCENARIO}" + echo "============================================================" + + setup_group + + case "${SCENARIO}" in + bicep) run_bicep ;; + terraform) run_terraform ;; + combined) run_combined ;; + all) + run_bicep + run_terraform + run_combined + ;; + *) + echo "Error: unknown scenario '${SCENARIO}'" >&2 + exit 1 + ;; + esac + + echo "============================================================" + echo "Done. Run '${SCRIPT_NAME} --cleanup' to remove demo resources." + echo "============================================================" +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -s | --scenario) + SCENARIO="$2" + shift 2 + ;; + --skip-publish) + PUBLISH_RECIPE="false" + shift + ;; + -c | --cleanup) + DO_CLEANUP="true" + shift + ;; + -h | --help) + usage + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +main From 7bfe86a53423f37945465dbbebd89b9f2a9efde1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:00:09 +0000 Subject: [PATCH 03/11] Address review: don't mask errors on group create / scenario-3 verify --- docs/demos/private-registries-recipes/scripts/run-e2e.ps1 | 3 ++- docs/demos/private-registries-recipes/scripts/run-e2e.sh | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 index 04e4ebe51c3..51697048956 100644 --- a/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 +++ b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 @@ -97,7 +97,8 @@ function Confirm-Namespace { } function Initialize-Group { - rad group create $RadGroup 2>$null | Out-Null + # Group may already exist; allow that but let 'rad group switch' validate. + rad group create $RadGroup | Out-Null rad group switch $RadGroup } diff --git a/docs/demos/private-registries-recipes/scripts/run-e2e.sh b/docs/demos/private-registries-recipes/scripts/run-e2e.sh index 3c61d84a9fb..292c66bfaae 100755 --- a/docs/demos/private-registries-recipes/scripts/run-e2e.sh +++ b/docs/demos/private-registries-recipes/scripts/run-e2e.sh @@ -100,7 +100,9 @@ ensure_namespace() { } setup_group() { - rad group create "${RAD_GROUP}" >/dev/null 2>&1 || true + # Group may already exist; suppress only the duplicate notice on stdout + # while letting real errors surface. 'rad group switch' below validates it. + rad group create "${RAD_GROUP}" >/dev/null || true rad group switch "${RAD_GROUP}" } @@ -166,7 +168,7 @@ run_combined() { --parameters bicepRegistryPassword="${BICEP_REGISTRY_PASSWORD}" echo "Verifying Scenario 3" - rad resource show Radius.Core/environments combined-env || true + rad resource show Radius.Core/environments combined-env rad resource list Radius.Core/terraformConfigs rad resource list Radius.Core/bicepConfigs } From a8078e569f43898aefaf3a87d0e3689d8290d586 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:03:44 +0000 Subject: [PATCH 04/11] Target cross-platform pwsh (PowerShell 7+) instead of Windows PowerShell --- .../private-registries-recipes/README.md | 37 +++++++++++-------- .../scripts/run-e2e.ps1 | 13 ++++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/docs/demos/private-registries-recipes/README.md b/docs/demos/private-registries-recipes/README.md index 5fdc0e0db89..956459cff8b 100644 --- a/docs/demos/private-registries-recipes/README.md +++ b/docs/demos/private-registries-recipes/README.md @@ -40,13 +40,15 @@ registries in the cloud**, across three scenarios: Every `rad`, `kubectl`, and `git` command in this guide is **identical on all platforms**. Only the way you set shell variables differs. Where a step needs a -variable, both forms are shown: +variable, both forms are shown. The PowerShell form uses +[`pwsh`](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) +(PowerShell 7+), which runs the same on Windows, Linux, and macOS: - **Linux / macOS (bash / zsh):** ```bash export REGISTRY="myregistry.azurecr.io" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell $env:REGISTRY = "myregistry.azurecr.io" ``` @@ -118,7 +120,7 @@ Set your registry details. export BICEP_REGISTRY="myregistry.azurecr.io" export BICEP_RECIPE="${BICEP_REGISTRY}/recipes/redis:latest" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell $env:BICEP_REGISTRY = "myregistry.azurecr.io" $env:BICEP_RECIPE = "$($env:BICEP_REGISTRY)/recipes/redis:latest" @@ -138,7 +140,7 @@ Publish the recipe: ```bash rad bicep publish --file ./recipes/redis-recipe.bicep --target "br:${BICEP_RECIPE}" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell rad bicep publish --file ./recipes/redis-recipe.bicep --target "br:$($env:BICEP_RECIPE)" ``` @@ -155,7 +157,7 @@ or a service principal. Capture them: export BICEP_REGISTRY_USERNAME="" export BICEP_REGISTRY_PASSWORD="" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell $env:BICEP_REGISTRY_USERNAME = "" $env:BICEP_REGISTRY_PASSWORD = "" @@ -176,7 +178,7 @@ bicepConfig, and an app that runs the recipe. --parameters registryUsername="$BICEP_REGISTRY_USERNAME" \ --parameters registryPassword="$BICEP_REGISTRY_PASSWORD" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell rad deploy ./bicep/bicep-private-registry.bicep ` --parameters registryHostname="$env:BICEP_REGISTRY" ` @@ -212,7 +214,7 @@ by a `Radius.Core/terraformConfigs` resource. Radius renders a `.terraformrc` export TF_RECIPE_LOCATION="app.terraform.io/my-org/redis/kubernetes" export TF_REGISTRY_TOKEN="" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell $env:TF_REGISTRY_HOST = "app.terraform.io" $env:TF_RECIPE_LOCATION = "app.terraform.io/my-org/redis/kubernetes" @@ -236,7 +238,7 @@ pack pointing at the private module, the environment, and an app. --parameters recipeLocation="$TF_RECIPE_LOCATION" \ --parameters terraformRegistryToken="$TF_REGISTRY_TOKEN" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell rad deploy ./bicep/terraform-private-registry.bicep ` --parameters terraformRegistryHostname="$env:TF_REGISTRY_HOST" ` @@ -283,7 +285,7 @@ into one environment. --parameters bicepRegistryUsername="$BICEP_REGISTRY_USERNAME" \ --parameters bicepRegistryPassword="$BICEP_REGISTRY_PASSWORD" ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell rad deploy ./bicep/combined.bicep ` --parameters terraformRegistryHostname="$env:TF_REGISTRY_HOST" ` @@ -369,8 +371,11 @@ executes the whole walkthrough non-interactively — ideal for E2E validation: | Platform | Script | | --- | --- | -| Linux / macOS | [`scripts/run-e2e.sh`](./scripts/run-e2e.sh) | -| Windows (PowerShell) | [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | +| Linux / macOS (bash) | [`scripts/run-e2e.sh`](./scripts/run-e2e.sh) | +| Any OS (PowerShell 7+ / pwsh) | [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | + +> The PowerShell runner requires [`pwsh`](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) +> (PowerShell 7+) and runs identically on Windows, Linux, and macOS. Both scripts read the **same environment variables** documented in the scenarios above, create the Radius group and namespaces, publish the sample Bicep recipe @@ -391,7 +396,7 @@ Set the variables for the scenario(s) you want, then run: ./scripts/run-e2e.sh --scenario bicep # or terraform | combined | all ./scripts/run-e2e.sh --cleanup # tear everything down ``` -- **Windows (PowerShell):** +- **PowerShell (pwsh, cross-platform):** ```powershell $env:BICEP_REGISTRY = "myregistry.azurecr.io" $env:BICEP_RECIPE = "$($env:BICEP_REGISTRY)/recipes/redis:latest" @@ -401,14 +406,14 @@ Set the variables for the scenario(s) you want, then run: $env:TF_RECIPE_LOCATION = "app.terraform.io/my-org/redis/kubernetes" $env:TF_REGISTRY_TOKEN = "" - .\scripts\run-e2e.ps1 -Scenario bicep # or terraform | combined | all - .\scripts\run-e2e.ps1 -Cleanup # tear everything down + pwsh ./scripts/run-e2e.ps1 -Scenario bicep # or terraform | combined | all + pwsh ./scripts/run-e2e.ps1 -Cleanup # tear everything down ``` Useful flags: `--scenario` / `-Scenario` selects `bicep`, `terraform`, `combined`, or `all`; `--skip-publish` / `-SkipPublish` reuses an already-pushed recipe; `--cleanup` / `-Cleanup` deletes all demo resources. Pass `--help` (bash) -or `Get-Help .\scripts\run-e2e.ps1` (PowerShell) for full details. +or `Get-Help ./scripts/run-e2e.ps1` (pwsh) for full details. --- @@ -436,4 +441,4 @@ kubectl delete namespace private-bicep-demo private-tf-demo private-combined-dem | [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 — one environment referencing both configs. | | [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) | Sample Bicep recipe to publish to your private OCI registry for Scenario 1. | | [`scripts/run-e2e.sh`](./scripts/run-e2e.sh) | Linux / macOS E2E runner that automates the full walkthrough. | -| [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | Windows (PowerShell) E2E runner that automates the full walkthrough. | +| [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | Cross-platform PowerShell (pwsh 7+) E2E runner that automates the full walkthrough. | diff --git a/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 index 51697048956..fae14490296 100644 --- a/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 +++ b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 @@ -1,8 +1,9 @@ -#Requires -Version 5.1 +#Requires -Version 7.0 <# .SYNOPSIS - E2E runner for the "Private Registries & Repositories" demo (Windows). + E2E runner for the "Private Registries & Repositories" demo (cross-platform + PowerShell / pwsh). .DESCRIPTION Automates the manual walkthrough in ..\README.md: creates the Radius group @@ -22,13 +23,13 @@ Delete all demo resources and exit. .EXAMPLE - .\run-e2e.ps1 -Scenario bicep + pwsh ./run-e2e.ps1 -Scenario bicep .EXAMPLE - .\run-e2e.ps1 -Scenario terraform -SkipPublish + pwsh ./run-e2e.ps1 -Scenario terraform -SkipPublish .EXAMPLE - .\run-e2e.ps1 -Cleanup + pwsh ./run-e2e.ps1 -Cleanup .NOTES Environment variables: @@ -208,7 +209,7 @@ function Main { } Write-Host '============================================================' - Write-Host "Done. Run '.\run-e2e.ps1 -Cleanup' to remove demo resources." + Write-Host "Done. Run 'pwsh ./run-e2e.ps1 -Cleanup' to remove demo resources." Write-Host '============================================================' } From f46c94881a675e848186ba3fe1d8917d5dec8d6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:17:27 +0000 Subject: [PATCH 05/11] Add prerequisites doc for setting up private registries for demo --- .../PREREQUISITES.md | 195 ++++++++++++++++++ .../private-registries-recipes/README.md | 4 + 2 files changed, 199 insertions(+) create mode 100644 docs/demos/private-registries-recipes/PREREQUISITES.md diff --git a/docs/demos/private-registries-recipes/PREREQUISITES.md b/docs/demos/private-registries-recipes/PREREQUISITES.md new file mode 100644 index 00000000000..6e40496f241 --- /dev/null +++ b/docs/demos/private-registries-recipes/PREREQUISITES.md @@ -0,0 +1,195 @@ +# Prerequisites: Setting up private registries for the demo + +This guide covers the **high-level steps to stand up the private registries** used +by the [Private Registries & Repositories demo](./README.md). The demo proves that +Radius can pull recipes from registries that require authentication, so you need +**at least one private registry you control** for each scenario you want to run: + +- **Scenario 1 (Bicep):** a private **OCI** registry that hosts Bicep artifacts. +- **Scenario 2 (Terraform):** a private **Terraform** module registry/repository + that authenticates with a token. +- **Scenario 3 (Combined):** both of the above. + +You do **not** need every provider listed below — pick whichever one you already +have access to. Each section is a short, provider-agnostic checklist plus one +concrete example. Once a registry is ready, return to the +[demo README](./README.md) and supply the values it produced. + +> These steps are for **demoing/testing only**. They favor the fastest path to a +> working private registry, not production hardening. Use throwaway/scoped +> credentials and tear them down afterward (see [Cleanup](#cleanup)). + +--- + +## Base tooling + +Before setting up any registry, make sure you have: + +1. A Kubernetes cluster and `kubectl` configured to talk to it. +2. The [`rad` CLI](https://docs.radapp.io/installation/) installed + (`rad version` works). The Bicep tooling for `rad bicep publish` is bundled. +3. Radius installed on the cluster with the `Radius.Core` resource types + available (`rad install kubernetes`). +4. The CLI for whichever registry provider you choose (e.g. `az`, `gh`, `aws`, + or `terraform`). + +--- + +## Part A — Private Bicep (OCI) registry + +**Goal:** a private OCI registry you can `rad bicep publish` to, and a +username/password pair the cluster can use to pull from it (the demo's Bicep +scenarios use `BasicAuth`). + +### High-level steps (any OCI provider) + +1. **Create a private OCI registry.** Any registry that speaks the OCI artifact + spec works — Azure Container Registry (ACR), GitHub Container Registry (GHCR), + Amazon ECR, Google Artifact Registry, Docker Hub (private repo), Harbor, etc. + Ensure it is **private** (not anonymously pullable) so the demo actually + exercises authentication. +2. **Create scoped pull credentials.** Generate a username + password/token that + has at least **pull** rights on the recipe repository (and **push** rights if + the *same* identity will publish the recipe). Prefer a scoped token over an + admin account. +3. **Authenticate your local tooling once** so you can push the recipe artifact + (e.g. `az acr login`, `docker login`, `gh auth token | docker login`). +4. **Record these values** — you'll pass them to the demo: + - `BICEP_REGISTRY` — the registry hostname, e.g. `myregistry.azurecr.io`. + - `BICEP_RECIPE` — the full artifact reference, e.g. + `myregistry.azurecr.io/recipes/redis:latest`. + - `BICEP_REGISTRY_USERNAME` / `BICEP_REGISTRY_PASSWORD` — the pull credentials. + +### Concrete example — Azure Container Registry (ACR) + +```bash +# 1. Create a private ACR (Basic SKU is fine for a demo). +az group create --name radius-demo-rg --location eastus +az acr create --resource-group radius-demo-rg --name myregistry --sku Basic + +# 2. Create a scoped, repository-limited token for pulling the recipe. +az acr token create \ + --registry myregistry \ + --name radius-demo-pull \ + --repository recipes/redis content/read content/write + +# The command prints a token password — capture it now (it is shown once). + +# 3. Log in locally so you can publish the recipe. +az acr login --name myregistry +``` + +Then record: + +```bash +export BICEP_REGISTRY="myregistry.azurecr.io" +export BICEP_RECIPE="${BICEP_REGISTRY}/recipes/redis:latest" +export BICEP_REGISTRY_USERNAME="radius-demo-pull" +export BICEP_REGISTRY_PASSWORD="" +``` + +### Concrete example — GitHub Container Registry (GHCR) + +```bash +# 1/2. Create a classic PAT with write:packages + read:packages scope at +# https://github.com/settings/tokens, then log in. +echo "" | docker login ghcr.io -u --password-stdin +``` + +```bash +export BICEP_REGISTRY="ghcr.io/" +export BICEP_RECIPE="${BICEP_REGISTRY}/recipes/redis:latest" +export BICEP_REGISTRY_USERNAME="" +export BICEP_REGISTRY_PASSWORD="" +``` + +> GHCR packages are private by default. Leave the package private so the demo +> exercises authentication. + +The demo's [Scenario 1](./README.md#scenario-1--private-bicep-recipe-registry-oci) +publishes [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) to +`BICEP_RECIPE` and feeds the credentials into a `Radius.Core/bicepConfigs`. + +--- + +## Part B — Private Terraform module registry / repository + +**Goal:** a private Terraform module source that authenticates with a **token**, +plus the module address Radius should fetch. The demo's +`Radius.Core/terraformConfigs` renders a `.terraformrc` `credentials` block for +that host. + +### High-level steps (any token-authenticated registry) + +1. **Choose a private module source** that authenticates over HTTPS with a bearer + token — for example Terraform Cloud / HCP Terraform (`app.terraform.io`), a + self-hosted private module registry, or an Artifactory/Harbor Terraform + registry. +2. **Publish (or identify) a module** that provisions the demo resource — a + Kubernetes Redis cache matches the sample. Note its full module address. +3. **Create an API token** with permission to read the module from that registry. +4. **Record these values** — you'll pass them to the demo: + - `TF_REGISTRY_HOST` — the registry hostname, e.g. `app.terraform.io`. + - `TF_RECIPE_LOCATION` — the module source address, e.g. + `app.terraform.io/my-org/redis/kubernetes`. + - `TF_REGISTRY_TOKEN` — the API token. + +### Concrete example — HCP Terraform (Terraform Cloud) + +1. Sign in at and create (or use) an organization. +2. Publish a private module to the organization's **Registry** (for a demo, you + can publish from a Git repo containing a small Kubernetes Redis module, or use + an existing private module). +3. Create a token under **User settings → Tokens** (or a team/organization token). + +```bash +export TF_REGISTRY_HOST="app.terraform.io" +export TF_RECIPE_LOCATION="app.terraform.io/my-org/redis/kubernetes" +export TF_REGISTRY_TOKEN="" +``` + +> `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at — a +> private registry module address or an HTTP module archive URL. The demo's +> [Scenario 2](./README.md#scenario-2--private-terraform-module-registry--repository) +> stores the token in a `SecretStore` and references it from a +> `Radius.Core/terraformConfigs`. + +> **Git-based private modules (PAT auth):** authenticating to a private **Git** +> module source with a personal access token is a separate path that the new +> `Radius.Core/terraformConfigs` resource does not cover yet. For Git PAT auth +> today, use the legacy `Applications.Core/environments` `recipeConfig` path — +> see [Known limitations](./README.md#known-limitations-as-of-this-demo). + +--- + +## Verify and continue + +Once your registries are ready and the variables are set, verify the base setup +and continue with the [demo README](./README.md): + +```bash +rad version +kubectl get nodes +``` + +The README's [Prerequisites](./README.md#prerequisites) section creates the +Radius group and namespaces; its scenarios then publish the recipe and deploy +using the values you recorded above. The +[automated E2E runner](./README.md#automated-e2e-runner) reads the **same** +environment variables. + +--- + +## Cleanup + +Tear down the throwaway registries and credentials when you're done: + +- **ACR:** `az group delete --name radius-demo-rg --yes --no-wait` + (or just `az acr token delete --registry myregistry --name radius-demo-pull`). +- **GHCR:** delete the package from the repository/org **Packages** UI and revoke + the PAT at . +- **HCP Terraform:** delete the module from the registry and revoke the token in + **User settings → Tokens**. + +See the demo README's [Cleanup](./README.md#cleanup) section to remove the Radius +applications, group, and namespaces. diff --git a/docs/demos/private-registries-recipes/README.md b/docs/demos/private-registries-recipes/README.md index 956459cff8b..8985a6e99a9 100644 --- a/docs/demos/private-registries-recipes/README.md +++ b/docs/demos/private-registries-recipes/README.md @@ -75,6 +75,9 @@ the same value. Substitute your own values throughout. - For Scenario 2: a private **Terraform** module registry or repository that authenticates with a token (e.g. Terraform Cloud `app.terraform.io`, or a self-hosted private module registry). + - **Don't have one yet?** See [PREREQUISITES.md](./PREREQUISITES.md) for + high-level steps to set up a private Bicep (OCI) and Terraform registry for + demoing. 5. The Bicep tooling for `rad bicep publish` (bundled with `rad`). Verify your setup: @@ -436,6 +439,7 @@ kubectl delete namespace private-bicep-demo private-tf-demo private-combined-dem | Path | Description | | --- | --- | +| [`PREREQUISITES.md`](./PREREQUISITES.md) | High-level steps to set up the private Bicep (OCI) and Terraform registries the demo needs. | | [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) | Scenario 1 — private Bicep (OCI) registry via `bicepConfigs` (BasicAuth). | | [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) | Scenario 2 — private Terraform registry via `terraformConfigs` (credentials token). | | [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 — one environment referencing both configs. | From 6766d55e47de76bd083c99022b0281f38c7db5dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:35:22 +0000 Subject: [PATCH 06/11] Mention self-hosted OSS Terraform registries (e.g. Terralist) in prerequisites --- .../PREREQUISITES.md | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/docs/demos/private-registries-recipes/PREREQUISITES.md b/docs/demos/private-registries-recipes/PREREQUISITES.md index 6e40496f241..c21098a115c 100644 --- a/docs/demos/private-registries-recipes/PREREQUISITES.md +++ b/docs/demos/private-registries-recipes/PREREQUISITES.md @@ -122,9 +122,16 @@ that host. ### High-level steps (any token-authenticated registry) 1. **Choose a private module source** that authenticates over HTTPS with a bearer - token — for example Terraform Cloud / HCP Terraform (`app.terraform.io`), a - self-hosted private module registry, or an Artifactory/Harbor Terraform - registry. + token. You have two broad options: + - **Cloud / hosted (official):** Terraform Cloud / HCP Terraform + (`app.terraform.io`). + - **Self-hosted OSS:** run your own private module registry. Lightweight, + open-source options implement the + [Terraform Module Registry protocol](https://developer.hashicorp.com/terraform/internals/module-registry-protocol) + and authenticate with a token — for example + [Terralist](https://www.terralist.io/) (also acts as a provider registry), + or an Artifactory/Harbor Terraform registry. These are a good fit when you + want a fully private registry without a SaaS account. 2. **Publish (or identify) a module** that provisions the demo resource — a Kubernetes Redis cache matches the sample. Note its full module address. 3. **Create an API token** with permission to read the module from that registry. @@ -148,6 +155,31 @@ export TF_RECIPE_LOCATION="app.terraform.io/my-org/redis/kubernetes" export TF_REGISTRY_TOKEN="" ``` +### Concrete example — self-hosted OSS registry ([Terralist](https://www.terralist.io/)) + +For a fully private setup without a SaaS account, run an open-source registry +such as [Terralist](https://www.terralist.io/), which implements the Terraform +module (and provider) registry protocol and supports token-based auth. + +1. **Deploy Terralist** following its + [documentation](https://www.terralist.io/docs/) (it ships as a single binary / + container and uses a SQL database plus object storage for module/provider + artifacts). Expose it over HTTPS at a hostname you control, e.g. + `registry.mycompany.com`. +2. **Publish a module** (a small Kubernetes Redis module works for the demo) to + your Terralist instance and note its address. +3. **Create an API token** in Terralist for Radius to pull the module. + +```bash +export TF_REGISTRY_HOST="registry.mycompany.com" +export TF_RECIPE_LOCATION="registry.mycompany.com/my-org/redis/kubernetes" +export TF_REGISTRY_TOKEN="" +``` + +> Any registry that implements the Terraform module registry protocol and +> authenticates with a token works the same way — Radius only needs the host, the +> module address, and the token. + > `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at — a > private registry module address or an HTTP module archive URL. The demo's > [Scenario 2](./README.md#scenario-2--private-terraform-module-registry--repository) @@ -190,6 +222,8 @@ Tear down the throwaway registries and credentials when you're done: the PAT at . - **HCP Terraform:** delete the module from the registry and revoke the token in **User settings → Tokens**. +- **Self-hosted (e.g. Terralist):** revoke the API token and remove the module + (and the instance itself if it was stood up only for the demo). See the demo README's [Cleanup](./README.md#cleanup) section to remove the Radius applications, group, and namespaces. From e2e7aae6069a9de6b0a99d459440461ed55433f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:30:50 +0000 Subject: [PATCH 07/11] Replace em dashes with hyphens in private-registries demo docs --- .../PREREQUISITES.md | 44 +++++++++---------- .../private-registries-recipes/README.md | 38 ++++++++-------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/demos/private-registries-recipes/PREREQUISITES.md b/docs/demos/private-registries-recipes/PREREQUISITES.md index c21098a115c..8b97578d5d0 100644 --- a/docs/demos/private-registries-recipes/PREREQUISITES.md +++ b/docs/demos/private-registries-recipes/PREREQUISITES.md @@ -10,7 +10,7 @@ Radius can pull recipes from registries that require authentication, so you need that authenticates with a token. - **Scenario 3 (Combined):** both of the above. -You do **not** need every provider listed below — pick whichever one you already +You do **not** need every provider listed below - pick whichever one you already have access to. Each section is a short, provider-agnostic checklist plus one concrete example. Once a registry is ready, return to the [demo README](./README.md) and supply the values it produced. @@ -35,7 +35,7 @@ Before setting up any registry, make sure you have: --- -## Part A — Private Bicep (OCI) registry +## Part A - Private Bicep (OCI) registry **Goal:** a private OCI registry you can `rad bicep publish` to, and a username/password pair the cluster can use to pull from it (the demo's Bicep @@ -44,7 +44,7 @@ scenarios use `BasicAuth`). ### High-level steps (any OCI provider) 1. **Create a private OCI registry.** Any registry that speaks the OCI artifact - spec works — Azure Container Registry (ACR), GitHub Container Registry (GHCR), + spec works - Azure Container Registry (ACR), GitHub Container Registry (GHCR), Amazon ECR, Google Artifact Registry, Docker Hub (private repo), Harbor, etc. Ensure it is **private** (not anonymously pullable) so the demo actually exercises authentication. @@ -54,13 +54,13 @@ scenarios use `BasicAuth`). admin account. 3. **Authenticate your local tooling once** so you can push the recipe artifact (e.g. `az acr login`, `docker login`, `gh auth token | docker login`). -4. **Record these values** — you'll pass them to the demo: - - `BICEP_REGISTRY` — the registry hostname, e.g. `myregistry.azurecr.io`. - - `BICEP_RECIPE` — the full artifact reference, e.g. +4. **Record these values** - you'll pass them to the demo: + - `BICEP_REGISTRY` - the registry hostname, e.g. `myregistry.azurecr.io`. + - `BICEP_RECIPE` - the full artifact reference, e.g. `myregistry.azurecr.io/recipes/redis:latest`. - - `BICEP_REGISTRY_USERNAME` / `BICEP_REGISTRY_PASSWORD` — the pull credentials. + - `BICEP_REGISTRY_USERNAME` / `BICEP_REGISTRY_PASSWORD` - the pull credentials. -### Concrete example — Azure Container Registry (ACR) +### Concrete example - Azure Container Registry (ACR) ```bash # 1. Create a private ACR (Basic SKU is fine for a demo). @@ -73,7 +73,7 @@ az acr token create \ --name radius-demo-pull \ --repository recipes/redis content/read content/write -# The command prints a token password — capture it now (it is shown once). +# The command prints a token password - capture it now (it is shown once). # 3. Log in locally so you can publish the recipe. az acr login --name myregistry @@ -88,7 +88,7 @@ export BICEP_REGISTRY_USERNAME="radius-demo-pull" export BICEP_REGISTRY_PASSWORD="" ``` -### Concrete example — GitHub Container Registry (GHCR) +### Concrete example - GitHub Container Registry (GHCR) ```bash # 1/2. Create a classic PAT with write:packages + read:packages scope at @@ -112,7 +112,7 @@ publishes [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) to --- -## Part B — Private Terraform module registry / repository +## Part B - Private Terraform module registry / repository **Goal:** a private Terraform module source that authenticates with a **token**, plus the module address Radius should fetch. The demo's @@ -128,20 +128,20 @@ that host. - **Self-hosted OSS:** run your own private module registry. Lightweight, open-source options implement the [Terraform Module Registry protocol](https://developer.hashicorp.com/terraform/internals/module-registry-protocol) - and authenticate with a token — for example + and authenticate with a token - for example [Terralist](https://www.terralist.io/) (also acts as a provider registry), or an Artifactory/Harbor Terraform registry. These are a good fit when you want a fully private registry without a SaaS account. -2. **Publish (or identify) a module** that provisions the demo resource — a +2. **Publish (or identify) a module** that provisions the demo resource - a Kubernetes Redis cache matches the sample. Note its full module address. 3. **Create an API token** with permission to read the module from that registry. -4. **Record these values** — you'll pass them to the demo: - - `TF_REGISTRY_HOST` — the registry hostname, e.g. `app.terraform.io`. - - `TF_RECIPE_LOCATION` — the module source address, e.g. +4. **Record these values** - you'll pass them to the demo: + - `TF_REGISTRY_HOST` - the registry hostname, e.g. `app.terraform.io`. + - `TF_RECIPE_LOCATION` - the module source address, e.g. `app.terraform.io/my-org/redis/kubernetes`. - - `TF_REGISTRY_TOKEN` — the API token. + - `TF_REGISTRY_TOKEN` - the API token. -### Concrete example — HCP Terraform (Terraform Cloud) +### Concrete example - HCP Terraform (Terraform Cloud) 1. Sign in at and create (or use) an organization. 2. Publish a private module to the organization's **Registry** (for a demo, you @@ -155,7 +155,7 @@ export TF_RECIPE_LOCATION="app.terraform.io/my-org/redis/kubernetes" export TF_REGISTRY_TOKEN="" ``` -### Concrete example — self-hosted OSS registry ([Terralist](https://www.terralist.io/)) +### Concrete example - self-hosted OSS registry ([Terralist](https://www.terralist.io/)) For a fully private setup without a SaaS account, run an open-source registry such as [Terralist](https://www.terralist.io/), which implements the Terraform @@ -177,10 +177,10 @@ export TF_REGISTRY_TOKEN="" ``` > Any registry that implements the Terraform module registry protocol and -> authenticates with a token works the same way — Radius only needs the host, the +> authenticates with a token works the same way - Radius only needs the host, the > module address, and the token. -> `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at — a +> `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at - a > private registry module address or an HTTP module archive URL. The demo's > [Scenario 2](./README.md#scenario-2--private-terraform-module-registry--repository) > stores the token in a `SecretStore` and references it from a @@ -189,7 +189,7 @@ export TF_REGISTRY_TOKEN="" > **Git-based private modules (PAT auth):** authenticating to a private **Git** > module source with a personal access token is a separate path that the new > `Radius.Core/terraformConfigs` resource does not cover yet. For Git PAT auth -> today, use the legacy `Applications.Core/environments` `recipeConfig` path — +> today, use the legacy `Applications.Core/environments` `recipeConfig` path - > see [Known limitations](./README.md#known-limitations-as-of-this-demo). --- diff --git a/docs/demos/private-registries-recipes/README.md b/docs/demos/private-registries-recipes/README.md index 8985a6e99a9..9fd3e006233 100644 --- a/docs/demos/private-registries-recipes/README.md +++ b/docs/demos/private-registries-recipes/README.md @@ -1,12 +1,12 @@ # E2E Demo: Private Registries & Repositories with Terraform and Bicep config resource types This demo validates the work delivered in -[radius-project/radius#11798 — *Private registries and repositories support for +[radius-project/radius#11798 - *Private registries and repositories support for compute extensibility*](https://github.com/radius-project/radius/issues/11798). With compute extensibility, Radius introduced the `Radius.Core/environments` -resource. Private recipe registry authentication — which used to live inline on -`Applications.Core/environments` under `recipeConfig` — is now expressed as +resource. Private recipe registry authentication - which used to live inline on +`Applications.Core/environments` under `recipeConfig` - is now expressed as **standalone, reusable resources**: | Resource | Purpose | @@ -21,12 +21,12 @@ many environments. This walkthrough proves the feature works end-to-end against **real private registries in the cloud**, across three scenarios: -1. **Private Bicep recipe registry** (OCI, e.g. Azure Container Registry) — `Radius.Core/bicepConfigs` with `BasicAuth`. -2. **Private Terraform module registry/repository** — `Radius.Core/terraformConfigs` with a `credentials` token. -3. **Combined** — a single environment that references *both* config resources. +1. **Private Bicep recipe registry** (OCI, e.g. Azure Container Registry) - `Radius.Core/bicepConfigs` with `BasicAuth`. +2. **Private Terraform module registry/repository** - `Radius.Core/terraformConfigs` with a `credentials` token. +3. **Combined** - a single environment that references *both* config resources. > Design references: -> [Terraform & Bicep settings feature spec — User Story 8 (Private Bicep Recipes)](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md#user-story-8--private-bicep-recipes), +> [Terraform & Bicep settings feature spec - User Story 8 (Private Bicep Recipes)](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md#user-story-8--private-bicep-recipes), > [Available Terraform settings](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md#available-terraform-settings), > [Reusable Terraform and Bicep config architecture doc](../../architecture/terraform-bicep-config.md). > @@ -109,7 +109,7 @@ recipe in [`./recipes`](./recipes). Run the commands from this demo directory. --- -## Scenario 1 — Private Bicep recipe registry (OCI) +## Scenario 1 - Private Bicep recipe registry (OCI) **Goal:** publish a Bicep recipe to a *private* OCI registry and have Radius pull it using credentials supplied through a `Radius.Core/bicepConfigs` resource. @@ -130,7 +130,7 @@ Set your registry details. ``` Authenticate your local tooling to the registry once so you can push the -artifact (example for Azure Container Registry — use the equivalent for your +artifact (example for Azure Container Registry - use the equivalent for your provider): ```bash @@ -198,12 +198,12 @@ kubectl get pods -n private-bicep-demo ``` A successful deployment means Radius authenticated to your **private** OCI -registry, pulled the Bicep recipe, and executed it — the Redis pod should be +registry, pulled the Bicep recipe, and executed it - the Redis pod should be `Running` in `private-bicep-demo`. --- -## Scenario 2 — Private Terraform module registry / repository +## Scenario 2 - Private Terraform module registry / repository **Goal:** authenticate to a *private* Terraform module source via a token carried by a `Radius.Core/terraformConfigs` resource. Radius renders a `.terraformrc` @@ -224,7 +224,7 @@ by a `Radius.Core/terraformConfigs` resource. Radius renders a `.terraformrc` $env:TF_REGISTRY_TOKEN = "" ``` -> `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at — +> `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at - > a private registry module address or an HTTP module archive URL. ### 2.2 Deploy the environment + app @@ -260,7 +260,7 @@ A successful deployment means Radius rendered a `.terraformrc` with your token, authenticated to the **private** Terraform registry, downloaded the module, and ran `terraform apply`. -> **Tip — confirm the credentials were applied.** Because the config sets +> **Tip - confirm the credentials were applied.** Because the config sets > `TF_LOG: INFO`, the recipe execution logs include the Terraform run. Tail the > recipe engine logs to observe the module download from your private host: > ```bash @@ -269,9 +269,9 @@ ran `terraform apply`. --- -## Scenario 3 — Combined (one environment, both private registries) +## Scenario 3 - Combined (one environment, both private registries) -**Goal:** demonstrate the reusability goal of #11798 — a single +**Goal:** demonstrate the reusability goal of #11798 - a single `Radius.Core/environments` that references **both** a `terraformConfigs` and a `bicepConfigs` resource. @@ -370,7 +370,7 @@ the demo focuses on the paths that are wired end-to-end: ## Automated E2E runner The [`./scripts`](./scripts) folder provides a cross-platform runner that -executes the whole walkthrough non-interactively — ideal for E2E validation: +executes the whole walkthrough non-interactively - ideal for E2E validation: | Platform | Script | | --- | --- | @@ -440,9 +440,9 @@ kubectl delete namespace private-bicep-demo private-tf-demo private-combined-dem | Path | Description | | --- | --- | | [`PREREQUISITES.md`](./PREREQUISITES.md) | High-level steps to set up the private Bicep (OCI) and Terraform registries the demo needs. | -| [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) | Scenario 1 — private Bicep (OCI) registry via `bicepConfigs` (BasicAuth). | -| [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) | Scenario 2 — private Terraform registry via `terraformConfigs` (credentials token). | -| [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 — one environment referencing both configs. | +| [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) | Scenario 1 - private Bicep (OCI) registry via `bicepConfigs` (BasicAuth). | +| [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) | Scenario 2 - private Terraform registry via `terraformConfigs` (credentials token). | +| [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 - one environment referencing both configs. | | [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) | Sample Bicep recipe to publish to your private OCI registry for Scenario 1. | | [`scripts/run-e2e.sh`](./scripts/run-e2e.sh) | Linux / macOS E2E runner that automates the full walkthrough. | | [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | Cross-platform PowerShell (pwsh 7+) E2E runner that automates the full walkthrough. | From 273be6b0df5ef8318918919654739b412abd0036 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:37:46 +0000 Subject: [PATCH 08/11] Add plainHttp note and soften Scenario 2 recipe-log tip in demo README --- docs/demos/private-registries-recipes/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/demos/private-registries-recipes/README.md b/docs/demos/private-registries-recipes/README.md index 9fd3e006233..91b2e16be96 100644 --- a/docs/demos/private-registries-recipes/README.md +++ b/docs/demos/private-registries-recipes/README.md @@ -173,6 +173,11 @@ creates the `SecretStore`, the `Radius.Core/bicepConfigs`, a `Radius.Core/recipe pointing at the private recipe, the `Radius.Core/environments` that references the bicepConfig, and an app that runs the recipe. +> **Self-hosted insecure registries.** This demo targets HTTPS registries. If your +> private OCI registry only serves plain HTTP (for example a locally hosted dev +> registry), set `plainHttp: true` on the recipe entry in the `recipePacks` +> resource so Radius pulls over HTTP instead of HTTPS. + - **Linux / macOS:** ```bash rad deploy ./bicep/bicep-private-registry.bicep \ @@ -262,8 +267,15 @@ ran `terraform apply`. > **Tip - confirm the credentials were applied.** Because the config sets > `TF_LOG: INFO`, the recipe execution logs include the Terraform run. Tail the -> recipe engine logs to observe the module download from your private host: +> recipe engine logs to observe the module download from your private host. The +> recipe runs in the resource provider that handles the environment - for +> `Radius.Core` environments this is typically `dynamic-rp`, while legacy +> `Applications.Core` paths run in `applications-rp`. Pick whichever pod is +> executing your recipe: > ```bash +> # Radius.Core (this demo) +> kubectl logs -n radius-system deploy/dynamic-rp -f +> # Legacy Applications.Core path > kubectl logs -n radius-system deploy/applications-rp -f > ``` From 95827bc6717a95c472999e2f52bc0c764cf477c9 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:24:35 -0700 Subject: [PATCH 09/11] fix: update resource references Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .../bicep/bicep-private-registry.bicep | 4 ++-- docs/demos/private-registries-recipes/bicep/combined.bicep | 6 +++--- .../bicep/terraform-private-registry.bicep | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep index a6a2e637215..201b8b70cb5 100644 --- a/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep +++ b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep @@ -32,7 +32,7 @@ param registryPassword string // SecretStore holding the username/password used to authenticate to the private // OCI registry. For BasicAuth the secret store must expose 'username' and 'password'. -resource registrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { +resource registrySecret 'Radius.Security/secretStores@2025-08-01-preview' = { name: 'private-bicep-registry-secret' location: 'global' properties: { @@ -98,7 +98,7 @@ resource env 'Radius.Core/environments@2025-08-01-preview' = { } } -resource app 'Applications.Core/applications@2023-10-01-preview' = { +resource app 'Radius.Core/applications@2025-08-01-preview' = { name: appName location: 'global' properties: { diff --git a/docs/demos/private-registries-recipes/bicep/combined.bicep b/docs/demos/private-registries-recipes/bicep/combined.bicep index d8491ff7ef4..e6e79305c56 100644 --- a/docs/demos/private-registries-recipes/bicep/combined.bicep +++ b/docs/demos/private-registries-recipes/bicep/combined.bicep @@ -45,7 +45,7 @@ param bicepRegistryUsername string param bicepRegistryPassword string // SecretStore for the Terraform registry token (key: token). -resource terraformTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { +resource terraformTokenSecret 'Radius.Security/secretStores@2025-08-01-preview' = { name: 'combined-tf-token-secret' location: 'global' properties: { @@ -60,7 +60,7 @@ resource terraformTokenSecret 'Applications.Core/secretStores@2023-10-01-preview } // SecretStore for the Bicep OCI registry BasicAuth (keys: username, password). -resource bicepRegistrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { +resource bicepRegistrySecret 'Radius.Security/secretStores@2025-08-01-preview' = { name: 'combined-bicep-registry-secret' location: 'global' properties: { @@ -145,7 +145,7 @@ resource env 'Radius.Core/environments@2025-08-01-preview' = { } } -resource app 'Applications.Core/applications@2023-10-01-preview' = { +resource app 'Radius.Core/applications@2025-08-01-preview' = { name: appName location: 'global' properties: { diff --git a/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep index 6d8f6e7cbbe..c10240a4352 100644 --- a/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep +++ b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep @@ -33,7 +33,7 @@ param redisCacheName string = 'tf-private-redis' // SecretStore holding the Terraform registry token. The terraformConfig // credentials block references this; the secret store must expose a 'token' key. -resource registryTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { +resource registryTokenSecret 'Radius.Security/secretStores@2025-08-01-preview' = { name: 'private-tf-token-secret' location: 'global' properties: { @@ -109,7 +109,7 @@ resource env 'Radius.Core/environments@2025-08-01-preview' = { } } -resource app 'Applications.Core/applications@2023-10-01-preview' = { +resource app 'Radius.Core/applications@2025-08-01-preview' = { name: appName location: 'global' properties: { From 543c8f28c71fdfa41c04a4542031fbb581b61d7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 02:09:17 +0000 Subject: [PATCH 10/11] fix: keep Applications.Core/secretStores for registry-auth secrets (required by recipe config loader) --- .../bicep/bicep-private-registry.bicep | 2 +- docs/demos/private-registries-recipes/bicep/combined.bicep | 4 ++-- .../bicep/terraform-private-registry.bicep | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep index 201b8b70cb5..70c9d719f8f 100644 --- a/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep +++ b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep @@ -32,7 +32,7 @@ param registryPassword string // SecretStore holding the username/password used to authenticate to the private // OCI registry. For BasicAuth the secret store must expose 'username' and 'password'. -resource registrySecret 'Radius.Security/secretStores@2025-08-01-preview' = { +resource registrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { name: 'private-bicep-registry-secret' location: 'global' properties: { diff --git a/docs/demos/private-registries-recipes/bicep/combined.bicep b/docs/demos/private-registries-recipes/bicep/combined.bicep index e6e79305c56..9997fb91e6c 100644 --- a/docs/demos/private-registries-recipes/bicep/combined.bicep +++ b/docs/demos/private-registries-recipes/bicep/combined.bicep @@ -45,7 +45,7 @@ param bicepRegistryUsername string param bicepRegistryPassword string // SecretStore for the Terraform registry token (key: token). -resource terraformTokenSecret 'Radius.Security/secretStores@2025-08-01-preview' = { +resource terraformTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { name: 'combined-tf-token-secret' location: 'global' properties: { @@ -60,7 +60,7 @@ resource terraformTokenSecret 'Radius.Security/secretStores@2025-08-01-preview' } // SecretStore for the Bicep OCI registry BasicAuth (keys: username, password). -resource bicepRegistrySecret 'Radius.Security/secretStores@2025-08-01-preview' = { +resource bicepRegistrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { name: 'combined-bicep-registry-secret' location: 'global' properties: { diff --git a/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep index c10240a4352..87476f2ec88 100644 --- a/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep +++ b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep @@ -33,7 +33,7 @@ param redisCacheName string = 'tf-private-redis' // SecretStore holding the Terraform registry token. The terraformConfig // credentials block references this; the secret store must expose a 'token' key. -resource registryTokenSecret 'Radius.Security/secretStores@2025-08-01-preview' = { +resource registryTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { name: 'private-tf-token-secret' location: 'global' properties: { From 2b7db01a6b3279a78d29e4dd263cc5d9666639d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:38:05 +0000 Subject: [PATCH 11/11] Update private-registries demo for bicepSettings/terraformSettings + Radius.Security/secrets (PR #12303) Co-authored-by: DariuszPorowski <3431813+DariuszPorowski@users.noreply.github.com> --- .../PREREQUISITES.md | 10 +- .../private-registries-recipes/README.md | 109 +++++++++++------- .../bicep/bicep-private-registry.bicep | 60 +++++++--- .../bicep/combined.bicep | 65 +++++++---- .../bicep/terraform-private-registry.bicep | 60 +++++++--- .../recipes/redis-recipe.bicep | 2 +- .../scripts/run-e2e.ps1 | 17 ++- .../scripts/run-e2e.sh | 15 ++- 8 files changed, 233 insertions(+), 105 deletions(-) diff --git a/docs/demos/private-registries-recipes/PREREQUISITES.md b/docs/demos/private-registries-recipes/PREREQUISITES.md index 8b97578d5d0..042d507caa2 100644 --- a/docs/demos/private-registries-recipes/PREREQUISITES.md +++ b/docs/demos/private-registries-recipes/PREREQUISITES.md @@ -108,7 +108,7 @@ export BICEP_REGISTRY_PASSWORD="" The demo's [Scenario 1](./README.md#scenario-1--private-bicep-recipe-registry-oci) publishes [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) to -`BICEP_RECIPE` and feeds the credentials into a `Radius.Core/bicepConfigs`. +`BICEP_RECIPE` and feeds the credentials into a `Radius.Core/bicepSettings`. --- @@ -116,7 +116,7 @@ publishes [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) to **Goal:** a private Terraform module source that authenticates with a **token**, plus the module address Radius should fetch. The demo's -`Radius.Core/terraformConfigs` renders a `.terraformrc` `credentials` block for +`Radius.Core/terraformSettings` renders a `.terraformrc` `credentials` block for that host. ### High-level steps (any token-authenticated registry) @@ -183,12 +183,12 @@ export TF_REGISTRY_TOKEN="" > `TF_RECIPE_LOCATION` is whatever module source your private recipe lives at - a > private registry module address or an HTTP module archive URL. The demo's > [Scenario 2](./README.md#scenario-2--private-terraform-module-registry--repository) -> stores the token in a `SecretStore` and references it from a -> `Radius.Core/terraformConfigs`. +> stores the token in a `Radius.Security/secrets` and references it from a +> `Radius.Core/terraformSettings`. > **Git-based private modules (PAT auth):** authenticating to a private **Git** > module source with a personal access token is a separate path that the new -> `Radius.Core/terraformConfigs` resource does not cover yet. For Git PAT auth +> `Radius.Core/terraformSettings` resource does not cover yet. For Git PAT auth > today, use the legacy `Applications.Core/environments` `recipeConfig` path - > see [Known limitations](./README.md#known-limitations-as-of-this-demo). diff --git a/docs/demos/private-registries-recipes/README.md b/docs/demos/private-registries-recipes/README.md index 91b2e16be96..8b2b119586a 100644 --- a/docs/demos/private-registries-recipes/README.md +++ b/docs/demos/private-registries-recipes/README.md @@ -1,8 +1,12 @@ -# E2E Demo: Private Registries & Repositories with Terraform and Bicep config resource types +# E2E Demo: Private Registries & Repositories with Terraform and Bicep settings resource types This demo validates the work delivered in [radius-project/radius#11798 - *Private registries and repositories support for -compute extensibility*](https://github.com/radius-project/radius/issues/11798). +compute extensibility*](https://github.com/radius-project/radius/issues/11798), +updated for +[radius-project/radius#12303](https://github.com/radius-project/radius/pull/12303), +which renamed these resource types to match the feature spec and added support +for `Radius.Security/secrets` as the credential store. With compute extensibility, Radius introduced the `Radius.Core/environments` resource. Private recipe registry authentication - which used to live inline on @@ -11,24 +15,29 @@ resource. Private recipe registry authentication - which used to live inline on | Resource | Purpose | | --- | --- | -| `Radius.Core/terraformConfigs` | Private Terraform registry credentials, provider installation (mirror/direct), and Terraform env vars. Radius renders a `.terraformrc` from it at recipe-execution time. | -| `Radius.Core/bicepConfigs` | Private Bicep (OCI) registry authentication, keyed by registry hostname. | +| `Radius.Core/terraformSettings` | Private Terraform registry credentials, provider installation (mirror/direct), and Terraform env vars. Radius renders a `.terraformrc` from it at recipe-execution time. | +| `Radius.Core/bicepSettings` | Private Bicep (OCI) registry authentication, keyed by registry hostname. | -A `Radius.Core/environments` resource references these by resource ID, so a -platform team can define registry authentication **once** and reuse it across +Registry credentials are supplied through a secret resource that these settings +reference by ID. As of #12303 the secret can be a **`Radius.Security/secrets`** +(recommended) or an `Applications.Core/secretStores`. This demo uses +`Radius.Security/secrets`. + +A `Radius.Core/environments` resource references these settings by resource ID, so +a platform team can define registry authentication **once** and reuse it across many environments. This walkthrough proves the feature works end-to-end against **real private registries in the cloud**, across three scenarios: -1. **Private Bicep recipe registry** (OCI, e.g. Azure Container Registry) - `Radius.Core/bicepConfigs` with `BasicAuth`. -2. **Private Terraform module registry/repository** - `Radius.Core/terraformConfigs` with a `credentials` token. -3. **Combined** - a single environment that references *both* config resources. +1. **Private Bicep recipe registry** (OCI, e.g. Azure Container Registry) - `Radius.Core/bicepSettings` with `BasicAuth`. +2. **Private Terraform module registry/repository** - `Radius.Core/terraformSettings` with a `credentials` token. +3. **Combined** - a single environment that references *both* settings resources. > Design references: > [Terraform & Bicep settings feature spec - User Story 8 (Private Bicep Recipes)](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md#user-story-8--private-bicep-recipes), > [Available Terraform settings](https://github.com/radius-project/design-notes/blob/main/features/2025-08-14-terraform-bicep-settings.md#available-terraform-settings), -> [Reusable Terraform and Bicep config architecture doc](../../architecture/terraform-bicep-config.md). +> [Reusable Terraform and Bicep settings architecture doc](../../architecture/terraform-bicep-settings.md). > > Product docs for the equivalent (legacy) feature, for additional context: > [Private Terraform registry](https://docs.radapp.io/guides/recipes/terraform/howto-private-registry/), @@ -90,12 +99,18 @@ rad group switch demo-private-registries ``` Create the namespaces the environments deploy into (the `Radius.Core/environments` -resource requires the target namespace to already exist): +resource requires the target namespace to already exist). Each scenario uses a +dedicated **secrets** namespace for the environment that provisions its +`Radius.Security/secrets` resources - Radius rejects two environments that share a +namespace, so the secrets environment must be separate from the application one: ```bash kubectl create namespace private-bicep-demo +kubectl create namespace private-bicep-demo-secrets kubectl create namespace private-tf-demo +kubectl create namespace private-tf-demo-secrets kubectl create namespace private-combined-demo +kubectl create namespace private-combined-demo-secrets ``` All Bicep templates referenced below live in [`./bicep`](./bicep) and the sample @@ -112,7 +127,7 @@ recipe in [`./recipes`](./recipes). Run the commands from this demo directory. ## Scenario 1 - Private Bicep recipe registry (OCI) **Goal:** publish a Bicep recipe to a *private* OCI registry and have Radius pull -it using credentials supplied through a `Radius.Core/bicepConfigs` resource. +it using credentials supplied through a `Radius.Core/bicepSettings` resource. ### 1.1 Publish the sample recipe to your private registry @@ -169,9 +184,12 @@ or a service principal. Capture them: ### 1.3 Deploy the environment + app The template [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) -creates the `SecretStore`, the `Radius.Core/bicepConfigs`, a `Radius.Core/recipePacks` +creates a `Radius.Security/secrets` (holding the registry username/password), +the `Radius.Core/bicepSettings` that references it, a `Radius.Core/recipePacks` pointing at the private recipe, the `Radius.Core/environments` that references the -bicepConfig, and an app that runs the recipe. +bicepSettings, and an app that runs the recipe. It also creates a small **secrets +environment** whose default recipe pack materializes the backing Kubernetes Secret +that the Bicep driver reads at recipe-execution time. > **Self-hosted insecure registries.** This demo targets HTTPS registries. If your > private OCI registry only serves plain HTTP (for example a locally hosted dev @@ -211,7 +229,7 @@ registry, pulled the Bicep recipe, and executed it - the Redis pod should be ## Scenario 2 - Private Terraform module registry / repository **Goal:** authenticate to a *private* Terraform module source via a token carried -by a `Radius.Core/terraformConfigs` resource. Radius renders a `.terraformrc` +by a `Radius.Core/terraformSettings` resource. Radius renders a `.terraformrc` `credentials` block for that registry at execution time. ### 2.1 Gather registry details and token @@ -235,9 +253,11 @@ by a `Radius.Core/terraformConfigs` resource. Radius renders a `.terraformrc` ### 2.2 Deploy the environment + app The template [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) -creates a `SecretStore` (with a `token` key), a `Radius.Core/terraformConfigs` +creates a `Radius.Security/secrets` (with a `token` key), a `Radius.Core/terraformSettings` that references it under `terraformrc.credentials` (and raises `TF_LOG`), a recipe -pack pointing at the private module, the environment, and an app. +pack pointing at the private module, the environment, and an app. A separate +**secrets environment** provisions the secret so its backing Kubernetes Secret +exists before the Terraform driver resolves it. - **Linux / macOS:** ```bash @@ -265,7 +285,7 @@ A successful deployment means Radius rendered a `.terraformrc` with your token, authenticated to the **private** Terraform registry, downloaded the module, and ran `terraform apply`. -> **Tip - confirm the credentials were applied.** Because the config sets +> **Tip - confirm the credentials were applied.** Because the terraformSettings sets > `TF_LOG: INFO`, the recipe execution logs include the Terraform run. Tail the > recipe engine logs to observe the module download from your private host. The > recipe runs in the resource provider that handles the environment - for @@ -284,10 +304,10 @@ ran `terraform apply`. ## Scenario 3 - Combined (one environment, both private registries) **Goal:** demonstrate the reusability goal of #11798 - a single -`Radius.Core/environments` that references **both** a `terraformConfigs` and a -`bicepConfigs` resource. +`Radius.Core/environments` that references **both** a `terraformSettings` and a +`bicepSettings` resource. -The template [`bicep/combined.bicep`](./bicep/combined.bicep) wires both configs +The template [`bicep/combined.bicep`](./bicep/combined.bicep) wires both settings into one environment. - **Linux / macOS:** @@ -311,12 +331,12 @@ into one environment. --parameters bicepRegistryPassword="$env:BICEP_REGISTRY_PASSWORD" ``` -Inspect the environment to confirm both config references resolved: +Inspect the environment to confirm both settings references resolved: ```bash rad resource show Radius.Core/environments combined-env -rad resource list Radius.Core/terraformConfigs -rad resource list Radius.Core/bicepConfigs +rad resource list Radius.Core/terraformSettings +rad resource list Radius.Core/bicepSettings ``` --- @@ -326,11 +346,11 @@ rad resource list Radius.Core/bicepConfigs ```mermaid graph LR subgraph "Platform team (rad deploy)" - BC["bicepConfigs
(registryAuthentications)"] - TC["terraformConfigs
(terraformrc.credentials + env)"] - SS["SecretStore(s)"] + BC["bicepSettings
(registryAuthentications)"] + TC["terraformSettings
(terraformrc.credentials + env)"] + SS["Radius.Security/secrets"] end - ENV["Radius.Core/environments
terraformConfig / bicepConfig refs"] + ENV["Radius.Core/environments
terraformSettings / bicepSettings refs"] DRV["Terraform / Bicep recipe driver"] SS --> BC @@ -342,28 +362,30 @@ graph LR ``` - **PUT-time validation:** the environment controller validates the referenced - `terraformConfig` / `bicepConfig` IDs exist and returns `400 Bad Request` if not. -- **Execution-time resolution:** the config loader fetches the referenced configs + `terraformSettings` / `bicepSettings` IDs exist and returns `400 Bad Request` if not. +- **Execution-time resolution:** the config loader fetches the referenced settings and bridges them into the shared `RecipeConfig` the existing drivers already consume, so legacy `Applications.Core/environments` users are unaffected. -- **Secrets are never persisted** in the config resources; the driver resolves - `SecretStore` references at execution time. +- **Secrets are never persisted** in the settings resources; the driver resolves the + referenced secret at execution time. For `Radius.Security/secrets`, whose sensitive + `data` is redacted from the database once provisioned, the loader reads the plaintext + from the backing Kubernetes Secret the secret's recipe materialized. ### Property mapping (legacy → new) | Legacy `Applications.Core/environments.recipeConfig` | New resource | | --- | --- | -| `terraform.authentication` (registry token) | `Radius.Core/terraformConfigs` → `terraformrc.credentials` | -| `terraform.providers` provider config | `Radius.Core/terraformConfigs` → `terraformrc.providerInstallation` | -| `env` (Terraform env vars) | `Radius.Core/terraformConfigs` → `env` | -| `bicep.authentication..secret` | `Radius.Core/bicepConfigs` → `registryAuthentications.` | +| `terraform.authentication` (registry token) | `Radius.Core/terraformSettings` → `terraformrc.credentials` | +| `terraform.providers` provider config | `Radius.Core/terraformSettings` → `terraformrc.providerInstallation` | +| `env` (Terraform env vars) | `Radius.Core/terraformSettings` → `env` | +| `bicep.authentication..secret` | `Radius.Core/bicepSettings` → `registryAuthentications.` | --- ## Known limitations (as of this demo) These are intentional follow-ups tracked in the -[architecture doc](../../architecture/terraform-bicep-config.md#status-and-known-limitations); +[architecture doc](../../architecture/terraform-bicep-settings.md#status-and-known-limitations); the demo focuses on the paths that are wired end-to-end: - **Bicep auth method:** only `BasicAuth` (`basicAuthSecretId`) is threaded into @@ -375,7 +397,7 @@ the demo focuses on the paths that are wired end-to-end: Git PAT auth today, use the legacy `Applications.Core/environments` `recipeConfig` path. - **Delete protection / `referencedBy`** are not yet enforced/populated for these - config resources. + settings resources. --- @@ -442,7 +464,10 @@ rad app delete private-combined-demo --yes rad group switch default rad group delete demo-private-registries --yes -kubectl delete namespace private-bicep-demo private-tf-demo private-combined-demo +kubectl delete namespace \ + private-bicep-demo private-bicep-demo-secrets \ + private-tf-demo private-tf-demo-secrets \ + private-combined-demo private-combined-demo-secrets ``` --- @@ -452,9 +477,9 @@ kubectl delete namespace private-bicep-demo private-tf-demo private-combined-dem | Path | Description | | --- | --- | | [`PREREQUISITES.md`](./PREREQUISITES.md) | High-level steps to set up the private Bicep (OCI) and Terraform registries the demo needs. | -| [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) | Scenario 1 - private Bicep (OCI) registry via `bicepConfigs` (BasicAuth). | -| [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) | Scenario 2 - private Terraform registry via `terraformConfigs` (credentials token). | -| [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 - one environment referencing both configs. | +| [`bicep/bicep-private-registry.bicep`](./bicep/bicep-private-registry.bicep) | Scenario 1 - private Bicep (OCI) registry via `bicepSettings` (BasicAuth) + `Radius.Security/secrets`. | +| [`bicep/terraform-private-registry.bicep`](./bicep/terraform-private-registry.bicep) | Scenario 2 - private Terraform registry via `terraformSettings` (credentials token) + `Radius.Security/secrets`. | +| [`bicep/combined.bicep`](./bicep/combined.bicep) | Scenario 3 - one environment referencing both settings. | | [`recipes/redis-recipe.bicep`](./recipes/redis-recipe.bicep) | Sample Bicep recipe to publish to your private OCI registry for Scenario 1. | | [`scripts/run-e2e.sh`](./scripts/run-e2e.sh) | Linux / macOS E2E runner that automates the full walkthrough. | | [`scripts/run-e2e.ps1`](./scripts/run-e2e.ps1) | Cross-platform PowerShell (pwsh 7+) E2E runner that automates the full walkthrough. | diff --git a/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep index 70c9d719f8f..a26bad18db9 100644 --- a/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep +++ b/docs/demos/private-registries-recipes/bicep/bicep-private-registry.bicep @@ -2,7 +2,12 @@ // Scenario 1 - Private Bicep recipe registry (OCI, e.g. Azure Container Registry) // // Demonstrates pulling a Bicep Recipe from a *private* OCI registry by attaching -// a Radius.Core/bicepConfigs resource (BasicAuth) to a Radius.Core/environments. +// a Radius.Core/bicepSettings resource (BasicAuth) to a Radius.Core/environments. +// +// Registry credentials are stored in a Radius.Security/secrets resource. The +// bicepSettings resource references that secret by ID; at recipe-execution time +// the Bicep driver resolves the referenced secret and authenticates to the +// registry using the method configured on bicepSettings (BasicAuth here). // // Replace the parameters below with the details of your own private registry. // See ../README.md for the full step-by-step walkthrough. @@ -13,9 +18,12 @@ extension radius @description('Name of the Radius Application to create.') param appName string = 'private-bicep-demo' -@description('Kubernetes namespace the environment deploys into. Must already exist.') +@description('Kubernetes namespace the application environment deploys into. Must already exist.') param kubernetesNamespace string = 'private-bicep-demo' +@description('Kubernetes namespace the secrets environment deploys into. Must already exist and differ from kubernetesNamespace.') +param secretsNamespace string = 'private-bicep-demo-secrets' + @description('Hostname of the private OCI registry that stores the Bicep recipe, e.g. "myregistry.azurecr.io".') param registryHostname string @@ -30,14 +38,33 @@ param registryUsername string @secure() param registryPassword string -// SecretStore holding the username/password used to authenticate to the private -// OCI registry. For BasicAuth the secret store must expose 'username' and 'password'. -resource registrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { +// Secrets environment. It carries NO recipePacks, so `rad deploy` injects the +// cluster's default recipe pack, which registers the Radius.Security/secrets +// Kubernetes recipe. That recipe materializes the backing Kubernetes Secret the +// Bicep driver later reads. It uses a separate namespace from the application +// environment because Radius rejects two environments that share a namespace. +resource secretsEnv 'Radius.Core/environments@2025-08-01-preview' = { + name: 'private-bicep-secrets-env' + location: 'global' + properties: { + providers: { + kubernetes: { + namespace: secretsNamespace + } + } + } +} + +// Radius.Security/secrets holding the username/password used to authenticate to +// the private OCI registry. Provisioning it runs the secrets recipe, which +// creates a same-named Kubernetes Secret (with 'username' and 'password' keys) +// that the Bicep driver reads at recipe-execution time. +resource registrySecret 'Radius.Security/secrets@2025-08-01-preview' = { name: 'private-bicep-registry-secret' location: 'global' properties: { - resource: '${kubernetesNamespace}/private-bicep-registry-secret' - type: 'generic' + environment: secretsEnv.id + kind: 'generic' data: { username: { value: registryUsername @@ -49,11 +76,12 @@ resource registrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { } } -// BicepConfig carrying the registry authentication. The map key is the registry +// BicepSettings carrying the registry authentication. The map key is the registry // hostname; the driver uses these credentials when pulling Bicep recipes hosted -// on that registry. -resource bicepConfig 'Radius.Core/bicepConfigs@2025-08-01-preview' = { - name: 'private-bicep-config' +// on that registry. The authentication method is selected here (BasicAuth), not +// from the secret's kind. +resource bicepSettings 'Radius.Core/bicepSettings@2025-08-01-preview' = { + name: 'private-bicep-settings' location: 'global' properties: { registryAuthentications: { @@ -66,7 +94,7 @@ resource bicepConfig 'Radius.Core/bicepConfigs@2025-08-01-preview' = { } // RecipePack pointing at the *private* Bicep recipe. Because the environment -// references the bicepConfig above, Radius authenticates to the registry when +// references the bicepSettings above, Radius authenticates to the registry when // the recipe is executed. resource recipePack 'Radius.Core/recipePacks@2025-08-01-preview' = { name: 'private-bicep-recipe-pack' @@ -74,14 +102,14 @@ resource recipePack 'Radius.Core/recipePacks@2025-08-01-preview' = { properties: { recipes: { 'Applications.Core/extenders': { - recipeKind: 'bicep' - recipeLocation: recipeLocation + kind: 'bicep' + source: recipeLocation } } } } -// Environment that references both the recipe pack and the bicep registry config. +// Environment that references both the recipe pack and the bicep registry settings. resource env 'Radius.Core/environments@2025-08-01-preview' = { name: 'private-bicep-env' location: 'global' @@ -94,7 +122,7 @@ resource env 'Radius.Core/environments@2025-08-01-preview' = { namespace: kubernetesNamespace } } - bicepConfig: bicepConfig.id + bicepSettings: bicepSettings.id } } diff --git a/docs/demos/private-registries-recipes/bicep/combined.bicep b/docs/demos/private-registries-recipes/bicep/combined.bicep index 9997fb91e6c..605136bd937 100644 --- a/docs/demos/private-registries-recipes/bicep/combined.bicep +++ b/docs/demos/private-registries-recipes/bicep/combined.bicep @@ -1,11 +1,15 @@ // ----------------------------------------------------------------------------- // Scenario 3 - Combined: one environment, private Terraform AND private Bicep // -// A single Radius.Core/environments references BOTH a Radius.Core/terraformConfigs -// (private Terraform registry credentials + env vars) and a Radius.Core/bicepConfigs +// A single Radius.Core/environments references BOTH a Radius.Core/terraformSettings +// (private Terraform registry credentials + env vars) and a Radius.Core/bicepSettings // (private OCI/Bicep registry BasicAuth). This mirrors a platform team that // defines registry authentication once and reuses it across recipe kinds. // +// Both registry credentials are stored as Radius.Security/secrets resources, +// provisioned into a dedicated secrets environment so their backing Kubernetes +// Secrets exist before the recipe drivers resolve them. +// // Replace the parameters below with the details of your own private registries. // See ../README.md for the full step-by-step walkthrough. // ----------------------------------------------------------------------------- @@ -15,9 +19,12 @@ extension radius @description('Name of the Radius Application to create.') param appName string = 'private-combined-demo' -@description('Kubernetes namespace the environment deploys into. Must already exist.') +@description('Kubernetes namespace the application environment deploys into. Must already exist.') param kubernetesNamespace string = 'private-combined-demo' +@description('Kubernetes namespace the secrets environment deploys into. Must already exist and differ from kubernetesNamespace.') +param secretsNamespace string = 'private-combined-demo-secrets' + // --- Private Terraform registry inputs --- @description('Hostname of the private Terraform registry that requires a token.') param terraformRegistryHostname string @@ -44,13 +51,31 @@ param bicepRegistryUsername string @secure() param bicepRegistryPassword string -// SecretStore for the Terraform registry token (key: token). -resource terraformTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { +// Secrets environment. It carries NO recipePacks, so `rad deploy` injects the +// cluster's default recipe pack, which registers the Radius.Security/secrets +// Kubernetes recipe. Both credential secrets below are provisioned here so their +// backing Kubernetes Secrets exist before the recipe drivers resolve them. It +// uses a separate namespace from the application environment because Radius +// rejects two environments that share a namespace. +resource secretsEnv 'Radius.Core/environments@2025-08-01-preview' = { + name: 'combined-secrets-env' + location: 'global' + properties: { + providers: { + kubernetes: { + namespace: secretsNamespace + } + } + } +} + +// Radius.Security/secrets for the Terraform registry token (key: token). +resource terraformTokenSecret 'Radius.Security/secrets@2025-08-01-preview' = { name: 'combined-tf-token-secret' location: 'global' properties: { - resource: '${kubernetesNamespace}/combined-tf-token-secret' - type: 'generic' + environment: secretsEnv.id + kind: 'generic' data: { token: { value: terraformRegistryToken @@ -59,13 +84,13 @@ resource terraformTokenSecret 'Applications.Core/secretStores@2023-10-01-preview } } -// SecretStore for the Bicep OCI registry BasicAuth (keys: username, password). -resource bicepRegistrySecret 'Applications.Core/secretStores@2023-10-01-preview' = { +// Radius.Security/secrets for the Bicep OCI registry BasicAuth (keys: username, password). +resource bicepRegistrySecret 'Radius.Security/secrets@2025-08-01-preview' = { name: 'combined-bicep-registry-secret' location: 'global' properties: { - resource: '${kubernetesNamespace}/combined-bicep-registry-secret' - type: 'generic' + environment: secretsEnv.id + kind: 'generic' data: { username: { value: bicepRegistryUsername @@ -77,8 +102,8 @@ resource bicepRegistrySecret 'Applications.Core/secretStores@2023-10-01-preview' } } -resource tfConfig 'Radius.Core/terraformConfigs@2025-08-01-preview' = { - name: 'combined-tf-config' +resource tfSettings 'Radius.Core/terraformSettings@2025-08-01-preview' = { + name: 'combined-tf-settings' location: 'global' properties: { terraformrc: { @@ -101,8 +126,8 @@ resource tfConfig 'Radius.Core/terraformConfigs@2025-08-01-preview' = { } } -resource bicepConfig 'Radius.Core/bicepConfigs@2025-08-01-preview' = { - name: 'combined-bicep-config' +resource bicepSettings 'Radius.Core/bicepSettings@2025-08-01-preview' = { + name: 'combined-bicep-settings' location: 'global' properties: { registryAuthentications: { @@ -120,14 +145,14 @@ resource recipePack 'Radius.Core/recipePacks@2025-08-01-preview' = { properties: { recipes: { 'Applications.Core/extenders': { - recipeKind: 'terraform' - recipeLocation: terraformRecipeLocation + kind: 'terraform' + source: terraformRecipeLocation } } } } -// One environment, two config resources, both reused from a single definition. +// One environment, two settings resources, both reused from a single definition. resource env 'Radius.Core/environments@2025-08-01-preview' = { name: 'combined-env' location: 'global' @@ -140,8 +165,8 @@ resource env 'Radius.Core/environments@2025-08-01-preview' = { namespace: kubernetesNamespace } } - terraformConfig: tfConfig.id - bicepConfig: bicepConfig.id + terraformSettings: tfSettings.id + bicepSettings: bicepSettings.id } } diff --git a/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep index 87476f2ec88..394d0502263 100644 --- a/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep +++ b/docs/demos/private-registries-recipes/bicep/terraform-private-registry.bicep @@ -2,9 +2,15 @@ // Scenario 2 - Private Terraform module registry / repository // // Demonstrates authenticating to a *private* Terraform module source by attaching -// a Radius.Core/terraformConfigs resource to a Radius.Core/environments. The -// terraformConfig renders a .terraformrc (credentials + provider_installation) -// at recipe execution time and points Terraform at it via TF_CLI_CONFIG_FILE. +// a Radius.Core/terraformSettings resource to a Radius.Core/environments. The +// terraformSettings resource renders a .terraformrc (credentials + provider +// installation) at recipe execution time and points Terraform at it via +// TF_CLI_CONFIG_FILE. +// +// The registry token is stored in a Radius.Security/secrets resource. The +// terraformSettings credentials block references that secret by ID; the +// Terraform driver resolves the secret's 'token' key and renders it into the +// generated .terraformrc. // // Replace the parameters below with the details of your own private registry. // See ../README.md for the full step-by-step walkthrough. @@ -15,9 +21,12 @@ extension radius @description('Name of the Radius Application to create.') param appName string = 'private-tf-demo' -@description('Kubernetes namespace the environment deploys into. Must already exist.') +@description('Kubernetes namespace the application environment deploys into. Must already exist.') param kubernetesNamespace string = 'private-tf-demo' +@description('Kubernetes namespace the secrets environment deploys into. Must already exist and differ from kubernetesNamespace.') +param secretsNamespace string = 'private-tf-demo-secrets' + @description('Hostname of the private Terraform registry that requires a token, e.g. "app.terraform.io" or "registry.mycompany.com".') param terraformRegistryHostname string @@ -31,14 +40,33 @@ param terraformRegistryToken string @description('Name of the Redis cache the example Terraform recipe provisions.') param redisCacheName string = 'tf-private-redis' -// SecretStore holding the Terraform registry token. The terraformConfig -// credentials block references this; the secret store must expose a 'token' key. -resource registryTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' = { +// Secrets environment. It carries NO recipePacks, so `rad deploy` injects the +// cluster's default recipe pack, which registers the Radius.Security/secrets +// Kubernetes recipe. That recipe materializes the backing Kubernetes Secret the +// Terraform driver later reads. It uses a separate namespace from the +// application environment because Radius rejects two environments that share a +// namespace. +resource secretsEnv 'Radius.Core/environments@2025-08-01-preview' = { + name: 'private-tf-secrets-env' + location: 'global' + properties: { + providers: { + kubernetes: { + namespace: secretsNamespace + } + } + } +} + +// Radius.Security/secrets holding the Terraform registry token. The +// terraformSettings credentials block references this by ID; the backing +// Kubernetes Secret must expose a 'token' key, which the driver reads. +resource registryTokenSecret 'Radius.Security/secrets@2025-08-01-preview' = { name: 'private-tf-token-secret' location: 'global' properties: { - resource: '${kubernetesNamespace}/private-tf-token-secret' - type: 'generic' + environment: secretsEnv.id + kind: 'generic' data: { token: { value: terraformRegistryToken @@ -47,14 +75,14 @@ resource registryTokenSecret 'Applications.Core/secretStores@2023-10-01-preview' } } -// TerraformConfig that: +// TerraformSettings that: // * authenticates to the private Terraform registry (credentials block), and // * keeps provider installation on the default "direct" path (fetch providers // from the public registry). Swap "direct" for "networkMirror" if your // providers also live behind a private mirror. // * injects env vars into the Terraform process (here, raise the log level). -resource tfConfig 'Radius.Core/terraformConfigs@2025-08-01-preview' = { - name: 'private-tf-config' +resource tfSettings 'Radius.Core/terraformSettings@2025-08-01-preview' = { + name: 'private-tf-settings' location: 'global' properties: { terraformrc: { @@ -85,14 +113,14 @@ resource recipePack 'Radius.Core/recipePacks@2025-08-01-preview' = { properties: { recipes: { 'Applications.Core/extenders': { - recipeKind: 'terraform' - recipeLocation: recipeLocation + kind: 'terraform' + source: recipeLocation } } } } -// Environment that references both the recipe pack and the Terraform config. +// Environment that references both the recipe pack and the Terraform settings. resource env 'Radius.Core/environments@2025-08-01-preview' = { name: 'private-tf-env' location: 'global' @@ -105,7 +133,7 @@ resource env 'Radius.Core/environments@2025-08-01-preview' = { namespace: kubernetesNamespace } } - terraformConfig: tfConfig.id + terraformSettings: tfSettings.id } } diff --git a/docs/demos/private-registries-recipes/recipes/redis-recipe.bicep b/docs/demos/private-registries-recipes/recipes/redis-recipe.bicep index 55f394cd104..2c7eed512de 100644 --- a/docs/demos/private-registries-recipes/recipes/redis-recipe.bicep +++ b/docs/demos/private-registries-recipes/recipes/redis-recipe.bicep @@ -2,7 +2,7 @@ // Sample Bicep recipe for the private-registry demo (Scenario 1). // // Publish this to your *private* OCI registry with `rad bicep publish`, then -// point the RecipePack's recipeLocation at the published artifact. See +// point the RecipePack's recipe `source` at the published artifact. See // ../README.md for the publishing commands. // // The recipe provisions a small Redis Deployment + Service on Kubernetes and diff --git a/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 index fae14490296..371dd9b31e7 100644 --- a/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 +++ b/docs/demos/private-registries-recipes/scripts/run-e2e.ps1 @@ -60,6 +60,12 @@ $RadGroup = 'demo-private-registries' $BicepNamespace = 'private-bicep-demo' $TfNamespace = 'private-tf-demo' $CombinedNamespace = 'private-combined-demo' +# Each scenario provisions its Radius.Security/secrets into a dedicated secrets +# environment, which needs its own namespace (Radius rejects two environments +# that share a namespace). +$BicepSecretsNamespace = 'private-bicep-demo-secrets' +$TfSecretsNamespace = 'private-tf-demo-secrets' +$CombinedSecretsNamespace = 'private-combined-demo-secrets' function Test-Tools { foreach ($tool in @('rad', 'kubectl')) { @@ -107,6 +113,7 @@ function Invoke-Bicep { Assert-Vars @('BICEP_REGISTRY', 'BICEP_RECIPE', 'BICEP_REGISTRY_USERNAME', 'BICEP_REGISTRY_PASSWORD') Confirm-Namespace $BicepNamespace + Confirm-Namespace $BicepSecretsNamespace if (-not $SkipPublish) { Write-Host "Publishing Bicep recipe to $(Get-Var 'BICEP_RECIPE')" @@ -130,6 +137,7 @@ function Invoke-Bicep { function Invoke-Terraform { Assert-Vars @('TF_REGISTRY_HOST', 'TF_RECIPE_LOCATION', 'TF_REGISTRY_TOKEN') Confirm-Namespace $TfNamespace + Confirm-Namespace $TfSecretsNamespace Write-Host 'Deploying Scenario 2 (private Terraform registry)' rad deploy (Join-Path $BicepDir 'terraform-private-registry.bicep') ` @@ -147,6 +155,7 @@ function Invoke-Combined { 'BICEP_REGISTRY_USERNAME', 'BICEP_REGISTRY_PASSWORD', 'TF_REGISTRY_HOST', 'TF_RECIPE_LOCATION', 'TF_REGISTRY_TOKEN') Confirm-Namespace $CombinedNamespace + Confirm-Namespace $CombinedSecretsNamespace if (-not $SkipPublish) { Write-Host "Publishing Bicep recipe to $(Get-Var 'BICEP_RECIPE')" @@ -166,8 +175,8 @@ function Invoke-Combined { Write-Host 'Verifying Scenario 3' rad resource show Radius.Core/environments combined-env - rad resource list Radius.Core/terraformConfigs - rad resource list Radius.Core/bicepConfigs + rad resource list Radius.Core/terraformSettings + rad resource list Radius.Core/bicepSettings } function Remove-Demo { @@ -179,7 +188,9 @@ function Remove-Demo { rad group switch default 2>$null rad group delete $RadGroup --yes 2>$null kubectl delete namespace ` - $BicepNamespace $TfNamespace $CombinedNamespace --ignore-not-found + $BicepNamespace $TfNamespace $CombinedNamespace ` + $BicepSecretsNamespace $TfSecretsNamespace $CombinedSecretsNamespace ` + --ignore-not-found Write-Host 'Cleanup complete' } diff --git a/docs/demos/private-registries-recipes/scripts/run-e2e.sh b/docs/demos/private-registries-recipes/scripts/run-e2e.sh index 292c66bfaae..416808199bf 100755 --- a/docs/demos/private-registries-recipes/scripts/run-e2e.sh +++ b/docs/demos/private-registries-recipes/scripts/run-e2e.sh @@ -26,6 +26,12 @@ readonly RAD_GROUP="demo-private-registries" readonly BICEP_NAMESPACE="private-bicep-demo" readonly TF_NAMESPACE="private-tf-demo" readonly COMBINED_NAMESPACE="private-combined-demo" +# Each scenario provisions its Radius.Security/secrets into a dedicated secrets +# environment, which needs its own namespace (Radius rejects two environments +# that share a namespace). +readonly BICEP_SECRETS_NAMESPACE="private-bicep-demo-secrets" +readonly TF_SECRETS_NAMESPACE="private-tf-demo-secrets" +readonly COMBINED_SECRETS_NAMESPACE="private-combined-demo-secrets" # Default values SCENARIO="all" @@ -110,6 +116,7 @@ run_bicep() { require_vars BICEP_REGISTRY BICEP_RECIPE \ BICEP_REGISTRY_USERNAME BICEP_REGISTRY_PASSWORD ensure_namespace "${BICEP_NAMESPACE}" + ensure_namespace "${BICEP_SECRETS_NAMESPACE}" if [[ "${PUBLISH_RECIPE}" == "true" ]]; then echo "Publishing Bicep recipe to ${BICEP_RECIPE}" @@ -133,6 +140,7 @@ run_bicep() { run_terraform() { require_vars TF_REGISTRY_HOST TF_RECIPE_LOCATION TF_REGISTRY_TOKEN ensure_namespace "${TF_NAMESPACE}" + ensure_namespace "${TF_SECRETS_NAMESPACE}" echo "Deploying Scenario 2 (private Terraform registry)" rad deploy "${BICEP_DIR}/terraform-private-registry.bicep" \ @@ -150,6 +158,7 @@ run_combined() { BICEP_REGISTRY_USERNAME BICEP_REGISTRY_PASSWORD \ TF_REGISTRY_HOST TF_RECIPE_LOCATION TF_REGISTRY_TOKEN ensure_namespace "${COMBINED_NAMESPACE}" + ensure_namespace "${COMBINED_SECRETS_NAMESPACE}" if [[ "${PUBLISH_RECIPE}" == "true" ]]; then echo "Publishing Bicep recipe to ${BICEP_RECIPE}" @@ -169,8 +178,8 @@ run_combined() { echo "Verifying Scenario 3" rad resource show Radius.Core/environments combined-env - rad resource list Radius.Core/terraformConfigs - rad resource list Radius.Core/bicepConfigs + rad resource list Radius.Core/terraformSettings + rad resource list Radius.Core/bicepSettings } cleanup_demo() { @@ -183,6 +192,8 @@ cleanup_demo() { rad group delete "${RAD_GROUP}" --yes 2>/dev/null || true kubectl delete namespace \ "${BICEP_NAMESPACE}" "${TF_NAMESPACE}" "${COMBINED_NAMESPACE}" \ + "${BICEP_SECRETS_NAMESPACE}" "${TF_SECRETS_NAMESPACE}" \ + "${COMBINED_SECRETS_NAMESPACE}" \ --ignore-not-found echo "Cleanup complete" }