Skip to content

Commit 9b353de

Browse files
committed
Generate environment variables for managed secrets
Copilot-Session: 04ca0340-665a-4e7a-a907-0a0e6ea942c5 Signed-off-by: willdavsmith <willdavsmith@gmail.com>
1 parent ebdeec9 commit 9b353de

30 files changed

Lines changed: 334 additions & 250 deletions

File tree

.github/scripts/test-recipe.sh

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,47 @@ ensure_workspace_context() {
4848
rad workspace switch "$WORKSPACE_NAME" >/dev/null 2>&1 || true
4949
}
5050

51+
validate_connection_environment_variables() {
52+
if [[ "$PLATFORM" != "kubernetes" ]]; then
53+
return 0
54+
fi
55+
56+
if [[ "$RESOURCE_TYPE" == "Radius.Compute/containers" ]]; then
57+
echo "==> Validating direct Secret connection environment variables"
58+
local deployment_json
59+
deployment_json=$(kubectl get deployment myapp -n testapp -o json) || return 1
60+
61+
echo "$deployment_json" | jq -e '
62+
(.spec.template.spec.containers[] | select(.name == "orderprocessor").env) as $app |
63+
(.spec.template.spec.initContainers[] | select(.name == "dbmigration").env) as $init |
64+
($app | any(.name == "CONNECTION_SECRETS_USERNAME" and .value == "explicit-user")) and
65+
($app | any(.name == "CONNECTION_SECRETS_PASSWORD" and .valueFrom.secretKeyRef.key == "password")) and
66+
($app | any(.name == "CONNECTION_SECRETS_APIKEY" and .valueFrom.secretKeyRef.key == "apikey")) and
67+
($app | all(.name | startswith("CONNECTION_DISABLEDSECRETS_") | not)) and
68+
($init | any(.name == "CONNECTION_SECRETS_USERNAME" and .valueFrom.secretKeyRef.key == "username")) and
69+
($init | any(.name == "CONNECTION_SECRETS_PASSWORD" and .valueFrom.secretKeyRef.key == "password")) and
70+
($init | any(.name == "CONNECTION_SECRETS_APIKEY" and .valueFrom.secretKeyRef.key == "apikey")) and
71+
($init | all(.name | startswith("CONNECTION_DISABLEDSECRETS_") | not))
72+
' >/dev/null
73+
elif [[ "$RESOURCE_TYPE" == "Radius.Data/redisCaches" ]]; then
74+
echo "==> Validating producer managed-secret environment variables"
75+
local deployment_json
76+
deployment_json=$(kubectl get deployment democontainer -n testapp -o json) || return 1
77+
78+
echo "$deployment_json" | jq -e '
79+
(.spec.template.spec.containers[] | select(.name == "demo").env) as $env |
80+
($env | any(.name == "CONNECTION_REDIS_HOST" and has("value"))) and
81+
($env | any(.name == "CONNECTION_REDIS_PORT" and has("value"))) and
82+
($env | any(
83+
.name == "CONNECTION_REDIS_URL" and
84+
.valueFrom.secretKeyRef.key == "url" and
85+
(.valueFrom.secretKeyRef.name | length > 0)
86+
)) and
87+
($env | all(.name != "CONNECTION_REDIS_SECRETS"))
88+
' >/dev/null
89+
fi
90+
}
91+
5192
resolve_environment_path() {
5293
# Resolve the full environment resource ID to avoid hardcoding the provider path
5394
if ! ENVIRONMENT_JSON=$(rad env show "$ENVIRONMENT_NAME" --workspace "$WORKSPACE_NAME" -o json --preview 2>/dev/null); then
@@ -180,6 +221,15 @@ fi
180221
# Deploy the test app
181222
if rad deploy "$TEST_FILE" --application "$APP_NAME" -e "$ENVIRONMENT_PATH" $PARAMS; then
182223
echo "==> Test deployment successful"
224+
225+
if ! validate_connection_environment_variables; then
226+
echo "==> Connection environment variable validation failed"
227+
rad app delete "$APP_NAME" --yes 2>/dev/null || true
228+
kubectl delete secrets --all -n testapp 2>/dev/null || true
229+
kubectl delete deployments --all -n testapp 2>/dev/null || true
230+
kubectl delete services --all -n testapp 2>/dev/null || true
231+
exit 1
232+
fi
183233

184234
# Cleanup: delete the app
185235
echo "==> Cleaning up test application"

AI/models/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ Recipes for this resource type are provided through the platform Recipe Packs at
2626

2727
## Using the resource type
2828

29-
Add a `models` resource to your application and connect a container to it. Radius injects the model's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_LLM_MODEL` and `CONNECTION_LLM_ENDPOINT`). The `apiKey` secret is not injected — bind it from the managed `Radius.Security/secrets` resource with a container `secretKeyRef` using `model.properties.secrets.name`. See [`test/app.bicep`](test/app.bicep) for a complete example.
29+
Add a `models` resource to your application and connect a container to it. One connection named `llm` injects ordinary `CONNECTION_LLM_MODEL` and `CONNECTION_LLM_ENDPOINT` values plus the secret-backed `CONNECTION_LLM_APIKEY`. No second managed-Secret connection is needed. For custom Kubernetes configuration, `model.properties.secrets.name` remains available as the `secretName` for an explicitly authored `secretKeyRef`. See [`test/app.bicep`](test/app.bicep) for a complete example.

AI/models/models.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ types:
4646
4747
- CONNECTION_LLM_MODEL
4848
- CONNECTION_LLM_ENDPOINT
49+
- CONNECTION_LLM_APIKEY (secret-backed)
4950
50-
The `apiKey` secret is NOT injected via the connection — it is materialized
51-
into a managed `Radius.Security/secrets` resource. Bind it into a container
52-
env var with a `secretKeyRef`, using `model.properties.secrets.name` as the
53-
`secretName` and key `apiKey` (see the `secrets` property).
51+
The same connection injects `apiKey` through a Kubernetes secret reference;
52+
no second managed-Secret connection is needed. For custom Kubernetes
53+
configuration, use `model.properties.secrets.name` as the `secretName` and
54+
`apiKey` as the key in an explicitly authored `secretKeyRef`.
5455
5556
apiVersions:
5657
'2025-08-01-preview':

AI/models/test/app.bicep

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,6 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
2727
containers: {
2828
demo: {
2929
image: 'ghcr.io/radius-project/samples/demo:latest'
30-
// The recipe's secret output(s) are materialized into a managed
31-
// Radius.Security/secrets resource and consumed here BY REFERENCE via
32-
// secretKeyRef — the value never lands on model state.
33-
env: {
34-
MODEL_APIKEY: {
35-
valueFrom: {
36-
secretKeyRef: {
37-
secretName: model.properties.secrets.name
38-
key: 'apiKey'
39-
}
40-
}
41-
}
42-
}
4330
ports: {
4431
web: {
4532
containerPort: 3000
@@ -48,6 +35,7 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
4835
}
4936
}
5037
connections: {
38+
// Injects model/endpoint plus secret-backed CONNECTION_LLM_APIKEY.
5139
llm: {
5240
source: model.id
5341
}

AI/search/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ Recipes for this resource type are provided through the platform Recipe Packs at
2525

2626
## Using the resource type
2727

28-
Add a `search` resource to your application and connect a container to it. Radius injects the search service's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_SEARCH_ENDPOINT`). The `apiKey` secret is not injected — bind it from the managed `Radius.Security/secrets` resource with a container `secretKeyRef` using `search.properties.secrets.name`. See [`test/app.bicep`](test/app.bicep) for a complete example.
28+
Add a `search` resource to your application and connect a container to it. One connection named `search` injects the ordinary `CONNECTION_SEARCH_ENDPOINT` value plus the secret-backed `CONNECTION_SEARCH_APIKEY`. No second managed-Secret connection is needed. For custom Kubernetes configuration, `search.properties.secrets.name` remains available as the `secretName` for an explicitly authored `secretKeyRef`. See [`test/app.bicep`](test/app.bicep) for a complete example.

AI/search/search.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ types:
4646
example the connection name is `search` so the environment variables will be:
4747
4848
- CONNECTION_SEARCH_ENDPOINT
49+
- CONNECTION_SEARCH_APIKEY (secret-backed)
4950
50-
The `apiKey` secret is NOT injected via the connection — it is materialized
51-
into a managed `Radius.Security/secrets` resource. Bind it into a container
52-
env var with a `secretKeyRef`, using `search.properties.secrets.name` as the
53-
`secretName` and key `apiKey` (see the `secrets` property).
51+
The same connection injects `apiKey` through a Kubernetes secret reference;
52+
no second managed-Secret connection is needed. For custom Kubernetes
53+
configuration, use `search.properties.secrets.name` as the `secretName` and
54+
`apiKey` as the key in an explicitly authored `secretKeyRef`.
5455
5556
apiVersions:
5657
'2025-08-01-preview':

AI/search/test/app.bicep

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,6 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
2626
containers: {
2727
demo: {
2828
image: 'ghcr.io/radius-project/samples/demo:latest'
29-
// The recipe's secret output(s) are materialized into a managed
30-
// Radius.Security/secrets resource and consumed here BY REFERENCE via
31-
// secretKeyRef — the value never lands on searchService state.
32-
env: {
33-
SEARCH_APIKEY: {
34-
valueFrom: {
35-
secretKeyRef: {
36-
secretName: searchService.properties.secrets.name
37-
key: 'apiKey'
38-
}
39-
}
40-
}
41-
}
4229
ports: {
4330
web: {
4431
containerPort: 3000
@@ -47,6 +34,7 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
4734
}
4835
}
4936
connections: {
37+
// Injects endpoint plus secret-backed CONNECTION_SEARCH_APIKEY.
5038
search: {
5139
source: searchService.id
5240
}

Compute/containers/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ A list of available Recipes for this Resource Type, including links to the Bicep
4949
| context.resource.properties.extensions | Dapr extension for Radius |
5050
| context.resource.properties.platformOptions | Kubernetes Deployment and Pod override properties |
5151

52+
### Connections and secrets
53+
54+
For ordinary connections, the Kubernetes Recipes inject `context.resource.connections.<name>.properties` as `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` environment variables. When a producer Recipe returns secrets, Radius supplies reference metadata under `context.resource.connections.<name>.secrets`; the same connection injects each secret through a Kubernetes `secretKeyRef`. For example, Redis connection `redis` supplies its ordinary `host` and `port` values together with the secret-backed `CONNECTION_REDIS_URL`.
55+
56+
Direct connections to user-authored `Radius.Security/secrets` resources remain supported and inject one secret-backed variable per data key. Both regular and init containers receive generated variables. Explicit container environment variables take precedence, managed secret references take precedence over ordinary properties with the same generated name, and `disableDefaultEnvVars: true` disables both ordinary and secret-backed variables for that connection.
57+
58+
Connection names, property names, and secret names are uppercased when generating environment variable names. Names that collide after uppercasing are rejected. The Kubernetes Secret name is derived from the final segment of each full Radius Secret resource ID. Secret values remain in Kubernetes references and are never copied into Recipe output or plaintext container configuration.
59+
60+
The Azure ACI Recipe is unchanged and does not consume the Kubernetes secret reference metadata described above.
61+
5262
Note: The Azure ACI recipe does not support `context.resource.properties.extensions.daprSidecar` and ignores Dapr sidecar configuration provided through `extensions`.
5363
Note: The Azure ACI recipe does not support `context.resource.properties.replicas` or `context.resource.properties.autoScaling.*`; scaling is controlled by recipe-specific parameters (`desiredCount` and `maintainDesiredCount`).
5464
Note: The Azure ACI recipe does not support `context.resource.properties.containers.args` or `context.resource.properties.containers.workingDir`; `args` are only used by merging into the ACI `command` array, and `workingDir` is ignored.

Compute/containers/containers.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ types:
8989
9090
To mount a persistent volume or secret see the PersistentVolumes and Secrets Resource Types.
9191
92+
On Kubernetes, each connection injects ordinary producer properties and
93+
Recipe-managed secret references using
94+
`CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>`. Explicit environment variables
95+
take precedence, followed by managed secret references, then ordinary values.
96+
Set `disableDefaultEnvVars: true` to disable all generated variables for a
97+
connection. This behavior applies to regular and init containers.
98+
9299
apiVersions:
93100
'2025-08-01-preview':
94101
schema:
@@ -117,7 +124,7 @@ types:
117124
description: (Required) The resource ID of the resource this container is dependent upon.
118125
disableDefaultEnvVars:
119126
type: boolean
120-
description: (Optional) Disables the automatic injection of environment variables from connected resource properties.
127+
description: (Optional) Disables automatic injection of ordinary properties and secret-backed environment variables from this connection.
121128
required: [source]
122129
containers:
123130
type: object

0 commit comments

Comments
 (0)