Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
7b28818
feat: add redshift domain models
vuthehuyht Aug 22, 2026
530e734
feat: add redshift service and test
vuthehuyht Aug 22, 2026
3a2b10c
feat: add redshift query handler and routing
vuthehuyht Aug 22, 2026
29877e4
feat(redshift): implement data plane container manager and wire storage
vuthehuyht Aug 22, 2026
bc3f2b4
feat: add redshift operations models and service logic
vuthehuyht Aug 22, 2026
4e70664
fix: correct yaml indentation for redshift storage config
vuthehuyht Aug 22, 2026
1eb6b71
fix: remove invalid redshift storage properties
vuthehuyht Aug 22, 2026
06b50a2
feat: implement pg_dump and psql for redshift snapshots
vuthehuyht Aug 22, 2026
e751434
feat: add redshift core operations query actions
vuthehuyht Aug 22, 2026
586610e
test: add redshift operations integration test
vuthehuyht Aug 22, 2026
4a104d2
fix(redshift): Write snapshot SQL dumps to file storage and add Param…
vuthehuyht Aug 22, 2026
2f96a2b
docs: add redshift implementation plan, design spec, and PR description
vuthehuyht Aug 22, 2026
99fe25f
chore: remove superpowers working files
vuthehuyht Aug 22, 2026
bbdf4d0
fix(redshift): scope snapshots and cluster recovery per account, rest…
vuthehuyht Aug 22, 2026
3614b67
fix(redshift): add logging for cleanup failures in RedshiftOperations…
vuthehuyht Aug 22, 2026
7e07a5b
fix(redshift): scope container identity per account, persist snapshot…
vuthehuyht Aug 22, 2026
b3f905d
feat(redshift): implement adoptOrStart method to reuse existing conta…
vuthehuyht Aug 22, 2026
9978de5
chore: gitignore subagent-driven-development scratch workspace
vuthehuyht Aug 22, 2026
d8804f9
feat(redshift): add tagging support to RedshiftService
vuthehuyht Aug 22, 2026
1a9e719
feat(redshift): wire CreateTags/DeleteTags/DescribeTags into the quer…
vuthehuyht Aug 22, 2026
463798f
feat(redshift): store real parameter values on ClusterParameterGroup
vuthehuyht Aug 23, 2026
80bd82a
fix(redshift): use proper imports and preserve parameter metadata on …
vuthehuyht Aug 23, 2026
d633f65
feat(redshift): wire ModifyClusterParameterGroup, return real Describ…
vuthehuyht Aug 23, 2026
d1aec52
feat(redshift): add ClusterSubnetGroup model and CRUD
vuthehuyht Aug 23, 2026
07b95d3
feat(redshift): wire ClusterSubnetGroup CRUD into the query handler
vuthehuyht Aug 23, 2026
2bbdf81
feat(redshift): add ClusterSubnetGroupName/VpcSecurityGroupIds metada…
vuthehuyht Aug 23, 2026
e1f053c
feat(redshift): add RedshiftContainerManager.alterUserPassword
vuthehuyht Aug 23, 2026
0463f92
fix(redshift): prevent SQL injection in alterUserPassword via usernam…
vuthehuyht Aug 23, 2026
14d9321
feat(redshift): add RedshiftService.modifyCluster
vuthehuyht Aug 23, 2026
3eb2832
feat(redshift): add RedshiftService.rebootCluster (dump/restore based…
vuthehuyht Aug 23, 2026
5ce6f19
feat(redshift): wire ModifyCluster/RebootCluster into the query handler
vuthehuyht Aug 23, 2026
85c8102
test(redshift): add compatibility coverage for tagging, subnet groups…
vuthehuyht Aug 23, 2026
f20f03e
fix(redshift): use imports and unique names in compatibility test
vuthehuyht Aug 23, 2026
985c94f
fix(redshift): address final whole-branch review findings
vuthehuyht Aug 27, 2026
a82c9a8
Merge branch 'worktree-redshift-crud-completeness'
vuthehuyht Aug 27, 2026
7f8262d
fix(redshift): address PR review feedback and green the CI
vuthehuyht Aug 29, 2026
7b26135
Merge branch 'main' into main
vuthehuyht Aug 29, 2026
e0dec9f
fix(redshift): reject path traversal in SnapshotIdentifier
vuthehuyht Aug 29, 2026
c7b1995
fix(redshift): validate snapshot dump path before restore provisioning
vuthehuyht Aug 29, 2026
81b2df3
Merge branch 'main' of github.com:floci-io/floci
vuthehuyht Aug 30, 2026
8585533
feat(redshift): add auth proxy port range and endpoint-host config
vuthehuyht Aug 30, 2026
45dfd74
feat(redshift): add containerHost/containerPort/proxyPort to Cluster …
vuthehuyht Aug 30, 2026
486a21d
feat(redshift): add RedshiftAuthProxy reusing the RDS PostgreSQL prot…
vuthehuyht Aug 30, 2026
06111fc
feat(redshift): add RedshiftProxyManager registry with shutdown hook
vuthehuyht Aug 30, 2026
338f391
feat(redshift): front new clusters with an auth proxy and advertise it
vuthehuyht Aug 30, 2026
b438254
feat(redshift): stop/update proxy on delete/modify, start it on snaps…
vuthehuyht Aug 30, 2026
33a3802
feat(redshift): keep proxy port stable across reboot and adopt-on-sta…
vuthehuyht Aug 30, 2026
13be869
test(redshift): integration coverage for JDBC through the auth proxy
vuthehuyht Aug 30, 2026
b074ba5
test(redshift): reinstate JDBC connectivity check now that the proxy …
vuthehuyht Aug 30, 2026
5277723
docs(redshift): document the cluster auth proxy and its config
vuthehuyht Aug 30, 2026
5ff7b3b
fix(redshift): prevent minor port leak when proxy fails to start
vuthehuyht Aug 30, 2026
f0d9853
fix(redshift): rollback container on proxy failure and surface teardo…
vuthehuyht Aug 30, 2026
235c9e0
fix(redshift): stop auth proxy on rollback to prevent listener collis…
vuthehuyht Aug 30, 2026
563b7c4
fix(redshift): ensure proxy shutdown failure does not abort container…
vuthehuyht Aug 30, 2026
b23be12
fix(redshift): add proper logging for intentionally ignored cleanup e…
vuthehuyht Aug 30, 2026
19e2d2a
fix: Wait for Redshift database readiness before proxying
vuthehuyht Aug 30, 2026
c0a961a
fix(redshift): prevent proxy port reallocation on shutdown failure
vuthehuyht Aug 30, 2026
9652985
fix(redshift): fix failing tests due to proxy changes
vuthehuyht Aug 30, 2026
5dc47e7
test(redshift): use Awaitility to connect to proxy
vuthehuyht Aug 30, 2026
daeaebd
chore(redshift): use proper imports instead of fully-qualified class …
vuthehuyht Aug 30, 2026
eaad313
fix(redshift): fix compiler errors introduced during import cleanup
vuthehuyht Aug 30, 2026
8277c25
fix(redshift): resolve proxy lifecycle bugs and CI network issues
vuthehuyht Aug 30, 2026
99e061e
fix(redshift): prevent proxy port exhaustion on startup failures
vuthehuyht Aug 30, 2026
96c751a
fix(redshift): retain proxy reference to allow retry on close failure
vuthehuyht Aug 30, 2026
c68033b
fix(redshift): abort cluster deletion if proxy fails to stop
vuthehuyht Aug 30, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ jobs:
DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
docker run -d --name floci --network compat-net \
-p 4566:4566 \
-p 7100-7199:7100-7199 \
-v /var/run/docker.sock:/var/run/docker.sock \
--group-add "$DOCKER_GID" \
-e FLOCI_BASE_URL=http://floci:4566 \
Expand All @@ -201,6 +202,7 @@ jobs:
-e FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ENABLED=true \
-e FLOCI_TLS_ENABLED=true \
-e FLOCI_SERVICES_EC2_MOCK=true \
-e FLOCI_SERVICES_REDSHIFT_ENDPOINT_HOST=floci \
floci:test-native

- name: Wait for floci to be ready
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RedshiftTest {
Expand Down Expand Up @@ -44,8 +45,13 @@ public void testCreateCluster() throws Exception {
Cluster cluster = describeRes.clusters().get(0);
assertEquals("test-cluster", cluster.clusterIdentifier());
assertNotNull(cluster.endpoint());
// No JDBC connection here: the endpoint carries the backing container's own host/port
// (Redshift has no RDS-style auth proxy yet), which isn't reachable from the harness.
String address = cluster.endpoint().address();
int port = cluster.endpoint().port();
String jdbcUrl = "jdbc:postgresql://" + address + ":" + port + "/dev";
try (java.sql.Connection conn =
java.sql.DriverManager.getConnection(jdbcUrl, "admin", "Password123")) {
assertTrue(conn.isValid(5));
}
}

@Test
Expand Down
9 changes: 7 additions & 2 deletions docs/services/redshift.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

**Protocol:** Query (XML) for the management API
**Management Endpoint:** `POST http://localhost:4566/` with `Action=` param
**Data Endpoint:** the `Endpoint` and `Port` returned by `DescribeClusters` (PostgreSQL wire protocol)
**Data Endpoint:** Floci's auth proxy on the `Endpoint` and `Port` returned by `DescribeClusters` (PostgreSQL wire protocol)

Floci emulates Amazon Redshift by managing a real [PostgreSQL](https://www.postgresql.org/) Docker container per cluster behind a Redshift-shaped control plane. Redshift speaks the PostgreSQL wire protocol, so the cluster endpoint returned by `DescribeClusters` works with any standard PostgreSQL driver (`psql`, JDBC, `psycopg`, …).
Floci emulates Amazon Redshift by managing a real [PostgreSQL](https://www.postgresql.org/) Docker container per cluster behind a Redshift-shaped control plane. Each cluster sits behind a lightweight auth proxy on the Floci host, so the endpoint is reachable from outside Docker and the master password is validated at the proxy — a `ModifyCluster` password change takes effect for new connections immediately. Redshift speaks the PostgreSQL wire protocol, so the cluster endpoint returned by `DescribeClusters` works with any standard PostgreSQL driver (`psql`, JDBC, `psycopg`, …).

> **Always read the host and port from `DescribeClusters`** rather than assuming a fixed port. PostgreSQL listens on `5432` *inside* the container; the port you connect to is dynamically assigned on the host and returned as `Clusters[0].Endpoint.Port`. Redshift's conventional port is `5439`, but the emulator does not bind it — use whatever `DescribeClusters` reports.

Expand Down Expand Up @@ -45,6 +45,9 @@ The container has **no persistent volume**: if the physical container survives a
| `FLOCI_SERVICES_REDSHIFT_ENABLED` | `true` | Enable or disable Redshift |
| `FLOCI_SERVICES_REDSHIFT_IMAGE_VERSION` | `postgres:15-alpine` | PostgreSQL Docker image backing each cluster |
| `FLOCI_SERVICES_REDSHIFT_DEFAULT_PORT` | `5439` | Reported Redshift port hint (the real host port is dynamic and comes from `DescribeClusters`) |
| `FLOCI_SERVICES_REDSHIFT_PROXY_BASE_PORT` | `7100` | Lowest host port the per-cluster auth proxies bind |
| `FLOCI_SERVICES_REDSHIFT_PROXY_MAX_PORT` | `7199` | Highest host port the per-cluster auth proxies bind |
| `FLOCI_SERVICES_REDSHIFT_ENDPOINT_HOST` | _(unset)_ | Hostname advertised in `DescribeClusters`; unset resolves from the Docker host |

Redshift needs the Docker socket so it can launch PostgreSQL containers. Each cluster's container is published on a dynamically assigned host port, returned by `DescribeClusters`.

Expand Down Expand Up @@ -139,3 +142,5 @@ print(cluster["Cluster"]["Endpoint"])
- Parameter groups apply no real engine settings; values are stored and echoed back only.
- Subnet groups, VPC routing, and security groups are metadata only.
- Resize, pause/resume, IAM authentication, snapshot schedules, and cross-region snapshot copy.
- The auth proxy validates only the master user's password. Non-master users pass straight through to PostgreSQL, which remains the authority for their credentials.
- IAM database authentication (`GetClusterCredentials`), and `sslmode=verify-full` against the self-signed proxy certificate.
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,17 @@ interface RedshiftServiceConfig {
@WithDefault("postgres:15-alpine")
String imageVersion();
Optional<String> dockerNetwork();

// Port range the per-cluster auth proxies bind on the Floci host. Disjoint from
// every other service's range (RDS uses 7001-7099).
@WithDefault("7100")
int proxyBasePort();
@WithDefault("7199")
int proxyMaxPort();

// Hostname clients use to reach a cluster endpoint. Empty -> resolved from
// DockerHostResolver (falls back to "localhost").
Optional<String> endpointHost();
}

interface RdsServiceConfig {
Expand Down
Loading
Loading