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
75 changes: 75 additions & 0 deletions compositions/postgresql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# PostgresDatabase Crossplane Composition

Provides a `PostgresDatabase` claim interface for application teams to self-service managed RDS PostgreSQL instances. The composition handles the underlying RDS instance, DBSubnetGroup, and connection secret.

## Claim Example

```yaml
apiVersion: platform.tcs.io/v1alpha1
kind: PostgresDatabase
metadata:
name: my-app-db
namespace: my-app
spec:
parameters:
storageGB: 20
instanceClass: db.t3.medium
multiAZ: false
backupRetentionDays: 7
engineVersion: "15.4"
deletionPolicy: Orphan
writeConnectionSecretToRef:
name: my-app-db-conn
```

## Connection Secret

The composition writes a connection secret with the following keys:

| Key | Description |
|-----|-------------|
| `host` | RDS endpoint hostname |
| `port` | RDS port (usually `5432`) |
| `username` | Master username |
| `password` | Master password |
| `dbname` | Initial database name |
| `url` | Full `postgresql://user:pass@host:port/dbname` connection string |

The secret is written to the same namespace as the claim. Applications can mount it directly as environment variables or a volume.

## Automatic multiAZ in Production

The composition includes a namespace-label patch: if the claim's namespace has the label `platform.tcs.io/environment=production`, `multiAZ` is forced to `true` regardless of what the claim specifies. This prevents development teams from accidentally provisioning single-AZ databases in production environments.

Comment on lines +40 to +43
Label your production namespaces:

```yaml
apiVersion: v1
kind: Namespace
metadata:
name: my-production-app
labels:
platform.tcs.io/environment: production
```

## Deletion Policy

The `deletionPolicy` parameter defaults to `Orphan`. When set to `Orphan`, deleting the `PostgresDatabase` claim removes the Crossplane-managed objects but leaves the underlying RDS instance running in AWS. This is the safe default for production databases.

**Do not change the default to `Delete` in production.** If you delete the claim, the RDS instance and all its data are permanently destroyed.
Comment on lines +55 to +59

## Engine Version Upgrades

If you update `engineVersion` in the claim (e.g. from `14.9` to `15.4`), Crossplane will attempt to update the RDS instance in place. AWS supports in-place minor version upgrades, but major version upgrades require a snapshot, a new instance, and a manual data migration. To upgrade a major version safely:

1. Create a new `PostgresDatabase` claim with the new engine version.
2. Migrate your data to the new instance (pg_dump / pg_restore or DMS).
3. Cut over your application connection strings.
4. Delete the old claim (with `deletionPolicy: Orphan` to protect the data).
5. After confirming the new instance is healthy, manually delete the orphaned RDS instance.

Never set `engineVersion` to a major version jump and apply it directly to a production claim.

## Subnet Group Naming

DBSubnetGroup names are derived from the claim namespace and name (`tcs-<namespace>-<name>-subnet-group`). This prevents name collisions when multiple `PostgresDatabase` claims exist in the same namespace.
143 changes: 143 additions & 0 deletions compositions/postgresql/composition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xpostgresdatabases.platform.tcs.io
labels:
provider: aws
service: rds
engine: postgresql
spec:
compositeTypeRef:
apiVersion: platform.tcs.io/v1alpha1
kind: XPostgresDatabase

writeConnectionSecretsToNamespace: crossplane-system

resources:
# ------------------------------------------------------------------
# DBSubnetGroup
# Naming includes claim namespace and name to prevent collisions when
# multiple claims exist in the same namespace.
# ------------------------------------------------------------------
- name: dbsubnetgroup
base:
apiVersion: rds.aws.crossplane.io/v1alpha1
kind: DBSubnetGroup
spec:
Comment on lines +22 to +26
forProvider:
region: us-east-1
description: "Managed by Crossplane - TCS PostgresDatabase"
subnetIds: [] # Patched in via environment config or EnvironmentConfig
providerConfigRef:
name: aws-provider
patches:
- type: FromCompositeFieldPath
Comment on lines +30 to +34
fromFieldPath: metadata.namespace
toFieldPath: spec.forProvider.groupName
transforms:
- type: string
string:
fmt: "%s"
- type: CombineFromComposite
combine:
variables:
- fromFieldPath: metadata.namespace
- fromFieldPath: metadata.name
strategy: string
string:
fmt: "tcs-%s-%s-subnet-group"
toFieldPath: spec.forProvider.groupName
Comment on lines +34 to +49

# ------------------------------------------------------------------
# RDSInstance
# ------------------------------------------------------------------
- name: rdsinstance
base:
apiVersion: rds.aws.crossplane.io/v1alpha1
kind: RDSInstance
spec:
forProvider:
region: us-east-1
dbInstanceClass: db.t3.medium
engine: postgres
engineVersion: "15.4"
allocatedStorage: 20
multiAZ: false
backupRetentionPeriod: 7
storageEncrypted: true
storageType: gp3
publiclyAccessible: false
deletionProtection: false
autoMinorVersionUpgrade: true
tags:
- key: ManagedBy
value: crossplane
- key: Service
value: postgresql
writeConnectionSecretToRef:
namespace: crossplane-system
providerConfigRef:
name: aws-provider
patches:
# Claim parameters -> RDS fields
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.instanceClass
toFieldPath: spec.forProvider.dbInstanceClass
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.storageGB
toFieldPath: spec.forProvider.allocatedStorage
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.multiAZ
toFieldPath: spec.forProvider.multiAZ
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.backupRetentionDays
toFieldPath: spec.forProvider.backupRetentionPeriod
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.engineVersion
toFieldPath: spec.forProvider.engineVersion
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.deletionPolicy
toFieldPath: spec.forProvider.deletionProtection
transforms:
- type: map
map:
Orphan: "true"
Delete: "false"
Comment on lines +100 to +105

# Connection secret name derived from claim name
- type: CombineFromComposite
combine:
variables:
- fromFieldPath: metadata.namespace
- fromFieldPath: metadata.name
strategy: string
string:
fmt: "tcs-%s-%s-conn"
toFieldPath: spec.writeConnectionSecretToRef.name

# Production namespace override: force multiAZ=true
# Requires the claim namespace to have label:
# platform.tcs.io/environment=production
- type: FromEnvironmentFieldPath
fromFieldPath: platform.tcs.io/environment
toFieldPath: spec.forProvider.multiAZ
transforms:
- type: map
map:
production: "true"
policy:
fromFieldPath: Optional
Comment on lines +118 to +129

connectionDetails:
- name: host
fromConnectionSecretKey: endpoint
- name: port
fromConnectionSecretKey: port
- name: username
fromConnectionSecretKey: username
- name: password
fromConnectionSecretKey: password
- name: dbname
fromConnectionSecretKey: dbname
- name: url
fromConnectionSecretKey: url
71 changes: 71 additions & 0 deletions compositions/postgresql/xrd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresdatabases.platform.tcs.io
spec:
group: platform.tcs.io
names:
kind: XPostgresDatabase
plural: xpostgresdatabases
claimNames:
kind: PostgresDatabase
plural: postgresdatabases
connectionSecretKeys:
- host
- port
- username
- password
- dbname
- url
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
description: Parameters for the PostgresDatabase claim.
properties:
storageGB:
type: integer
description: Allocated storage in gigabytes.
default: 20
minimum: 5
maximum: 65536
instanceClass:
type: string
description: RDS DB instance class (e.g. db.t3.medium, db.r6g.large).
default: db.t3.medium
multiAZ:
type: boolean
description: |
Enable Multi-AZ deployment for high availability. Automatically
set to true in namespaces labelled platform.tcs.io/environment=production.
Comment on lines +48 to +49
default: false
backupRetentionDays:
type: integer
description: Number of days to retain automated backups. 0 disables backups.
default: 7
minimum: 0
maximum: 35
engineVersion:
type: string
description: PostgreSQL engine version (e.g. "15.4", "14.9").
default: "15.4"
deletionPolicy:
type: string
description: |
What happens to the underlying RDS instance when the claim is deleted.
Orphan (default) leaves the instance running - recommended for production.
Delete removes the instance immediately.
default: Orphan
enum:
- Delete
- Orphan
required: []