Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,88 @@ var isSecretsResource = reduce(items(connectionDefinitions), {}, (acc, conn) =>
}))

// Secrets connections to inject via envFrom.secretRef
// The K8s secret name is the Radius resource name (last segment of the source ID)
// The K8s secret name is the Radius resource name (last segment of the source ID) or a surfaced secretName property on the connection
var secretsEnvFrom = reduce(items(resourceConnections), [], (acc, conn) =>
isSecretsResource[conn.key] && connectionDefinitions[conn.key].?disableDefaultEnvVars != true
? concat(acc, [{
prefix: toUpper('CONNECTION_${conn.key}_')
secretRef: {
// Extract the secret name from the connection source (last segment of the resource ID)
name: last(split(string(connectionDefinitions[conn.key].source), '/'))
}
}])
: acc
connectionDefinitions[conn.key].?disableDefaultEnvVars == true
? acc
: isSecretsResource[conn.key]
? concat(acc, [{
prefix: toUpper('CONNECTION_${conn.key}_')
secretRef: {
// Extract the secret name from the connection source (last segment of the resource ID)
name: last(split(string(connectionDefinitions[conn.key].source), '/'))
}
}])
: acc
)

// Explicitly map common secret keys to uppercase env vars when a secretName is provided
var connectionSecretKeyEnvVars = reduce(items(resourceConnections), [], (acc, conn) =>
connectionDefinitions[conn.key].?disableDefaultEnvVars == true
? acc
: contains(conn.value ?? {}, 'secretName')
? concat(acc, [
{
name: toUpper('CONNECTION_${conn.key}_USERNAME')
valueFrom: {
secretKeyRef: {
name: string(conn.value.secretName)
key: 'username'
}
}
}
{
name: toUpper('CONNECTION_${conn.key}_PASSWORD')
valueFrom: {
secretKeyRef: {
name: string(conn.value.secretName)
key: 'password'
}
}
}
])
: contains(conn.value.?properties ?? {}, 'secretName')
? concat(acc, [
{
name: toUpper('CONNECTION_${conn.key}_USERNAME')
valueFrom: {
secretKeyRef: {
name: string(conn.value.properties.secretName)
key: 'username'
}
}
}
{
name: toUpper('CONNECTION_${conn.key}_PASSWORD')
valueFrom: {
secretKeyRef: {
name: string(conn.value.properties.secretName)
key: 'password'
}
}
}
])
: acc
)

// Build environment variables from non-secrets connections when not explicitly disabled via disableDefaultEnvVars
// Secrets connections use envFrom.secretRef instead for cleaner injection
// Each connection's resource properties become CONNECTION_<CONNECTION_NAME>_<PROPERTY_NAME>
// Each connection's resource properties (including the nested properties bag) become CONNECTION_<CONNECTION_NAME>_<PROPERTY_NAME>
var connectionEnvVars = reduce(items(resourceConnections), [], (acc, conn) =>
// Only process non-secrets connections here (secrets use envFrom)
// Only process non-secrets connections here (secrets use envFrom)
!isSecretsResource[conn.key] && connectionDefinitions[conn.key].?disableDefaultEnvVars != true
? concat(acc,
// Add resource properties directly from connection (excluding metadata properties)
? concat(
acc,
// Add top-level connection properties (excluding metadata and the nested properties bag)
reduce(items(conn.value ?? {}), [], (envAcc, prop) =>
contains(excludedProperties, prop.key)
(prop.key == 'properties' || prop.key == 'secretName' || contains(excludedProperties, prop.key))
? envAcc
: concat(envAcc, [{
name: toUpper('CONNECTION_${conn.key}_${prop.key}')
value: string(prop.value)
}])
),
// Flatten the nested connection.properties bag so values like host/port become their own env vars
reduce(items(conn.value.?properties ?? {}), [], (envAcc, prop) =>
(prop.key == 'secretName' || contains(excludedProperties, prop.key))
? envAcc
: concat(envAcc, [{
name: toUpper('CONNECTION_${conn.key}_${prop.key}')
Expand Down Expand Up @@ -102,7 +161,7 @@ var containerSpecs = reduce(containerItems, [], (acc, item) => concat(acc, [{
} : {},
// Add environment variables from container definition and connections
// Connection environment variables are automatically added from output values
(contains(item.value, 'env') || length(connectionEnvVars) > 0) ? {
(contains(item.value, 'env') || length(connectionEnvVars) > 0 || length(connectionSecretKeyEnvVars) > 0) ? {
env: concat(
// Container-defined env vars
reduce(items(item.value.?env ?? {}), [], (envAcc, envItem) => concat(envAcc, [union(
Expand All @@ -120,7 +179,9 @@ var containerSpecs = reduce(containerItems, [], (acc, item) => concat(acc, [{
} : {}
)])),
// Connection-derived env vars (non-secrets connections)
connectionEnvVars
connectionEnvVars,
// Explicit secret key env vars (uppercase) when secretName is provided
connectionSecretKeyEnvVars
)
} : {},
// Add envFrom for secrets connections (injects all keys from secret as env vars)
Expand Down
3 changes: 1 addition & 2 deletions Data/postgreSqlDatabases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ A list of available Recipes for this resource type, including links to the Bicep
Properties for the **Radius.Data/postgreSqlDatabases** resource type are provided via the [Recipe Context](https://docs.radapp.io/reference/context-schema/) object. These properties include:

- `context.properties.size`(string, optional): The size of the database. Defaults to `S` if not provided.
- `context.properties.secretName`(string, required): name of the secret containing the database credentials

## Recipe Output Properties

Expand All @@ -27,5 +28,3 @@ The **Radius.Data/postgreSqlDatabases** resource type expects the following outp
- `context.properties.host` (string): The hostname used to connect to the database.
- `context.properties.port` (integer): The port number used to connect to the database.
- `context.properties.database` (string): The name of the database.
- `context.properties.username` (string): The username for connecting to the database.
- `context.properties.password` (string): The password for connecting to the database.
57 changes: 34 additions & 23 deletions Data/postgreSqlDatabases/postgreSqlDatabases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,43 @@ namespace: Radius.Data
types:
postgreSqlDatabases:
description: |
The Radius.Data/postgreSQLDatabases Resource Type deploys a PostgresSQL database. To deploy a new PostgresSQL database, add the postgreSQLDatabases resource to the application definition Bicep file.

The Radius.Data/postgreSqlDatabases Resource Type deploys a PostgreSQL database. To deploy a new PostgreSQL database add the postgreSqlDatabases resource and the secret resource to the application definition Bicep file.
```
extension radius
param environment string
resource postgresql 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' = {
name: 'postgresql'
properties: {
environment: environment
application: myApplication.id
size: 'S'
secretName: dbCredentials.name
}
}

resource database 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' = {
name: 'database'
properties: {
application: myApplication.id
environment: environment
resource dbCredentials 'Radius.Security/secrets@2025-08-01-preview' = {
name: 'db-creds'
properties: {
environment: environment
application: myApplication.id
data: {
username: {
value: 'admin'
}
password: {
// From password parameter passed in via CLI
value: password
}
}
}
}
}
```

When deploying the application definition, specify the secret value as a command-line parameter. It is recommended to use a password generator such as `openssl` or equivalent. For example, `rad deploy app.bicep -p password=$(openssl rand -hex 16)`.

To connect your container to the database, create a connection from the Container resource to the database as shown below.
```
resource myApplication 'Applications.Core/Applications@2023-10-01-preview' = { ... }

resource frontend 'Applications.Core/containers@2023-10-01-preview' = {
resource frontend 'Radius.Compute/containers@2025-08-01-preview' = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be

          connections: {
            postgresql: {
              source: postgresql.id

name: 'frontend'
properties: {
application: myApplication.id
Expand All @@ -43,13 +60,12 @@ types:
}
```

The connection automatically injects environment variables into the container for all properties from the database. The environment variables are named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>`. In this example, the connection name is `mysqldb` so the environment variables will be:
The connection automatically injects environment variables into the container for all properties from the database. The environment variables are named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>`. In this example, the connection name is `postgresql` so the environment variables will be:

- CONNECTION_POSTGRESQL_DATABASE
- CONNECTION_POSTGRESQL_USERNAME
- CONNECTION_POSTGRESQL_PASSWORD
- CONNECTION_POSTGRESQL_HOST
- CONNECTION_POSTGRESQL_PORT

apiVersions:
'2025-08-01-preview':
schema:
Expand All @@ -65,6 +81,9 @@ types:
type: string
enum: ['S', 'M', 'L']
description: "(Optional) The size of the PostgreSQL database."
secretName:
type: string
description: "(Required) The name of the secret containing the database credentials."
database:
type: string
description: The name of the database.
Expand All @@ -77,12 +96,4 @@ types:
type: string
description: The port number used to connect to the database.
readOnly: true
username:
type: string
description: The username for connecting to the database.
readOnly: true
password:
type: string
description: The password for connecting to the database.
readOnly: true
required: [environment]
required: [environment,secretName]
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
@description('Information about what resource is calling this Recipe. Generated by Radius.')
param context object
extension kubernetes with {
kubeConfig: ''
namespace: context.runtime.kubernetes.namespace
} as kubernetes

@description('Name of the PostgreSQL database. Defaults to the name of the Radius resource.')
param database string = context.resource.name

@description('PostgreSQL username')
param user string = 'postgres'
//////////////////////////////////////////
// Common Radius variables
//////////////////////////////////////////

@description('PostgreSQL password')
@secure()
#disable-next-line secure-parameter-default
param password string = uniqueString(context.resource.id)
@description('Information about what resource is calling this Recipe. Generated by Radius.')
param context object

@description('Tag to pull for the postgres container image.')
param tag string = '16-alpine'
//////////////////////////////////////////
// PostgreSQL variables
//////////////////////////////////////////

@description('Memory limits for the PostgreSQL container')
var resourceName = context.resource.name
var namespace = context.runtime.kubernetes.namespace
var dbSecretName = context.resource.properties.secretName
var database string = 'postgres_db'
var tag string = '16-alpine'
var port = 5432
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var port = 5432
var port = 5432
var applicationName = context.application != null ? context.application.name : ''
// Extract last segment from environment path for labels
var environmentId = resourceProperties.?environment ?? ''
var environmentParts = environmentId != '' ? split(environmentId, '/') : []
var environmentName = length(environmentParts) > 0
? environmentParts[length(environmentParts) - 1]
: ''
// Extract resource group name
// Index 4 is the resource group name
var resourceGroupName = split(context.resource.id, '/')[4]
// Common labels
var labels = {
'radapp.io/resource': resourceName
'radapp.io/application': applicationName
'radapp.io/environment': environmentName
'radapp.io/resource-type': replace(context.resource.type, '/', '-')
'radapp.io/resource-group': resourceGroupName
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should all of these be populated when deploying Recipes? I think we should prioritize this issue -#60 and not have users write so much code to get Radius metadata.

var memory ={
S: {
memoryRequest: '512Mi'
Expand All @@ -28,33 +33,26 @@ var memory ={
}
}

extension kubernetes with {
kubeConfig: ''
namespace: context.runtime.kubernetes.namespace
} as kubernetes
//////////////////////////////////////////
// PostgreSQL Deployment
//////////////////////////////////////////

var uniqueName = 'postgres-${uniqueString(context.resource.id)}'
var port = 5432

// Based on https://hub.docker.com/_/postgres/
resource postgresql 'apps/Deployment@v1' = {
metadata: {
name: uniqueName
name: resourceName
namespace: namespace
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace: namespace
namespace: namespace
labels: labels

}
spec: {
selector: {
matchLabels: {
app: 'postgresql'
resource: context.resource.name
}
}
template: {
metadata: {
labels: {
app: 'postgresql'
resource: context.resource.name
// Label pods with the application name so `rad run` can find the logs.
'radapp.io/application': context.application == null ? '' : context.application.name
}
}
spec: {
Expand All @@ -76,11 +74,21 @@ resource postgresql 'apps/Deployment@v1' = {
env: [
{
name: 'POSTGRES_USER'
value: user
valueFrom: {
secretKeyRef: {
name: dbSecretName
key: 'username'
}
}
}
{
name: 'POSTGRES_PASSWORD'
value: password
valueFrom: {
secretKeyRef: {
name: dbSecretName
key: 'password'
}
}
}
{
name: 'POSTGRES_DB'
Expand All @@ -96,16 +104,13 @@ resource postgresql 'apps/Deployment@v1' = {

resource svc 'core/Service@v1' = {
metadata: {
name: uniqueName
labels: {
name: uniqueName
}
name: resourceName
namespace: namespace
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace: namespace
namespace: namespace
labels: labels

}
spec: {
type: 'ClusterIP'
selector: {
app: 'postgresql'
resource: context.resource.name
}
ports: [
{
Expand All @@ -115,6 +120,10 @@ resource svc 'core/Service@v1' = {
}
}

//////////////////////////////////////////
// Output Radius result
//////////////////////////////////////////

output result object = {
resources: [
'/planes/kubernetes/local/namespaces/${svc.metadata.namespace}/providers/core/Service/${svc.metadata.name}'
Expand All @@ -124,10 +133,5 @@ output result object = {
host: '${svc.metadata.name}.${svc.metadata.namespace}.svc.cluster.local'
port: port
database: database
username: user
}
secrets: {
#disable-next-line outputs-should-not-contain-secrets
password: password
}
}
Loading
Loading