diff --git a/Data/postgreSqlDatabases/README.md b/Data/postgreSqlDatabases/README.md index e5ebc7dc..0502a70a 100644 --- a/Data/postgreSqlDatabases/README.md +++ b/Data/postgreSqlDatabases/README.md @@ -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 diff --git a/Data/postgreSqlDatabases/postgreSqlDatabases.yaml b/Data/postgreSqlDatabases/postgreSqlDatabases.yaml index 56e7d518..158e79f9 100644 --- a/Data/postgreSqlDatabases/postgreSqlDatabases.yaml +++ b/Data/postgreSqlDatabases/postgreSqlDatabases.yaml @@ -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 + } + } 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: { @@ -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__`. 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__`. 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: @@ -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] \ No newline at end of file + required: [environment, connections] \ No newline at end of file diff --git a/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep b/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep index 0d6bbb98..55d84933 100644 --- a/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep +++ b/Data/postgreSqlDatabases/recipes/kubernetes/bicep/kubernetes-postgresql.bicep @@ -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 ={ @@ -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 +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' } ] } @@ -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: [ { @@ -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}' @@ -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 - } } diff --git a/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf b/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf index 91dd2590..b7b801d9 100644 --- a/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf +++ b/Data/postgreSqlDatabases/recipes/kubernetes/terraform/main.tf @@ -7,11 +7,47 @@ terraform { } } +# ======================================== +# Common Radius variables +# ======================================== + variable "context" { - description = "This variable contains Radius Recipe context." + description = "The Radius Recipe context variable. See https://docs.radapp.io/reference/context-schema/." type = any } +locals { + resource_name = var.context.resource.name + namespace = var.context.runtime.kubernetes.namespace + + # Extract resource properties + resource_properties = try(var.context.resource.properties, {}) + + # Extract last segment from environment path for labels + environment_id = try(local.resource_properties.environment, "") + environment_parts = local.environment_id != "" ? split("/", local.environment_id) : [] + environment_label = length(local.environment_parts) > 0 ? local.environment_parts[length(local.environment_parts) - 1] : "" + + # Extract resource group name + resource_group_name = split("/", var.context.resource.id)[4] + + # Application name + application_name = var.context.application != null ? var.context.application.name : "" + + # Build labels + labels = { + "radapp.io/resource" = local.resource_name + "radapp.io/application" = local.application_name + "radapp.io/environment" = local.environment_label + "radapp.io/resource-type" = replace(var.context.resource.type, "/", "-") + "radapp.io/resource-group" = local.resource_group_name + } +} + +# ======================================== +# PostgreSQL variables +# ======================================== + variable "memory" { description = "Memory limits for the PostgreSQL container" type = map(object({ @@ -31,20 +67,25 @@ variable "memory" { } locals { - uniqueName = var.context.resource.name - port = 5432 - namespace = var.context.runtime.kubernetes.namespace -} + port = 5432 + + # Get the secret reference. Should be only a single connected resource. + radius_connections_map = try(var.context.resource.connections, {}) + radius_connection_list = values(local.radius_connections_map) + radius_first_connection = try(local.radius_connection_list[0], null) + radius_secret_name = local.radius_first_connection != null ? lookup(local.radius_first_connection, "name", null) : null -resource "random_password" "password" { - length = 16 - special = false } +# ======================================== +# PostgreSQL resources +# ======================================== + resource "kubernetes_deployment" "postgresql" { metadata { - name = local.uniqueName + name = local.resource_name namespace = local.namespace + labels = local.labels } spec { @@ -56,35 +97,45 @@ resource "kubernetes_deployment" "postgresql" { template { metadata { - labels = { + labels = merge(local.labels, { app = "postgres" - } + }) } spec { container { - image = "postgres:16-alpine" name = "postgres" + image = "postgres:16-alpine" resources { requests = { memory = var.memory[var.context.resource.properties.size].memoryRequest } } - env { - name = "POSTGRES_PASSWORD" - value = random_password.password.result + port { + container_port = local.port } env { name = "POSTGRES_USER" - value = "postgres" + value_from { + secret_key_ref { + name = local.radius_secret_name + key = "username" + } + } + } + env { + name = "POSTGRES_PASSWORD" + value_from { + secret_key_ref { + name = local.radius_secret_name + key = "password" + } + } } env { name = "POSTGRES_DB" value = "postgres_db" } - port { - container_port = local.port - } } } } @@ -93,30 +144,35 @@ resource "kubernetes_deployment" "postgresql" { resource "kubernetes_service" "postgres" { metadata { - name = local.uniqueName + name = local.resource_name namespace = local.namespace + labels = local.labels } - spec { + type = "ClusterIP" selector = { app = "postgres" } - port { - port = local.port - target_port = local.port + port = local.port } } } +# ======================================== +# Output Radius result +# ======================================== + output "result" { value = { + resources = [ + "/planes/kubernetes/local/namespaces/${kubernetes_service.postgres.metadata[0].namespace}/providers/core/Service/${kubernetes_service.postgres.metadata[0].name}", + "/planes/kubernetes/local/namespaces/${kubernetes_deployment.postgresql.metadata[0].namespace}/providers/apps/Deployment/${kubernetes_deployment.postgresql.metadata[0].name}" + ] values = { host = "${kubernetes_service.postgres.metadata[0].name}.${kubernetes_service.postgres.metadata[0].namespace}.svc.cluster.local" port = local.port database = "postgres_db" - username = "postgres" - password = random_password.password.result } } } diff --git a/Data/postgreSqlDatabases/test/app.bicep b/Data/postgreSqlDatabases/test/app.bicep new file mode 100644 index 00000000..4b051084 --- /dev/null +++ b/Data/postgreSqlDatabases/test/app.bicep @@ -0,0 +1,99 @@ +// +// PostgreSQL test case +// +// Test script: +// +// rad deploy app.bicep -p password=$(openssl rand -hex 16) +// Typically use base64 instead of hex, but the todolist has a bug where is does not handle special characters in the password +// Manually verify todolist application can create and delete tasks + +extension radius +extension postgreSqlDatabases +extension secrets + +@description('The Radius environment ID') +param environment string + +@secure() +param password string + +resource app 'Applications.Core/applications@2023-10-01-preview' = { + name: 'todolist' + properties: { + environment: environment + } +} + +resource frontend 'Applications.Core/containers@2023-10-01-preview' = { + name: 'frontend' + properties: { + application: app.id + environment: environment + container: { + image: 'ghcr.io/radius-project/samples/demo:latest' + ports: { + web: { + containerPort: 3000 + } + } + // TODO: Remove after https://github.com/radius-project/radius/issues/10870 + env: { + CONNECTION_POSTGRESQL_USERNAME: { + valueFrom: { + secretRef: { + key: 'username' + source: 'todolist-credentials-default' + } + } + } + CONNECTION_POSTGRESQL_PASSWORD: { + valueFrom: { + secretRef: { + key: 'password' + source: 'todolist-credentials-default' + } + } + } + } + } + connections: { + postgresql: { + source: postgresql.id + } + credentials: { + source: credentials.id + } + } + } +} + +resource postgresql 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' = { + name: 'postgresql' + properties: { + application: app.id + environment: environment + size: 'S' + connections: { + credentials: { + source: credentials.id + } + } + } +} + +resource credentials 'Radius.Security/secrets@2025-08-01-preview' = { + name: 'credentials' + properties: { + environment: environment + application: app.id + data: { + username: { + value: 'postgres' + } + password: { + value: password + } + } + kind: 'basicAuthentication' + } +} diff --git a/Security/secrets/recipes/kubernetes/bicep/kubernetes-secrets.bicep b/Security/secrets/recipes/kubernetes/bicep/kubernetes-secrets.bicep index af92bf83..2e665676 100644 --- a/Security/secrets/recipes/kubernetes/bicep/kubernetes-secrets.bicep +++ b/Security/secrets/recipes/kubernetes/bicep/kubernetes-secrets.bicep @@ -1,10 +1,45 @@ extension kubernetes with { - namespace: context.runtime.kubernetes.namespace + namespace: namespace kubeConfig: '' } as kubernetes +////////////////////////////////////////// +// Common Radius variables +////////////////////////////////////////// + param context object +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] + : '' + +// Extract resource group name +// Index 4 is the resource group name +var resourceGroupName = split(context.resource.id, '/')[4] + +// 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 +} + +////////////////////////////////////////// +// Kubernetes Secret variables +////////////////////////////////////////// + // If secretKind is not set, set to 'generic' var secretKind = context.resource.properties.?kind ?? 'generic' var secretData = context.resource.properties.data @@ -31,24 +66,28 @@ var stringData = reduce(items(secretData), {}, (acc, item) => // Determine secret type based on kind var secretType = secretKind == 'certificate-pem' ? 'kubernetes.io/tls' : (secretKind == 'basicAuthentication' ? 'kubernetes.io/basic-auth' : 'Opaque') -var secretName = length(missingFields) > 0 ? missingFields : context.resource.name - + +////////////////////////////////////////// +// Kubernetes Secret resource +////////////////////////////////////////// + resource secret 'core/Secret@v1' = { metadata: { - name: secretName - namespace: context.runtime.kubernetes.namespace - labels: { - resource: context.resource.name - app: context.application == null ? '' : context.application.name - } + name: resourceName + namespace: namespace + labels: labels } type: secretType data: base64Data stringData: stringData } +////////////////////////////////////////// +// Output Radius result +////////////////////////////////////////// + output result object = { resources: [ - '/planes/kubernetes/local/namespaces/${context.runtime.kubernetes.namespace}/providers/core/Secret/${secretName}' + '/planes/kubernetes/local/namespaces/${context.runtime.kubernetes.namespace}/providers/core/Secret/${resourceName}' ] } diff --git a/Security/secrets/recipes/kubernetes/terraform/main.tf b/Security/secrets/recipes/kubernetes/terraform/main.tf index 3c2fcc49..96739cd0 100644 --- a/Security/secrets/recipes/kubernetes/terraform/main.tf +++ b/Security/secrets/recipes/kubernetes/terraform/main.tf @@ -8,6 +8,46 @@ terraform { } } +# ======================================== +# Common Radius variables +# ======================================== + +variable "context" { + description = "The Radius Recipe context variable. See https://docs.radapp.io/reference/context-schema/." + type = any +} + +locals { + resource_name = var.context.resource.name + namespace = var.context.runtime.kubernetes.namespace + resource_properties = try(var.context.resource.properties, {}) + + # Extract last segment from environment path for labels + environment_id = try(local.resource_properties.environment, "") + environment_parts = local.environment_id != "" ? split("/", local.environment_id) : [] + environment_label = length(local.environment_parts) > 0 ? local.environment_parts[length(local.environment_parts) - 1] : "" + + # Extract resource group name + # Index 4 is the resource group name + resource_group_name = split("/", var.context.resource.id)[4] + + # Application name + application_name = var.context.application != null ? var.context.application.name : "" + + # Common labels + labels = { + "radapp.io/resource" = local.resource_name + "radapp.io/application" = local.application_name + "radapp.io/environment" = local.environment_label + "radapp.io/resource-type" = replace(var.context.resource.type, "/", "-") + "radapp.io/resource-group" = local.resource_group_name + } +} + +# ======================================== +# Kubernetes Secret variables +# ======================================== + # Local values for processing secret data locals { secret_data = var.context.resource.properties.data @@ -21,7 +61,7 @@ locals { } string_data = { - for k, v in local.secret_data : k => base64encode(v.value) + for k, v in local.secret_data : k => v.value if try(v.encoding, "") != "base64" } @@ -31,9 +71,12 @@ locals { local.secret_kind == "basicAuthentication" ? "kubernetes.io/basic-auth" : "Opaque" ) - } +# ======================================== +# Kubernetes Secret resource +# ======================================== + resource "kubernetes_secret" "secret" { # Validation preconditions - these will stop deployment if they fail lifecycle { @@ -74,16 +117,24 @@ resource "kubernetes_secret" "secret" { } metadata { - name = local.secret_name - namespace = var.context.runtime.kubernetes.namespace - - labels = { - resource = var.context.resource.name - app = var.context.application != null ? var.context.application.name : "" - } + name = local.resource_name + namespace = local.namespace + labels = local.labels } type = local.secret_type data = length(local.string_data) > 0 ? local.string_data : {} binary_data = length(local.base64_data) > 0 ? local.base64_data : {} } + +# ======================================== +# Output Radius result +# ======================================== + +output "result" { + value = { + resources = [ + "/planes/kubernetes/local/namespaces/${kubernetes_secret.secret.metadata[0].namespace}/providers/core/Secret/${kubernetes_secret.secret.metadata[0].name}" + ] + } +} diff --git a/Security/secrets/recipes/kubernetes/terraform/var.tf b/Security/secrets/recipes/kubernetes/terraform/var.tf deleted file mode 100644 index 8c4b0856..00000000 --- a/Security/secrets/recipes/kubernetes/terraform/var.tf +++ /dev/null @@ -1,5 +0,0 @@ - -variable "context" { - description = "This variable contains Radius recipe context." - type = any -} diff --git a/Security/secrets/secrets.yaml b/Security/secrets/secrets.yaml index d7f5f240..4aaba34f 100644 --- a/Security/secrets/secrets.yaml +++ b/Security/secrets/secrets.yaml @@ -4,74 +4,43 @@ types: description: | The Radius.Security/secrets Resource Type stores sensitive data such as tokens, passwords, keys, and certificates. To create a new Secret, start by adding parameter to your application definition decorated with `@secure()`. Then add a `secrets` resource. Never include secret values in an application definition. - @secure() - param myTokenValue string + ``` + extension radius + extension secrets - resource mySecret 'Radius.Security/secrets@2025-08-01-preview' = { - name: 'mySecret' - properties: { - environment: environment - application: myApplication.id - data: { - myToken: { - value: myTokenValue - encoding: 'base64' //optional - } - } - } - } + @description('The Radius environment ID. Typically set by the Radius CLI.') + param environment - When `encoding` is not set, the value is assumed to be a string. For non-string secret data, set the `encoding` to `base64`. + @secure() + param password string - To use the new Secret with a Container, either, include the Secret in the `env` property or mount the Secret using the `volumes` property. - - 1. To use the Secret as an environment variable: - - resource myContainer 'Radius.Compute/containers@2025-08-01-preview' = { - name: 'myContainer' - properties: { - ... - container: { - ... - env: - MY_SECRET_VALUE: { - valueFrom: { - secretRef: { - source: mySecret.id - key: 'myToken' - } - } - } - } - } - } + resource myApplication 'Radius.Core/applications@2025-08-01-preview' = { + name: 'myApplication' + properties: { + environment: environment } + } - 2. To mount the secret as a volume: - - resource myContainer 'Radius.Compute/containers@2025-08-01-preview' = { - name: 'myContainer' - properties: { - ... - container: { - ... - volumes: - mySecretVolume: { - kind: 'secret' - mountPath: '/secrets' - source: mySecret.id - } - } + resource credentials 'Radius.Security/secrets@2025-08-01-preview' = { + name: 'credentials' + properties: { + environment: environment + application: myApplication.id + data: { + username: { + value: admin + } + password: { + value: password } } } + } + ``` - Secrets may already existing in the Radius Resource Group. These can be referenced by name using the `existing` Bicep keyword. For example: - - resource existingSecret 'Radius.Security/secrets@2025-08-01-preview' existing = { - name: 'existingSecret' - } + 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 equivilent. For example, `rad deploy app.bicep -p password=$(openssl rand -base64 16)`. + For details on how to use the secret with another resource such as a container or database, see the documentation for those resource types. apiVersions: "2025-08-01-preview": schema: diff --git a/Security/secrets/test/app.bicep b/Security/secrets/test/app.bicep index 0f18c367..c9b42e30 100644 --- a/Security/secrets/test/app.bicep +++ b/Security/secrets/test/app.bicep @@ -3,37 +3,40 @@ extension secrets param environment string -resource app 'Applications.Core/applications@2023-10-01-preview' = { +@secure() +param password string + +// Temporarily required https://github.com/radius-project/resource-types-contrib/issues/52 +resource testapp 'Applications.Core/applications@2023-10-01-preview' = { name: 'testapp' - location: 'global' properties: { environment: environment - extensions: [ - { - kind: 'kubernetesNamespace' - namespace: 'testapp' - } - ] } } -resource secret 'Radius.Security/secrets@2025-08-01-preview' = { - name: 'app-secrets-${uniqueString(deployment().name)}' +// +// Basic test case +// +// Test script: +// +// environment=$(rad env show -o json | jq -r '.name') +// password=$(openssl rand -base64 16) +// rad deploy app.bicep -p password=$password +// [[ "$(kubectl get secret testsecret1 -n $environment-testapp -o jsonpath="{.data.username}" | base64 --decode)" == "admin" ]] && echo "Username matches" || { echo "Username mismatch"; exit 1; } +// [[ "$(kubectl get secret testsecret1 -n $environment-testapp -o jsonpath="{.data.password}" | base64 --decode)" == "$password" ]] && echo "Password matches" || { echo "Password mismatch"; exit 1; } +// +resource testsecret1 'Radius.Security/secrets@2025-08-01-preview' = { + name: 'testsecret1' properties: { environment: environment - application: app.id + application: testapp.id data: { username: { value: 'admin' } password: { - value: 'c2VjcmV0cGFzc3dvcmQ=' - encoding: 'base64' - } - apikey: { - value: 'abc123xyz' + value: password } } } } -