Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 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.resource.connections`: A connection to a Radius.Security/secret is required. The secret must have a username and password.

## Recipe Output Properties

Expand Down
64 changes: 42 additions & 22 deletions Data/postgreSqlDatabases/postgreSqlDatabases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,40 @@ 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 PostgresSQL database. The database resource must have one and only one connection to a Radius.Security/secrets resource with a username and password. To deploy a new PostgresSQL database, first add a secret to the application definition Bicep file. See the Radius.Security/secrets resource type documentation for details. Then add the postgreSQLDatabases resource.

```
extension radius
param environment string
extension postgreSqlDatabases

@description('The Radius environment ID. Typically set by the Radius CLI.')
param environment

resource myApplication 'Radius.Core/applications@2025-08-01-preview' = {
name: 'myApplication'
properties: {
environment: environment
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add an example for the credentials secrets resource declaration here?

resource database 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' = {
name: 'database'
properties: {
application: myApplication.id
environment: environment
size: 'S'
connections: {
credentials: {
source: credentials.id // Assumes the secret resource is named credentials
}
}
}
}
```

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' = { ... }
To connect a container to the database, create a connection from the Container resource to the database and to the secret.

```
resource frontend 'Applications.Core/containers@2023-10-01-preview' = {
name: 'frontend'
properties: {
Expand All @@ -38,18 +53,21 @@ types:
postgresql: {
source: database.id
}
credentials: {
source: credentials.id
}
}
}
}
```

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 environment variables will be:

- CONNECTION_POSTGRESQL_DATABASE
- CONNECTION_POSTGRESQL_USERNAME
- CONNECTION_POSTGRESQL_PASSWORD
- CONNECTION_POSTGRESQL_HOST
- CONNECTION_POSTGRESQL_PORT
- CONNECTION_CREDENTIALS_USERNAME
- CONNECTION_CREDENTIALS_PASSWORD
apiVersions:
'2025-08-01-preview':
schema:
Expand All @@ -64,25 +82,27 @@ types:
size:
type: string
enum: ['S', 'M', 'L']
description: "(Optional) The size of the PostgreSQL database."
database:
type: string
description: The name of the database.
readOnly: true
description: "(Optional) The relative size of the PostgreSQL database, actual size varies based on the Environment. If not specified, small is assumed."
connections:
type: object
description: (Required) A connection to a Secret resource. Secret must have username and password keys.
additionalProperties:
type: object
properties:
source:
type: string
description: (Required) The resource ID of the Secret resource.
required: [source]
host:
type: string
description: The host name used to connect to the database.
description: (Read Only) The host name used to connect to the database.
readOnly: true
port:
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.
description: (Read Only) The port number used to connect to the database.
readOnly: true
password:
database:
type: string
description: The password for connecting to the database.
description: (Read Only) The name of the database.
readOnly: true
required: [environment]
required: [environment, connections]
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
@description('Information about what resource is calling this Recipe. Generated by Radius.')
extension kubernetes with {
namespace: namespace
kubeConfig: ''
} as kubernetes

//////////////////////////////////////////
// Common Radius variables
//////////////////////////////////////////

param context object

@description('Name of the PostgreSQL database. Defaults to the name of the Radius resource.')
param database string = context.resource.name
var resourceName = context.resource.name
var namespace = context.runtime.kubernetes.namespace
var resourceProperties = context.resource.properties ?? {}

// Extract last segment from environment path for labels
var environmentId = resourceProperties.?environment ?? ''
var environmentParts = environmentId != '' ? split(environmentId, '/') : []
var environmentLabel = length(environmentParts) > 0
? environmentParts[length(environmentParts) - 1]
: ''

@description('PostgreSQL username')
param user string = 'postgres'
// Extract resource group name
// Index 4 is the resource group name
var resourceGroupName = split(context.resource.id, '/')[4]

@description('PostgreSQL password')
@secure()
#disable-next-line secure-parameter-default
param password string = uniqueString(context.resource.id)
// Application name (safe)
var applicationName = context.application != null ? context.application.name : ''

// Common labels
var labels = {
'radapp.io/resource': resourceName
'radapp.io/application': applicationName
'radapp.io/environment': environmentLabel
'radapp.io/resource-type': replace(context.resource.type, '/', '-')
'radapp.io/resource-group': resourceGroupName
}

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

@description('Memory limits for the PostgreSQL container')
var memory ={
Expand All @@ -28,63 +53,75 @@ var memory ={
}
}

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

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

// Based on https://hub.docker.com/_/postgres/
// Get the secret reference. Should be only a single connected resource.
var radiusConnectionsMap = context.resource.?connections ?? {}
var radiusConnectionList = items(radiusConnectionsMap)
var radiusFirstConnection = length(radiusConnectionList) > 0 ? radiusConnectionList[0].value : null
Copy link
Member

Choose a reason for hiding this comment

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

Why would the secret be the only connection? Or if there are multiple, why would it be the first connection? Maybe a comment here would help explain.

Copy link
Contributor Author

@zachcasper zachcasper Dec 2, 2025

Choose a reason for hiding this comment

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

This is the challenge with modeling the secret as a connection. @Reshrahim highlighted an inconsistency in how we are handling secrets.

@lakshmimsft's approach was to use connections from a database resource to a secret. We got some things for free, but I'll have to let her comment on what exactly the benefits of using connections was.

However, in the Containers Resource Type, @sk593 used the secretName and key which is more Kubernetes-like. This appears more straightforward since there can only be one secretName and key. Unlike connections which could be anything.

We should be consistent.

var radiusSecretName = radiusFirstConnection != null ? (radiusFirstConnection.?name ?? null) : null


//////////////////////////////////////////
// PostgreSQL variables
//////////////////////////////////////////

resource postgresql 'apps/Deployment@v1' = {
metadata: {
name: uniqueName
name: resourceName
namespace: namespace
labels: labels
}
spec: {
selector: {
matchLabels: {
app: 'postgresql'
resource: context.resource.name
app: 'postgres'
}
}
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
labels: union(labels, {
app: 'postgres'
})
}
}
spec: {
containers: [
{
// This container is the running postgresql instance.
name: 'postgres'
image: 'postgres:${tag}'
ports: [
{
containerPort: port
}
]
image: 'postgres:16-alpine'
resources: {
requests: {
memory: memory[context.resource.properties.size].memoryRequest
}
}
ports: [
{
containerPort: port
}
]
env: [
{
name: 'POSTGRES_USER'
value: user
valueFrom: {
secretKeyRef: {
name: radiusSecretName
key: 'username'
}
}
}
{
name: 'POSTGRES_PASSWORD'
value: password
valueFrom: {
secretKeyRef: {
name: radiusSecretName
key: 'password'
}
}
}
{
name: 'POSTGRES_DB'
value: database
value: 'postgres_db'
}
]
}
Expand All @@ -96,16 +133,14 @@ resource postgresql 'apps/Deployment@v1' = {

resource svc 'core/Service@v1' = {
metadata: {
name: uniqueName
labels: {
name: uniqueName
}
name: resourceName
namespace: namespace
labels: labels
}
spec: {
type: 'ClusterIP'
selector: {
app: 'postgresql'
resource: context.resource.name
app: 'postgres'
}
ports: [
{
Expand All @@ -115,6 +150,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 @@ -123,11 +162,6 @@ output result object = {
values: {
host: '${svc.metadata.name}.${svc.metadata.namespace}.svc.cluster.local'
port: port
database: database
username: user
database: 'postgres_db'
}
secrets: {
#disable-next-line outputs-should-not-contain-secrets
password: password
}
}
Loading
Loading