Skip to content

Commit b7c9e55

Browse files
anshulsaoclaude
andauthored
feat(datastore/postgres): logical flavour (#361)
* feat(datastore/postgres): add reference flavour — passthrough of an existing postgres datastore's outputs (no resources); enables per-env DB consolidation references Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKZUrLtUbJS5GuEyRA8Vd5 * refactor(datastore/postgres): rename reference flavour to logical 'reference' clashed with Facets' cross-blueprint reference concept. 'logical' better names the use case: a logical database hosted on an existing/shared physical postgres instance. Renames the flavour dir, flavor field, titles, descriptions, and catalog/README/index.html/project-type entries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKZUrLtUbJS5GuEyRA8Vd5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5ce366e commit b7c9e55

10 files changed

Lines changed: 260 additions & 5 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ raptor import project-type --managed facets/aws --name "My Platform"
4545
`Cloud Account (aws_provider)` `Network/VPC (aws_network)` `EKS Standard (eks_standard)` `EKS Automode (eks_automode)` `Node Pool/Karpenter (karpenter)` `Node Pool/Automode (eks_automode)` `Karpenter (default)` `AWS ALB Controller (standard)` `EFS CSI Driver (standard)` `AWS EFS (standard)` `StorageClass/EBS (aws_ebs)` `StorageClass/EFS (aws_efs)` `Service (aws)` `AWS IAM Policy (standard)` `AWS IAM Role (standard)`
4646

4747
**Managed Datastores**
48-
`PostgreSQL/RDS (aws-rds)` `PostgreSQL/Aurora (aws-aurora)` `MySQL/RDS (aws-rds)` `MySQL/Aurora (aws-aurora)` `MongoDB/DocumentDB (aws-documentdb)` `Redis/ElastiCache (aws-elasticache)` `Kafka/MSK (aws-msk)`
48+
`PostgreSQL/RDS (aws-rds)` `PostgreSQL/Aurora (aws-aurora)` `PostgreSQL/Logical (logical)` `MySQL/RDS (aws-rds)` `MySQL/Aurora (aws-aurora)` `MongoDB/DocumentDB (aws-documentdb)` `Redis/ElastiCache (aws-elasticache)` `Kafka/MSK (aws-msk)`
4949

5050
**Self-hosted via KubeBlocks**
5151
`PostgreSQL` `MySQL` `MongoDB` `Redis`
@@ -91,7 +91,7 @@ raptor import project-type --managed facets/gcp --name "My Platform"
9191
`Cloud Account (gcp_provider)` `Network/VPC (gcp_network)` `GKE (gke)` `Node Pool (gcp)` `Node Fleet (gcp_node_fleet)` `Service (gcp)` `Workload Identity (gcp)` `Pub/Sub (gcp)` `GCP Secret Manager (gcp)`
9292

9393
**Managed Datastores**
94-
`PostgreSQL/Cloud SQL (gcp-cloudsql)` `MySQL/Cloud SQL (gcp-cloudsql)` `Redis/Memorystore (gcp-memorystore)`
94+
`PostgreSQL/Cloud SQL (gcp-cloudsql)` `PostgreSQL/Logical (logical)` `MySQL/Cloud SQL (gcp-cloudsql)` `Redis/Memorystore (gcp-memorystore)`
9595

9696
**Self-hosted via KubeBlocks**
9797
`PostgreSQL` `MySQL` `MongoDB` `Redis`
@@ -137,7 +137,7 @@ raptor import project-type --managed facets/azure --name "My Platform"
137137
`Cloud Account (azure_provider)` `Network/VNet (azure_network)` `AKS (aks)` `Node Pool (azure)` `Service (azure)` `Workload Identity (azure)`
138138

139139
**Managed Datastores**
140-
`PostgreSQL/Flexible Server (azure-flexible-server)` `MySQL/Flexible Server (azure-flexible-server)` `MongoDB/Cosmos DB (cosmosdb)` `Redis/Azure Cache (azure_cache_custom)`
140+
`PostgreSQL/Flexible Server (azure-flexible-server)` `PostgreSQL/Logical (logical)` `MySQL/Flexible Server (azure-flexible-server)` `MongoDB/Cosmos DB (cosmosdb)` `Redis/Azure Cache (azure_cache_custom)`
141141

142142
**Self-hosted via KubeBlocks**
143143
`PostgreSQL` `MySQL` `MongoDB` `Redis`

index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,7 @@ <h3 class="template-card-name">NGINX Gateway Fabric Ingress (AWS) <span class="t
11331133
"AWS IAM Policy (standard)", "AWS IAM Role (standard)"
11341134
],
11351135
"Managed Datastores": [
1136-
"PostgreSQL/RDS (aws-rds)", "PostgreSQL/Aurora (aws-aurora)", "MySQL/RDS (aws-rds)",
1136+
"PostgreSQL/RDS (aws-rds)", "PostgreSQL/Aurora (aws-aurora)", "PostgreSQL/Logical (logical)", "MySQL/RDS (aws-rds)",
11371137
"MySQL/Aurora (aws-aurora)", "MongoDB/DocumentDB (aws-documentdb)",
11381138
"Redis/ElastiCache (aws-elasticache)", "Kafka/MSK (aws-msk)"
11391139
],
@@ -1164,7 +1164,7 @@ <h3 class="template-card-name">NGINX Gateway Fabric Ingress (AWS) <span class="t
11641164
"Workload Identity (gcp)", "Pub/Sub (gcp)", "GCP Secret Manager (gcp)"
11651165
],
11661166
"Managed Datastores": [
1167-
"PostgreSQL/Cloud SQL (gcp-cloudsql)", "MySQL/Cloud SQL (gcp-cloudsql)",
1167+
"PostgreSQL/Cloud SQL (gcp-cloudsql)", "PostgreSQL/Logical (logical)", "MySQL/Cloud SQL (gcp-cloudsql)",
11681168
"Redis/Memorystore (gcp-memorystore)"
11691169
],
11701170
"Self-hosted via KubeBlocks": [
@@ -1194,6 +1194,7 @@ <h3 class="template-card-name">NGINX Gateway Fabric Ingress (AWS) <span class="t
11941194
],
11951195
"Managed Datastores": [
11961196
"PostgreSQL/Flexible Server (azure-flexible-server)",
1197+
"PostgreSQL/Logical (logical)",
11971198
"MySQL/Flexible Server (azure-flexible-server)",
11981199
"MongoDB/Cosmos DB (cosmosdb)", "Redis/Azure Cache (azure_cache_custom)"
11991200
],
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# PostgreSQL Logical Module
2+
3+
![Version](https://img.shields.io/badge/version-1.0-blue) ![Cloud](https://img.shields.io/badge/cloud-AWS%20%7C%20GCP%20%7C%20Azure-lightgrey)
4+
5+
## Overview
6+
7+
This flavour creates **no cloud resources**. It models a **logical database
8+
hosted on an existing/shared PostgreSQL instance** (selected via `spec.source`):
9+
a pure passthrough that re-exposes that instance's connection outputs under the
10+
`@facets/postgres` contract.
11+
12+
The primary use case is **staging DB consolidation**: a logical database
13+
co-located on a shared physical instance. A blueprint can declare one logical
14+
`postgres` resource per service while each one points at the same shared
15+
instance — optionally re-targeting the connection string at a different logical
16+
database name.
17+
18+
## Resources Created
19+
20+
None. `main.tf` declares no resources and no providers. The module is a
21+
stateless transform over `var.instance.spec.source`.
22+
23+
## Spec
24+
25+
| Field | Type | Required | Notes |
26+
|-------|------|----------|-------|
27+
| `source` | `@facets/postgres` (via `x-ui-output-type`) | yes | The existing/shared postgres instance hosting this logical database. Resolves to the selected resource's full outputs (interfaces + attributes). Selectable per environment, so it is override-able. |
28+
| `database_name` | string | no (override-only) | Logical database to target on the shared host. When set, the `connection_string` is rebuilt to point at this database; otherwise the source connection string passes through unchanged. |
29+
30+
## Outputs
31+
32+
Emits `@facets/postgres` with `reader` and `writer` interfaces
33+
(`host`, `port`, `username`, `password`, `connection_string`, `secrets`)
34+
carried over from the source. Any source `attributes` (e.g.
35+
`db_instance_identifier`, `arn`) are passed through. This matches the output
36+
contract of `aws-rds` and `aws-aurora`, so consumers cannot tell the
37+
difference between a dedicated instance and a logical database.
38+
39+
## Connection string re-targeting
40+
41+
When `database_name` is set, both reader and writer `connection_string`
42+
values are rebuilt as:
43+
44+
```
45+
postgres://<source-username>:<source-password>@<source-host>:<source-port>/<database_name>
46+
```
47+
48+
Credentials, host and port are inherited from the source; only the database
49+
path segment is replaced. When `database_name` is unset, the source's own
50+
`connection_string` is passed through verbatim.
51+
52+
## Use case: staging DB consolidation
53+
54+
In staging it is common to run a single shared physical PostgreSQL instance
55+
and give each service its own logical database. Model this by:
56+
57+
1. Deploying one real postgres datastore (e.g. `aws-rds`) — the shared instance.
58+
2. Adding a `postgres/logical` resource per service, with `spec.source`
59+
pointing (per environment, via `--flavor`/override) at the shared instance
60+
and `database_name` set to the service's logical database.
61+
62+
This keeps blueprints identical across environments while consolidating
63+
physical infrastructure in non-production.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
intent: postgres
2+
flavor: logical
3+
version: '1.0'
4+
clouds:
5+
- aws
6+
- gcp
7+
- azure
8+
description: Logical — a logical database hosted on an existing/shared postgres instance;
9+
re-exposes its connection outputs (no resources created)
10+
intentDetails:
11+
type: Datastores
12+
description: A logical database hosted on an existing/shared PostgreSQL instance (no resources created)
13+
displayName: PostgreSQL
14+
iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/postgres.svg
15+
spec:
16+
title: PostgreSQL Logical
17+
description: A logical database hosted on an existing/shared postgres instance.
18+
Re-exposes the shared instance's connection outputs and creates no cloud
19+
resources. Use for staging DB consolidation, optionally overriding the
20+
target database name in the connection string.
21+
type: object
22+
x-ui-order:
23+
- source
24+
- database_name
25+
properties:
26+
source:
27+
type: object
28+
title: Source PostgreSQL Datastore
29+
description: The existing/shared postgres instance hosting this logical
30+
database, whose connection outputs are re-exposed. Resolves to the
31+
selected resource's full outputs (interfaces and attributes).
32+
x-ui-output-type: '@facets/postgres'
33+
database_name:
34+
type: string
35+
title: Logical Database Name
36+
description: Optional logical database name to target on the shared host. When
37+
set, the connection string is rebuilt to point at this database instead of
38+
the source's default database. Leave empty to pass the source connection
39+
through unchanged.
40+
x-ui-overrides-only: true
41+
pattern: ^[a-zA-Z][a-zA-Z0-9_]*$
42+
minLength: 1
43+
maxLength: 63
44+
required:
45+
- source
46+
outputs:
47+
default:
48+
type: '@facets/postgres'
49+
title: PostgreSQL Logical
50+
iac:
51+
validated_files:
52+
- main.tf
53+
- variables.tf
54+
- outputs.tf
55+
sample:
56+
kind: postgres
57+
flavor: logical
58+
version: '1.0'
59+
disabled: true
60+
spec:
61+
source: {}
62+
database_name: app_logical
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# PostgreSQL Logical flavour
2+
#
3+
# This flavour creates NO cloud resources and declares NO providers.
4+
# It is a pure passthrough: it re-exposes the connection outputs of an
5+
# existing postgres datastore (selected via spec.source), optionally
6+
# re-targeting the connection string at a different logical database.
7+
#
8+
# Use case: staging DB consolidation, where a logical database points at a
9+
# shared physical instance. The source datastore is selected per-environment
10+
# (override-able), so the same blueprint resource can fan out to different
11+
# shared instances across environments.
12+
13+
terraform {
14+
required_version = ">= 1.0"
15+
}
16+
17+
# Intentionally no resources.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
locals {
2+
# Full outputs of the referenced postgres datastore.
3+
source_interfaces = var.instance.spec.source.interfaces
4+
source_reader = local.source_interfaces.reader
5+
source_writer = local.source_interfaces.writer
6+
7+
# Optional logical database to re-target the connection string at.
8+
override_db = lookup(var.instance.spec, "database_name", null)
9+
10+
# Rebuild the connection string against the overridden logical database when
11+
# one is supplied, otherwise pass the source connection string through.
12+
reader_connection_string = local.override_db != null ? format(
13+
"postgres://%s:%s@%s:%s/%s",
14+
local.source_reader.username,
15+
local.source_reader.password,
16+
local.source_reader.host,
17+
local.source_reader.port,
18+
local.override_db,
19+
) : local.source_reader.connection_string
20+
21+
writer_connection_string = local.override_db != null ? format(
22+
"postgres://%s:%s@%s:%s/%s",
23+
local.source_writer.username,
24+
local.source_writer.password,
25+
local.source_writer.host,
26+
local.source_writer.port,
27+
local.override_db,
28+
) : local.source_writer.connection_string
29+
30+
# Passthrough of any source attributes (e.g. db_instance_identifier, arn)
31+
# when the source datastore emits them.
32+
output_attributes = lookup(var.instance.spec.source, "attributes", {})
33+
34+
output_interfaces = {
35+
reader = {
36+
host = local.source_reader.host
37+
port = local.source_reader.port
38+
username = local.source_reader.username
39+
password = local.source_reader.password
40+
connection_string = local.reader_connection_string
41+
secrets = ["password", "connection_string"]
42+
}
43+
writer = {
44+
host = local.source_writer.host
45+
port = local.source_writer.port
46+
username = local.source_writer.username
47+
password = local.source_writer.password
48+
connection_string = local.writer_connection_string
49+
secrets = ["password", "connection_string"]
50+
}
51+
}
52+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
variable "instance" {
2+
description = "A logical database hosted on an existing/shared PostgreSQL instance — re-exposes its connection outputs (no resources created)"
3+
type = object({
4+
kind = string
5+
flavor = string
6+
version = string
7+
spec = object({
8+
# Resolves to the selected postgres resource's FULL outputs
9+
# (interfaces + attributes) injected by the platform via x-ui-output-type.
10+
source = object({
11+
attributes = optional(any, {})
12+
interfaces = object({
13+
reader = object({
14+
host = string
15+
port = string
16+
username = string
17+
password = string
18+
connection_string = string
19+
secrets = optional(list(string), [])
20+
})
21+
writer = object({
22+
host = string
23+
port = string
24+
username = string
25+
password = string
26+
connection_string = string
27+
secrets = optional(list(string), [])
28+
})
29+
})
30+
})
31+
# Optional logical DB name to target on the shared host (override-only).
32+
database_name = optional(string)
33+
})
34+
})
35+
}
36+
37+
variable "instance_name" {
38+
description = "The architectural name for the resource as added in the Facets blueprint designer."
39+
type = string
40+
}
41+
42+
variable "environment" {
43+
description = "An object containing details about the environment."
44+
type = object({
45+
name = string
46+
unique_name = string
47+
cloud_tags = map(string)
48+
})
49+
}
50+
51+
variable "inputs" {
52+
description = "A map of inputs requested by the module developer. This logical flavour provisions nothing and requires no inputs."
53+
type = object({})
54+
}

project-type/aws/project-type.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ modules:
6262
flavor: aws-aurora
6363
- intent: postgres
6464
flavor: aws-rds
65+
- intent: postgres
66+
flavor: logical
6567
- intent: redis
6668
flavor: aws-elasticache
6769
- intent: keda

project-type/azure/project-type.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ modules:
3434
flavor: azure-flexible-server
3535
- intent: postgres
3636
flavor: azure-flexible-server
37+
- intent: postgres
38+
flavor: logical
3739
- intent: redis
3840
flavor: azure_cache_custom
3941

project-type/gcp/project-type.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ modules:
5353
flavor: gcp-cloudsql
5454
- intent: postgres
5555
flavor: gcp-cloudsql
56+
- intent: postgres
57+
flavor: logical
5658
- intent: redis
5759
flavor: gcp-memorystore
5860
# - intent: kafka_topic

0 commit comments

Comments
 (0)