diff --git a/Data/mongoDatabases/README.md b/Data/mongoDatabases/README.md new file mode 100644 index 00000000..dd45a492 --- /dev/null +++ b/Data/mongoDatabases/README.md @@ -0,0 +1,128 @@ +# MongoDB Kubernetes Recipe (Terraform) + +This recipe provisions a MongoDB instance on Kubernetes using Terraform. It is implemented as a **Radius.Data/mongoDatabases** resource type and can be used in Bicep or Terraform-based applications. + +--- + +## Overview + +The `mongoDatabases` Resource Type provisions a MongoDB database instance on Kubernetes with optional persistence, resource configuration, and admin credentials. +It supports creating the database service, StatefulSet, PVCs (if persistence is enabled), and secrets for credentials. + +--- + +## Recipes + +| Platform | IaC Language | Recipe Name | Stage | +|------------|-------------|---------------------|-------| +| Kubernetes | Terraform | kubernetes-mongodb/main.tf | Alpha | + +--- + +## Recipe Input Properties + +| Variable | Type | Default | Description | +|-------------------|--------|-------------|----------------------------------------------| +| `name` | string | required | MongoDB instance name | +| `version` | string | 6.0 | MongoDB version | +| `replicas` | int | 1 | Number of replicas | +| `storage_size` | string | 10Gi | PVC size | +| `storage_class` | string | standard | Kubernetes storage class | +| `username` | string | admin | Admin username | +| `password` | string | required | Admin password | +| `persistence` | bool | true | Enable persistence (creates PVC) | +| `backup_enabled` | bool | false | Enable backups | +| `backup_schedule` | string | "" | Cron schedule for backups | +| `resources` | object | {} | CPU/memory requests and limits | + +> **Note:** For CI or ephemeral testing, set `persistence=false` to avoid PVC-related delays in Kind clusters. + +--- + +## Recipe Output Properties + +| Property | Description | +|--------------------------|------------------------------------------------------| +| `values.host` | MongoDB service host (read-only) | +| `values.port` | MongoDB service port (read-only) | +| `values.username` | Admin username (read-only) | +| `secrets.password` | Admin password (sensitive, read-only) | +| `resources` | UCP resource IDs for Service and StatefulSet | + +--- + +## Recipe Description + +This Terraform recipe creates: + +1. A Kubernetes **Secret** for MongoDB credentials. +2. An optional **PersistentVolumeClaim** if `persistence=true`. +3. A **ClusterIP Service** for MongoDB. +4. A **StatefulSet** running the specified MongoDB version, replicas, and resources. +5. Optional dynamic volume mounts and PVCs. +6. Outputs exposing connection info and secrets for downstream usage. + +--- + +## Usage Instructions + +### Manual Testing + +1. Apply Terraform: + ```bash + terraform init + terraform apply \ + -var="name=mydb" \ + -var="password=MySecretPass123" \ + -auto-approve + ``` + +2. Check pod status: + ```bash + kubectl get pods + # Ensure test-mongodb-0 is READY=1/1 + ``` + +3. Connect to MongoDB: + ```bash + kubectl run mongo-client --rm -it --image=mongo -- \ + mongo "mongodb://$(kubectl get svc mydb-svc -o jsonpath='{.spec.clusterIP}'):27017" \ + -u admin -p MySecretPass123 + ``` + +4. Verify database operations. + +5. Clean up (optional): + ```bash + terraform destroy -var="name=mydb" -var="password=MySecretPass123" -auto-approve + ``` + +--- + +## CI / GitHub Actions Testing + +This recipe is automatically tested in GitHub Actions for pull requests and branch pushes. + +- A temporary Kind cluster is created. +- Terraform applies the recipe with `persistence=false` for fast ephemeral testing. +- The workflow waits for MongoDB pods to become ready. +- Terraform destroys the resources and deletes the Kind cluster after the test. + +Workflow file: `.github/workflows/test-mongodb-recipe.yml` + +Example ephemeral test run: +```bash +terraform apply -var="name=test-mongodb" -var="password=MySecretPass123" -var="persistence=false" -auto-approve +kubectl get pods +terraform destroy -var="name=test-mongodb" -var="password=MySecretPass123" -var="persistence=false" -auto-approve +``` + +> Make sure the MongoDB pod shows `READY=1/1` before connecting. + +--- + +## References + +- Radius MongoDB Resource Schema: https://docs.radapp.io/reference/resource-schema/databases/mongodb/ +- Example Kubernetes Recipe: https://github.com/radius-project/recipes/blob/main/local-dev/mongodatabases.bicep + diff --git a/Data/mongoDatabases/mongoDatabases.yaml b/Data/mongoDatabases/mongoDatabases.yaml new file mode 100644 index 00000000..a1ecc565 --- /dev/null +++ b/Data/mongoDatabases/mongoDatabases.yaml @@ -0,0 +1,102 @@ +namespace: Radius.Data +types: + mongoDatabases: + description: | + The Radius.Data/mongoDatabases Resource Type provisions a MongoDB database instance on Kubernetes using Terraform. + + Start by adding a mongoDatabases resource to your application definition Bicep file: + + resource mongo 'Radius.Data/mongoDatabases@2025-10-01-preview' = { + name: 'mongo' + properties: { + name: 'myMongoDb' + version: '6.0' + size: 'M' + credentials: { + username: 'admin' + password: 'MySecurePassword123' + } + } + } + + Then connect a container to the database: + + resource myContainer 'Radius.Compute/containers@2025-10-01-preview' = { + name: 'myContainer' + properties: { + connections: { + mongo: { + source: mongo.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 `mongo`, so the environment variables will be: + + CONNECTION_MONGO_NAME + CONNECTION_MONGO_VERSION + CONNECTION_MONGO_USERNAME + CONNECTION_MONGO_PASSWORD + CONNECTION_MONGO_HOST + CONNECTION_MONGO_PORT + + These variables expose the connection details and credentials of the MongoDB instance for use by your application. + + > ⚠️ **Security note:** The `CONNECTION_MONGO_PASSWORD` variable contains sensitive data and should be handled carefully. + Avoid logging or exposing it in application outputs. + + apiVersions: + '2025-10-03-preview': + schema: + type: object + properties: + name: + type: string + description: The name of the MongoDB database instance. + pattern: '^[a-zA-Z0-9\-]+$' + version: + type: string + description: MongoDB server version to deploy. + default: "6.0" + size: + type: string + enum: ['S', 'M', 'L'] + description: | + The deployment size profile for the MongoDB instance. + The Recipe implementation defines replicas, resource limits, and storage class for each size. + + - `S`: Small — development and testing + - `M`: Medium — staging or moderate workloads + - `L`: Large — production or high-availability + default: "S" + credentials: + type: object + description: Database credentials for the admin user. + properties: + username: + type: string + description: Admin username. + default: "admin" + password: + type: string + description: Admin password. + minLength: 8 + required: + - username + - password + host: + type: string + description: "(Read-only) The host name used to connect to the MongoDB database." + readOnly: true + port: + type: integer + description: "(Read-only) The port number used to connect to the MongoDB database." + readOnly: true + required: + - name + - version + - size + - credentials diff --git a/Data/mongoDatabases/recipes/kubernetes/terraform/main.tf b/Data/mongoDatabases/recipes/kubernetes/terraform/main.tf new file mode 100644 index 00000000..31bfbfdb --- /dev/null +++ b/Data/mongoDatabases/recipes/kubernetes/terraform/main.tf @@ -0,0 +1,182 @@ +provider "kubernetes" { + config_path = "~/.kube/config" +} + +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.0" + } + } +} + +# Secret for credentials +resource "kubernetes_secret" "mongodb_credentials" { + metadata { + name = "${var.name}-credentials" + namespace = var.context.runtime.kubernetes.namespace + } + data = { + username = base64encode(var.username) + password = base64encode(var.password) + } +} + +# Persistent Volume Claim (if persistence enabled) +resource "kubernetes_persistent_volume_claim" "mongodb" { + count = var.persistence ? 1 : 0 + + metadata { + name = "${var.name}-pvc" + namespace = var.context.runtime.kubernetes.namespace + } + + spec { + access_modes = ["ReadWriteOnce"] + resources { + requests = { + storage = var.storage_size + } + } + storage_class_name = var.storage_class + } +} + +# Service +resource "kubernetes_service" "mongodb" { + metadata { + name = "${var.name}-svc" + namespace = var.context.runtime.kubernetes.namespace + } + + spec { + selector = { + app = var.name + } + port { + port = 27017 + target_port = 27017 + } + type = "ClusterIP" + } +} + +# StatefulSet +resource "kubernetes_stateful_set" "mongodb" { + metadata { + name = var.name + namespace = var.context.runtime.kubernetes.namespace + labels = { + app = var.name + } + } + + spec { + service_name = "${var.name}-svc" + replicas = var.replicas + selector { + match_labels = { + app = var.name + } + } + + template { + metadata { + labels = { + app = var.name + } + } + + spec { + container { + name = "mongodb" + image = "mongo:${var.mongodb_version}" + + port { + container_port = 27017 + } + + env { + name = "MONGO_INITDB_ROOT_USERNAME" + value_from { + secret_key_ref { + name = kubernetes_secret.mongodb_credentials.metadata.name + key = "username" + } + } + } + + env { + name = "MONGO_INITDB_ROOT_PASSWORD" + value_from { + secret_key_ref { + name = kubernetes_secret.mongodb_credentials.metadata.name + key = "password" + } + } + } + + resources { + requests = var.resources.requests + limits = var.resources.limits + } + + dynamic "volume_mount" { + for_each = var.persistence ? [1] : [] + content { + name = "data" + mount_path = "/data/db" + } + } + } + + dynamic "volume" { + for_each = var.persistence ? [1] : [] + content { + name = "data" + + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim.mongodb[0].metadata.name + } + } + } + } + } + + # Optional: Grace period + termination_grace_period_seconds = 30 + } + + timeouts { + create = "40m" + } +} + +output "result" { + value = { + values = { + host = "${kubernetes_service.mongodb.metadata[0].name}.${kubernetes_service.mongodb.metadata[0].namespace}.svc.cluster.local" + port = kubernetes_service.mongodb.spec[0].port[0].port + username = var.username + } + secrets = { + password = var.password + } + # UCP resource IDs + resources = [ + "/planes/kubernetes/local/namespaces/${kubernetes_service.mongodb.metadata[0].namespace}/providers/core/Service/${kubernetes_service.mongodb.metadata[0].name}", + "/planes/kubernetes/local/namespaces/${kubernetes_stateful_set.mongodb.metadata[0].namespace}/providers/apps/StatefulSet/${kubernetes_stateful_set.mongodb.metadata[0].name}" + ] + } + description = <