Skip to content

Commit 1d2085d

Browse files
authored
Merge branch 'main' into add-azure-deployments
2 parents f0aabe8 + f0512e4 commit 1d2085d

File tree

6 files changed

+242
-79
lines changed

6 files changed

+242
-79
lines changed

.github/workflows/validate-resource-types.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ jobs:
3232
RAD_ENVIRONMENT_NAME: default
3333
steps:
3434
- name: Checkout
35-
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
35+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
3636
with:
3737
persist-credentials: false
3838

3939
- name: Setup Node
40-
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
40+
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
4141
with:
4242
node-version: 22
4343
- name: Set up ORAS

Compute/containers/recipes/kubernetes/bicep/kubernetes-containers.bicep

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,49 @@ var labels = {
3535
'radapp.io/application': context.application == null ? '' : context.application.name
3636
}
3737

38-
// Extract connection data from linked resources
38+
// Extract connection data from linked resources (merged with resource properties)
3939
var resourceConnections = context.resource.connections ?? {}
40+
var connectionDefinitions = context.resource.properties.connections ?? {}
4041

41-
// Build environment variables from connections when explicitly enabled via disableDefaultEnvVars
42-
// Each connection's output values become CONNECTION_<CONNECTION_NAME>_<PROPERTY_NAME>
42+
// Properties to exclude from connection environment variables
43+
var excludedProperties = ['recipe', 'status', 'provisioningState']
44+
45+
// Helper function to check if a connection is a secrets resource (using source from original connection definition)
46+
var isSecretsResource = reduce(items(connectionDefinitions), {}, (acc, conn) => union(acc, {
47+
'${conn.key}': contains(string(conn.value.?source ?? ''), 'Radius.Security/secrets')
48+
}))
49+
50+
// Secrets connections to inject via envFrom.secretRef
51+
// The K8s secret name is the Radius resource name (last segment of the source ID)
52+
var secretsEnvFrom = reduce(items(resourceConnections), [], (acc, conn) =>
53+
isSecretsResource[conn.key] && connectionDefinitions[conn.key].?disableDefaultEnvVars != true
54+
? concat(acc, [{
55+
prefix: toUpper('CONNECTION_${conn.key}_')
56+
secretRef: {
57+
// Extract the secret name from the connection source (last segment of the resource ID)
58+
name: last(split(string(connectionDefinitions[conn.key].source), '/'))
59+
}
60+
}])
61+
: acc
62+
)
63+
64+
// Build environment variables from non-secrets connections when not explicitly disabled via disableDefaultEnvVars
65+
// Secrets connections use envFrom.secretRef instead for cleaner injection
66+
// Each connection's resource properties become CONNECTION_<CONNECTION_NAME>_<PROPERTY_NAME>
4367
var connectionEnvVars = reduce(items(resourceConnections), [], (acc, conn) =>
44-
conn.value.?disableDefaultEnvVars == true
45-
? concat(acc, reduce(items(conn.value.?status.?computedValues ?? {}), [], (envAcc, prop) =>
46-
concat(envAcc, [{
47-
name: toUpper('CONNECTION_${conn.key}_${prop.key}')
48-
value: string(prop.value)
49-
}])
50-
))
68+
// Only process non-secrets connections here (secrets use envFrom)
69+
!isSecretsResource[conn.key] && connectionDefinitions[conn.key].?disableDefaultEnvVars != true
70+
? concat(acc,
71+
// Add resource properties directly from connection (excluding metadata properties)
72+
reduce(items(conn.value ?? {}), [], (envAcc, prop) =>
73+
contains(excludedProperties, prop.key)
74+
? envAcc
75+
: concat(envAcc, [{
76+
name: toUpper('CONNECTION_${conn.key}_${prop.key}')
77+
value: string(prop.value)
78+
}])
79+
)
80+
)
5181
: acc
5282
)
5383

@@ -79,12 +109,24 @@ var containerSpecs = reduce(containerItems, [], (acc, item) => concat(acc, [{
79109
{
80110
name: envItem.key
81111
},
82-
contains(envItem.value, 'value') ? { value: envItem.value.value } : {}
112+
contains(envItem.value, 'value') ? { value: envItem.value.value } : {},
113+
(contains(envItem.value, 'valueFrom') && contains(envItem.value.valueFrom, 'secretKeyRef')) ? {
114+
valueFrom: {
115+
secretKeyRef: {
116+
name: envItem.value.valueFrom.secretKeyRef.secretName
117+
key: envItem.value.valueFrom.secretKeyRef.key
118+
}
119+
}
120+
} : {}
83121
)])),
84-
// Connection-derived env vars
122+
// Connection-derived env vars (non-secrets connections)
85123
connectionEnvVars
86124
)
87125
} : {},
126+
// Add envFrom for secrets connections (injects all keys from secret as env vars)
127+
length(secretsEnvFrom) > 0 ? {
128+
envFrom: secretsEnvFrom
129+
} : {},
88130
// Add volume mounts if they exist
89131
contains(item.value, 'volumeMounts') ? {
90132
volumeMounts: reduce(item.value.volumeMounts, [], (vmAcc, vm) => concat(vmAcc, [{
@@ -177,26 +219,20 @@ var podVolumes = reduce(volumeItems, [], (acc, vol) => concat(acc, [union(
177219
{
178220
name: vol.key
179221
},
180-
contains(vol.value, 'persistentVolume') ? union(
181-
(contains(vol.value.persistentVolume, 'claimName') && vol.value.persistentVolume.claimName != '') ? {
182-
persistentVolumeClaim: {
183-
claimName: vol.value.persistentVolume.claimName
184-
}
185-
} : {},
186-
(!(contains(vol.value.persistentVolume, 'claimName') && vol.value.persistentVolume.claimName != '') && contains(resourceConnections, vol.key) && (resourceConnections[vol.key].?status.?computedValues.?claimName ?? '') != '') ? {
187-
persistentVolumeClaim: {
188-
claimName: resourceConnections[vol.key].?status.?computedValues.?claimName
189-
}
190-
} : {}
191-
) : {},
192-
contains(vol.value, 'secret') ? {
222+
contains(vol.value, 'persistentVolume') ? {
223+
persistentVolumeClaim: {
224+
// Extract the PVC name from the resourceId (last segment of the path)
225+
claimName: last(split(string(vol.value.persistentVolume.resourceId), '/'))
226+
}
227+
} : {},
228+
contains(vol.value, 'secretName') ? {
193229
secret: {
194-
secretName: vol.value.secret.secretName
230+
secretName: vol.value.secretName
195231
}
196232
} : {},
197233
contains(vol.value, 'emptyDir') ? {
198234
emptyDir: contains(vol.value.emptyDir, 'medium') ? {
199-
medium: vol.value.emptyDir.medium
235+
medium: vol.value.emptyDir.medium == 'memory' ? 'Memory' : ''
200236
} : {}
201237
} : {}
202238
)]))

Compute/containers/recipes/kubernetes/terraform/main.tf

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ locals {
2727
dapr_app_id = local.has_dapr_sidecar && try(tostring(local.dapr_sidecar.appId), "") != "" ? tostring(local.dapr_sidecar.appId) : local.normalized_name
2828
dapr_app_port = local.has_dapr_sidecar && try(local.dapr_sidecar.appPort, null) != null ? tostring(local.dapr_sidecar.appPort) : null
2929
dapr_config_name = local.has_dapr_sidecar && try(tostring(local.dapr_sidecar.config), "") != "" ? tostring(local.dapr_sidecar.config) : null
30-
dapr_annotations = local.has_dapr_sidecar ? merge(
30+
dapr_annotations = local.has_dapr_sidecar ? merge(
3131
{
3232
"dapr.io/enabled" = "true"
3333
"dapr.io/app-id" = local.dapr_app_id
@@ -36,20 +36,52 @@ locals {
3636
local.dapr_config_name != null ? { "dapr.io/config" = local.dapr_config_name } : {}
3737
) : {}
3838

39-
# Connections - used for linked resources like persistent volumes
39+
# Connections - merged resource data from linked resources
4040
connections = try(var.context.resource.connections, {})
4141

42-
# Connection-derived environment variables, enabled when disableDefaultEnvVars is false
42+
# Connection definitions - original connection config (has source, disableDefaultEnvVars)
43+
connection_definitions = try(var.context.resource.properties.connections, {})
44+
45+
# Properties to exclude from connection environment variables
46+
excluded_properties = ["recipe", "status", "provisioningState"]
47+
48+
# Helper to check if a connection is a secrets resource (from source in connection definition)
49+
is_secrets_resource = {
50+
for conn_name, conn_def in local.connection_definitions :
51+
conn_name => can(regex("Radius.Security/secrets", try(conn_def.source, "")))
52+
}
53+
54+
# Secrets connections to inject via envFrom.secretRef
55+
# The K8s secret name is the Radius resource name (last segment of the source ID)
56+
secrets_env_from = [
57+
for conn_name, conn in local.connections :
58+
{
59+
# Extract the secret name from the connection source (last segment of the resource ID)
60+
name = element(split("/", local.connection_definitions[conn_name].source), length(split("/", local.connection_definitions[conn_name].source)) - 1)
61+
prefix = upper("CONNECTION_${conn_name}_")
62+
}
63+
if try(local.is_secrets_resource[conn_name], false) &&
64+
try(local.connection_definitions[conn_name].disableDefaultEnvVars, false) != true
65+
]
66+
67+
# Connection-derived environment variables for non-secrets connections
68+
# Secrets connections use envFrom.secretRef instead for cleaner injection
69+
# Each connection's resource properties become CONNECTION_<CONNECTION_NAME>_<PROPERTY_NAME>
70+
# Note: disableDefaultEnvVars is on connection_definitions, not the merged connections data
4371
connection_env_vars = flatten([
4472
for conn_name, conn in local.connections :
45-
try(conn.disableDefaultEnvVars, false)
46-
? [
47-
for prop_name, prop_value in try(conn.status.computedValues, {}) : {
48-
name = upper("CONNECTION_${conn_name}_${prop_name}")
49-
value = tostring(prop_value)
50-
}
51-
]
52-
: []
73+
# Only process non-secrets connections here (secrets use envFrom)
74+
!try(local.is_secrets_resource[conn_name], false) &&
75+
try(local.connection_definitions[conn_name].disableDefaultEnvVars, false) != true
76+
? [
77+
# Add resource properties directly from connection (excluding metadata properties)
78+
for prop_name, prop_value in conn : {
79+
name = upper("CONNECTION_${conn_name}_${prop_name}")
80+
value = tostring(prop_value)
81+
}
82+
if !contains(local.excluded_properties, prop_name)
83+
]
84+
: []
5385
])
5486

5587
# Replica count - use from properties or default to 1
@@ -99,23 +131,27 @@ locals {
99131
]
100132

101133
# Environment variables
102-
# TODO: Add support for environment variables from Radius secrets resource
103-
# When a container references a Radius.Security/secrets resource via connections,
104-
# the recipe should populate environment variables from the secret values
105-
# stored in the connected Radius secret resource.
134+
# Supports direct values and secretKeyRef for referencing Kubernetes secrets
106135
env = concat(
107136
[
108137
for env_name, env_config in try(config.env, {}) : {
109-
name = env_name
110-
value = try(env_config.value, null)
111-
value_from = try(env_config.valueFrom, null)
112-
# TODO: Currently only 'value' is rendered in the deployment.
113-
# Add support for 'valueFrom' to reference Kubernetes secrets/configmaps.
138+
name = env_name
139+
value = try(env_config.value, null)
140+
value_from = try(env_config.valueFrom, null) != null ? {
141+
secret_key_ref = try(env_config.valueFrom.secretKeyRef, null) != null ? {
142+
name = env_config.valueFrom.secretKeyRef.secretName
143+
key = env_config.valueFrom.secretKeyRef.key
144+
} : null
145+
} : null
114146
}
115147
],
116148
local.connection_env_vars
117149
)
118150

151+
# Environment variables from secrets (via envFrom.secretRef)
152+
# Injects all keys from secrets connections as environment variables
153+
env_from = local.secrets_env_from
154+
119155
# Volume mounts
120156
volume_mounts = [
121157
for vm in try(config.volumeMounts, []) : {
@@ -164,27 +200,21 @@ locals {
164200
for vol_name, vol_config in local.volumes : {
165201
name = vol_name
166202

167-
# Persistent Volume Claim
168-
persistent_volume_claim = try(vol_config.persistentVolume, null) != null ? (
169-
try(vol_config.persistentVolume.claimName, "") != "" ? {
170-
claim_name = vol_config.persistentVolume.claimName
171-
} : (
172-
try(local.connections[vol_name].status.computedValues.claimName, "") != "" ? {
173-
claim_name = local.connections[vol_name].status.computedValues.claimName
174-
} : null
175-
)
176-
) : null
177-
178-
# Secret
179-
secret = try(vol_config.secret, null) != null ? {
180-
secret_name = vol_config.secret.secretName
203+
# Persistent Volume Claim - extract PVC name from resourceId (last segment of the path)
204+
persistent_volume_claim = try(vol_config.persistentVolume, null) != null ? {
205+
claim_name = element(split("/", vol_config.persistentVolume.resourceId), length(split("/", vol_config.persistentVolume.resourceId)) - 1)
206+
} : null
207+
208+
# Secret volume - use secretName from volume config
209+
secret = try(vol_config.secretName, null) != null ? {
210+
secret_name = vol_config.secretName
181211
} : null
182212

183213
# EmptyDir
184214
empty_dir = try(vol_config.emptyDir, null) != null ? {
185215
medium = try(vol_config.emptyDir.medium, null) != null ? (
186-
lower(vol_config.emptyDir.medium) == "memory" ? "Memory" : lower(vol_config.emptyDir.medium) == "disk" ? "" : vol_config.emptyDir.medium
187-
) : ""
216+
lower(vol_config.emptyDir.medium) == "memory" ? "Memory" : ""
217+
) : null
188218
} : null
189219
}
190220
]
@@ -260,7 +290,7 @@ resource "kubernetes_deployment" "deployment" {
260290

261291
template {
262292
metadata {
263-
labels = local.labels
293+
labels = local.labels
264294
annotations = local.has_dapr_sidecar ? local.dapr_annotations : null
265295
}
266296

@@ -289,7 +319,7 @@ resource "kubernetes_deployment" "deployment" {
289319
}
290320
}
291321

292-
# Environment variables
322+
# Environment variables with direct values
293323
dynamic "env" {
294324
for_each = [for e in init_container.value.env : e if e.value != null]
295325
content {
@@ -298,6 +328,30 @@ resource "kubernetes_deployment" "deployment" {
298328
}
299329
}
300330

331+
# Environment variables with secretKeyRef
332+
dynamic "env" {
333+
for_each = [for e in init_container.value.env : e if e.value == null && try(e.value_from.secret_key_ref, null) != null]
334+
content {
335+
name = env.value.name
336+
value_from {
337+
secret_key_ref {
338+
name = env.value.value_from.secret_key_ref.name
339+
key = env.value.value_from.secret_key_ref.key
340+
}
341+
}
342+
}
343+
}
344+
345+
# Environment variables from secrets (via envFrom.secretRef)
346+
dynamic "env_from" {
347+
for_each = init_container.value.env_from
348+
content {
349+
prefix = env_from.value.prefix
350+
secret_ref {
351+
name = env_from.value.name
352+
}
353+
}
354+
}
301355

302356
# Volume mounts
303357
dynamic "volume_mount" {
@@ -350,6 +404,30 @@ resource "kubernetes_deployment" "deployment" {
350404
}
351405
}
352406

407+
# Environment variables with secretKeyRef
408+
dynamic "env" {
409+
for_each = [for e in container.value.env : e if e.value == null && try(e.value_from.secret_key_ref, null) != null]
410+
content {
411+
name = env.value.name
412+
value_from {
413+
secret_key_ref {
414+
name = env.value.value_from.secret_key_ref.name
415+
key = env.value.value_from.secret_key_ref.key
416+
}
417+
}
418+
}
419+
}
420+
421+
# Environment variables from secrets (via envFrom.secretRef)
422+
dynamic "env_from" {
423+
for_each = container.value.env_from
424+
content {
425+
prefix = env_from.value.prefix
426+
secret_ref {
427+
name = env_from.value.name
428+
}
429+
}
430+
}
353431

354432
# Volume mounts
355433
dynamic "volume_mount" {

0 commit comments

Comments
 (0)