diff --git a/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep b/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep index 3cfb533d..797707e7 100644 --- a/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep +++ b/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep @@ -188,7 +188,16 @@ resource svc 'core/Service@v1' = { } ////////////////////////////////////////// -// Output Radius result +// Output Radius result +// +// No `secrets` output: the administrator credentials flow *into* this Recipe as +// user-supplied properties, so there is nothing for the Recipe to hand back. An +// application that needs the password for a consuming container already holds it +// and should author its own Radius.Security/secrets resource, binding it with +// `valueFrom.secretKeyRef` (see test/app.bicep). This matches the Azure recipe in +// recipe-packs/azure/aks-recipepack.bicep, which maps only `host`. Contrast with +// redisCaches, where the access key is generated by the infrastructure and so is +// returned via `outputs.secrets`. ////////////////////////////////////////// output result object = { @@ -204,11 +213,8 @@ output result object = { ) values: { host: '${svc.metadata.name}.${svc.metadata.namespace}.svc.cluster.local' - port: port + // `port` is declared `type: string` on the Resource Type, so emit a string. + port: string(port) database: database } - secrets: { - password: password - connectionString: 'postgresql://${username}:${password}@${svc.metadata.name}.${svc.metadata.namespace}.svc.cluster.local:${port}/${database}' - } } diff --git a/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf b/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf index bffd681e..76ad8ddf 100644 --- a/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf +++ b/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf @@ -10,7 +10,7 @@ terraform { variable "context" { description = "This variable contains Radius Recipe context." - type = any + type = any } variable "memory" { @@ -32,17 +32,17 @@ variable "memory" { } locals { - resource_name = var.context.resource.name - application_name = var.context.application != null ? var.context.application.name : "" - environment_name = var.context.environment != null ? var.context.environment.name : "" - resource_group = element(split("/", var.context.resource.id), 5) - namespace = var.context.runtime.kubernetes.namespace - port = 5432 - tag = "16-alpine" - username = var.context.resource.properties.username - password = var.context.resource.properties.password - database = try(var.context.resource.properties.database, "postgres_db") - size_value = try(var.context.resource.properties.size, "S") + resource_name = var.context.resource.name + application_name = var.context.application != null ? var.context.application.name : "" + environment_name = var.context.environment != null ? var.context.environment.name : "" + resource_group = element(split("/", var.context.resource.id), 5) + namespace = var.context.runtime.kubernetes.namespace + port = 5432 + tag = "16-alpine" + username = var.context.resource.properties.username + password = var.context.resource.properties.password + database = try(var.context.resource.properties.database, "postgres_db") + size_value = try(var.context.resource.properties.size, "S") labels = { "radapp.io/resource" = local.resource_name @@ -150,6 +150,8 @@ resource "kubernetes_service" "postgres" { } } +# The administrator credentials are user-supplied Recipe inputs. They configure +# PostgreSQL through the Kubernetes Secret above but are not Recipe outputs. output "result" { value = { resources = [ @@ -159,13 +161,8 @@ output "result" { ] values = { host = "${kubernetes_service.postgres.metadata[0].name}.${kubernetes_service.postgres.metadata[0].namespace}.svc.cluster.local" - port = local.port + port = tostring(local.port) database = local.database } - secrets = { - password = local.password - connectionString = "postgresql://${local.username}:${local.password}@${kubernetes_service.postgres.metadata[0].name}.${kubernetes_service.postgres.metadata[0].namespace}.svc.cluster.local:${local.port}/${local.database}" - } } - sensitive = true } diff --git a/Data/postgreSqlDatabases/test/app.bicep b/Data/postgreSqlDatabases/test/app.bicep index 62e78b36..e1fef655 100644 --- a/Data/postgreSqlDatabases/test/app.bicep +++ b/Data/postgreSqlDatabases/test/app.bicep @@ -3,17 +3,37 @@ extension radius @description('The ID of your Radius Environment. Set automatically by the rad CLI.') param environment string -@description('Database admin password. Set on the resource `password` property (x-radius-sensitive), so Radius encrypts it at rest and injects it decrypted into the Recipe as the flexible server administrator password.') +@description('Database admin password. Set on the `password` property of the database (x-radius-sensitive, so Radius encrypts it at rest and injects it decrypted into the Recipe) and stored in a Radius.Security/secrets resource for the consuming container to bind by reference.') @secure() param password string resource app 'Radius.Core/applications@2025-08-01-preview' = { - name: 'postgresql-azure-test' + name: 'postgresql-test' properties: { environment: environment } } +// The password is also needed by the consuming container. Store it in a +// Radius.Security/secrets resource rather than passing it to the container as a +// plain `env` value: `data.value` is x-radius-sensitive (encrypted at rest, +// redacted on reads), whereas a container `env.value` is stored unencrypted on +// the container resource and rendered literally into the Pod spec. +resource dbCreds 'Radius.Security/secrets@2025-08-01-preview' = { + // Keep this distinct from the Recipe-owned `postgresql-credentials` + // Kubernetes Secret that configures the PostgreSQL container. + name: 'postgresql-client-credentials' + properties: { + environment: environment + application: app.id + data: { + password: { + value: password + } + } + } +} + resource postgresql 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' = { name: 'postgresql' properties: { @@ -34,6 +54,22 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = { containers: { demo: { image: 'ghcr.io/radius-project/samples/demo:latest' + // Host, port, username, and database arrive automatically as + // CONNECTION_POSTGRESQL_* env vars from the connection below. The + // connection cannot carry the password (x-radius-sensitive properties + // redact to null on reads and are skipped), so it is bound by reference + // here under the same naming scheme, filling the one gap the connection + // leaves. The value never lands in the Pod spec or on this container. + env: { + CONNECTION_POSTGRESQL_PASSWORD: { + valueFrom: { + secretKeyRef: { + secretName: dbCreds.name + key: 'password' + } + } + } + } ports: { web: { containerPort: 3000 @@ -42,7 +78,9 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = { } } connections: { - postgres: { + // Named `postgresql` so the injected variables are CONNECTION_POSTGRESQL_*, + // which is the prefix the demo image looks for. + postgresql: { source: postgresql.id } }