Skip to content

feat: add PostgREST container spec, config file, and service user role#303

Open
moizpgedge wants to merge 3 commits intofeat/PLAT-499/PLAT-500/PostgREST-prerequisites/image-wiringfrom
feat/PLAT-501/PLAT-502/PLAT-503/postgrest-container-spec
Open

feat: add PostgREST container spec, config file, and service user role#303
moizpgedge wants to merge 3 commits intofeat/PLAT-499/PLAT-500/PostgREST-prerequisites/image-wiringfrom
feat/PLAT-501/PLAT-502/PLAT-503/postgrest-container-spec

Conversation

@moizpgedge
Copy link
Copy Markdown
Contributor

@moizpgedge moizpgedge commented Mar 19, 2026

  • Add postgrest_config.go: generates postgrest.conf with db-schemas, db-anon-role,
    db-pool, db-max-rows, and optional JWT fields; credentials are excluded from the file
    and injected as libpq env vars at the container level; nil-guards on params/Config
  • Add postgrest_config_resource.go: manages postgrest.conf lifecycle on the host
    filesystem; depends on DirResource; delete is a no-op (parent dir handles cleanup)
  • Update service_spec.go: PostgREST container uses postgrest /app/data/postgrest.conf
    as command, postgrest --ready health check (no curl in static binary image),
    read-only bind mount, and PGHOST/PGPORT/PGDATABASE/PGUSER/PGPASSWORD env vars;
    PGHOST and PGPORT are built as comma-separated lists from the DatabaseHosts slice
    to support multi-host libpq connections; PGTARGETSESSIONATTRS is set when present
  • Update service_instance_spec.go: pass DatabaseHosts and TargetSessionAttrs
    through to ServiceContainerSpecOptions
  • Update service_user_role.go: add ServiceType and DBAnonRole fields; PostgREST
    role is created with LOGIN NOINHERIT + GRANT <anon_role> to enable the
    SET ROLE mechanism; bump ResourceVersion to 4
  • Register PostgRESTConfigResource in resources.go

Summary

Implements the PostgREST container runtime layer: config file generation,
host filesystem resource management, container spec (command, health check,
mounts, env vars), and service user role SQL with NOINHERIT semantics.

Changes

  • Add postgrest_config.go: generates postgrest.conf with db-schemas, db-anon-role,
    db-pool, db-max-rows, and optional JWT fields; credentials are excluded from the file
    and injected as libpq env vars at the container level
  • Add postgrest_config_resource.go: manages postgrest.conf lifecycle on the host
    filesystem; depends on DirResource; delete is a no-op (parent dir handles cleanup)
  • Update service_spec.go: PostgREST container uses postgrest /app/data/postgrest.conf
    as command, postgrest --ready health check (no curl in static binary image),
    read-only bind mount, and PGHOST/PGPORT/PGDATABASE/PGUSER/PGPASSWORD env vars
  • Update service_user_role.go: add ServiceType and DBAnonRole fields; PostgREST
    role is created with LOGIN NOINHERIT + GRANT <anon_role> to enable the
    SET ROLE mechanism; bump ResourceVersion to 4
  • Register PostgRESTConfigResource in resources.go

Testing

# Unit tests
go test ./server/internal/orchestrator/swarm/... -run "TestGeneratePostgRESTConfig|TestServiceContainerSpec|TestRoleAttributesAndGrants" -v
go test ./server/internal/database/... -run TestParsePostgRESTServiceConfig -v

# API validation
curl -s -X POST http://localhost:3000/v1/databases/storefront \
  -H "Content-Type: application/json" \
  -d '{"spec":{...,"services":[{"service_type":"postgrest","config":{"bad_key":"x"}}]}}' | jq .
# → 400 invalid_input: unknown config key "bad_key" '''

Retish command:

restish control-plane-local-1 update-database storefront \
  --body '{
    "spec": {
      "database_name": "storefront",
      "database_users": [{"username": "admin", "password": "password123", "db_owner": true, "attributes": ["LOGIN", "SUPERUSER"]}],
      "nodes": [
        {"name": "n1", "host_ids": ["host-1"]},
        {"name": "n2", "host_ids": ["host-2"]},
        {"name": "n3", "host_ids": ["host-3"]}
      ],
      "services": [{
        "service_id": "postgrest",
        "service_type": "postgrest",
        "version": "14.5",
        "host_ids": ["host-1"],
        "config": {
        }
      }]
    }
  }'



Curl command:

curl -s -X PUT http://localhost:3000/v1/databases/storefront \
  -H "Content-Type: application/json" \
  -d '{
    "spec": {
      "database_name": "storefront",
      "database_users": [{"username": "admin", "password": "password123", "db_owner": true, "attributes": ["LOGIN", "SUPERUSER"]}],
      "nodes": [
        {"name": "n1", "host_ids": ["host-1"]},
        {"name": "n2", "host_ids": ["host-2"]},
        {"name": "n3", "host_ids": ["host-3"]}
      ],
      "services": [{
        "service_id": "postgrest",
        "service_type": "postgrest",
        "version": "14.5",
        "host_ids": ["host-1"],
        "config": {
        }
      }]
    }
  }' 

Checklist

  • Tests added or updated (unit and/or e2e, as needed)
  • Documentation updated (if needed)
  • Issue is linked (branch name or URL in PR description)
  • Changelog entry added for user-facing behavior changes
  • Breaking changes (if any) are clearly called out in the PR description

Notes for Reviewers

Base branch is feat/PLAT-499/PLAT-500/PostgREST-prerequisites/image-wiring (not main)
End-to-end provisioning is not yet functional — blocked on (PLAT-504, orchestrator wiring + multi-host PGHOST) which depends on PR #301 merging
MCP path is unchanged; both service types share roleAttributesAndGrants() dispatch
Copilot review items addressed: nil guard in GeneratePostgRESTConfig, panic on
unexpected statement type in statementsSQL test helper, unquoted username in MCP test.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 758cef9d-ba81-46fe-8a5a-d74ecde4ce22

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main changes: adding PostgREST container spec, config file generation, and service user role functionality.
Description check ✅ Passed PR description follows the template structure with summary, detailed changes, testing examples, and most checklist items completed, though documentation and changelog entries are acknowledged as pending.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/PLAT-501/PLAT-502/PLAT-503/postgrest-container-spec

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the initial Swarm “runtime layer” for running PostgREST alongside existing services by introducing PostgREST config generation + host-file lifecycle management, extending the service container spec builder, and expanding service-user role SQL to support PostgREST’s SET ROLE flow.

Changes:

  • Add PostgREST config generation (postgrest.conf) plus a host filesystem resource to manage it.
  • Extend Swarm ServiceContainerSpec to support a new postgrest service type (command/args, healthcheck, env vars, RO mount).
  • Extend ServiceUserRole with ServiceType and DBAnonRole to generate PostgREST-appropriate role attributes/grants (and add unit tests).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
server/internal/orchestrator/swarm/postgrest_config.go Generates postgrest.conf content from typed PostgREST service config.
server/internal/orchestrator/swarm/postgrest_config_resource.go Manages postgrest.conf lifecycle on the host filesystem.
server/internal/orchestrator/swarm/postgrest_config_test.go Unit coverage for config generation and absence/presence of JWT fields.
server/internal/orchestrator/swarm/service_spec.go Adds postgrest container spec branch (env, mounts, healthcheck, command).
server/internal/orchestrator/swarm/service_spec_test.go Adds PostgREST container spec unit tests.
server/internal/orchestrator/swarm/service_user_role.go Adds service-type-driven role/grant generation, bumps resource version.
server/internal/orchestrator/swarm/service_user_role_test.go Adds unit tests for PostgREST + MCP role/grant behavior.
server/internal/orchestrator/swarm/resources.go Registers PostgRESTConfigResource type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

default: // "mcp" and future service types
// MCP role: direct grants rather than role membership to avoid exposing
// replication internals via pgedge_application_read_only.
// https://github.com/pgEdge/pgedge-postgres-mcp/blob/main/docs/guide/security_mgmt.go
Comment on lines +13 to +19
func statementsSQL(stmts postgres.Statements) []string {
out := make([]string, 0, len(stmts))
for _, s := range stmts {
if stmt, ok := s.(postgres.Statement); ok {
out = append(out, stmt.SQL)
}
}
r := &ServiceUserRole{
ServiceType: "mcp",
DatabaseName: "mydb",
Username: `"svc_mcp"`,
Comment on lines +17 to +25
func GeneratePostgRESTConfig(params *PostgRESTConfigParams) ([]byte, error) {
cfg := params.Config

var buf bytes.Buffer

fmt.Fprintf(&buf, "db-schemas = %q\n", cfg.DBSchemas)
fmt.Fprintf(&buf, "db-anon-role = %q\n", cfg.DBAnonRole)
fmt.Fprintf(&buf, "db-pool = %d\n", cfg.DBPool)
fmt.Fprintf(&buf, "db-max-rows = %d\n", cfg.MaxRows)
Comment on lines +123 to +126
configPath := filepath.Join(dirPath, "postgrest.conf")
if err := afero.WriteFile(fs, configPath, content, 0o600); err != nil {
return fmt.Errorf("failed to write %s: %w", configPath, err)
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jason-lynch
Can you guide for this issue?

@moizpgedge moizpgedge force-pushed the feat/PLAT-499/PLAT-500/PostgREST-prerequisites/image-wiring branch from c1b42cd to f8de470 Compare March 24, 2026 13:26
- Add PostgRESTConfigResource: writes postgrest.conf to the service data
  directory from a Go template; re-runs on every apply via ErrNotFound
- Add PostgRESTConfigResource registration in RegisterResourceTypes
- Extend ServiceUserRole with ServiceType and DBAnonRole fields;
  add roleAttributesAndGrants() — PostgREST gets LOGIN+NOINHERIT and
  GRANT <anon_role>, MCP/default uses group role membership
- Extend ServiceContainerSpecOptions with DatabaseHosts and
  TargetSessionAttrs; build PGHOST/PGPORT from the hosts slice in
  buildPostgRESTEnvVars; pass both fields from ServiceInstanceSpecResource
- Add postgrest case to ServiceContainerSpec: postgrest command,
  read-only config mount, and postgrest --ready health check
- Add tests for postgrest container spec, config generation, and
  service user role attributes/grants

PLAT-501, PLAT-502, PLAT-503
@moizpgedge moizpgedge force-pushed the feat/PLAT-501/PLAT-502/PLAT-503/postgrest-container-spec branch from 99689f0 to 4d38da0 Compare March 24, 2026 14:12
- Fix dead link in MCP grants comment: .go → .md
- Panic on unexpected statement type in statementsSQL test helper
  instead of silently dropping statements
- Use unquoted username in TestRoleAttributesAndGrants_MCP to better
  reflect production input (quoting is the caller's responsibility)
- Guard against nil params/Config in GeneratePostgRESTConfig

PLAT-501, PLAT-502, PLAT-503
// replication internals via pgedge_application_read_only.
// https://github.com/pgEdge/pgedge-postgres-mcp/blob/main/docs/guide/security_mgmt.md
attributes := []string{"LOGIN"}
grants := postgres.Statements{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we've removed these in the latest version of this file. Is that right, @rshoemaker?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, we use the built-in RO/RW roles instead:

// Determine group role based on mode
var groupRole string
switch r.Mode {
case ServiceUserRoleRO:
groupRole = "pgedge_application_read_only"
case ServiceUserRoleRW:
groupRole = "pgedge_application"
default:
return fmt.Errorf("unknown service user role mode: %q", r.Mode)
}

Copy link
Copy Markdown
Contributor

@rshoemaker rshoemaker Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moizpgedge After looking at this more carefully, I can see that the RO/RW roles are still being used correctly in createUserRole(). The "default:" block here is dead code (I think). It should probably be removed and this func should return an error for non-PostgREST cases. Or refactor the code.

mounts = []mount.Mount{
docker.BuildMount(opts.DataPath, "/app/data", true),
}
default: // "mcp"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - MCP specific code shouldn't be in a "default" branch, it should have an explicit branch check for "mcp".

- Remove dead MCP path from roleAttributesAndGrants; function is now
  PostgREST-only since MCP uses the group-role path in createUserRole
- Remove associated MCP tests (TestRoleAttributesAndGrants_MCP,
  TestRoleAttributesAndGrants_EmptyServiceTypeIsMCP)
- Replace default branch with explicit case "mcp" in ServiceContainerSpec
  switch; unknown service types now return an error

PLAT-501, PLAT-502, PLAT-503
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants