Skip to content

Declarative per-schema owner directive #89

Description

@hardbyte

What I'd like to be able to express

A way to say "this schema is owned by this role" directly in the policy YAML, analogous to how default_owner sets the global default today:

default_owner: pgloader_pg

schemas:
  - name: cdc
    owner: cdc_owner        # <-- new, per-schema override
    profiles: [editor, viewer, subscriber]
  - name: awa
    owner: awa_owner
    profiles: [editor, viewer]

On apply, pgroles would:

  1. Ensure the schema exists with the correct AUTHORIZATION (using CREATE SCHEMA ... AUTHORIZATION on first create, ALTER SCHEMA ... OWNER TO on converge).
  2. Set ALTER DEFAULT PRIVILEGES FOR ROLE {owner} per profile, so the editor/viewer grants keep applying to objects the owner creates afterwards.

Out of scope

Bulk reassignment of existing schema-scoped objects (tables, functions, sequences, types) to the new owner. Reasons to leave this out:

  • Blast radius. Changing the declared owner would rewrite ownership of potentially hundreds of objects on the next apply. Bigger consequence than any of pgroles' current converge steps.
  • Edge cases. Partitioned parents + children, enum array types, function overloads, materialised views, extension-owned objects. Each solvable, each a source of bugs.
  • Overlap with migrations. Migrations sometimes intentionally reassign ownership for specific reasons (RLS, replication, FDW). Silently re-overriding that on converge is the kind of trap that's hard to debug.
  • Incompleteness. Services that create objects at runtime (e.g. a cdc-relay calling CREATE TABLE IF NOT EXISTS) produce objects owned by the connecting role, not necessarily the schema owner, unless they SET ROLE first. pgroles can't enforce that from outside, so the invariant is never fully pgroles'.

Object-level ownership reassignment stays a one-time migration concern when adopting the pattern. Optionally pgroles could ship a CLI helper (pgroles reassign --schema cdc) that does the walk, but it's not part of the converge loop.

Why

A handful of services at work own a schema that they manage themselves at runtime — their startup code does CREATE OR REPLACE FUNCTION, CREATE TABLE IF NOT EXISTS, dynamic CREATE TRIGGER, etc. Those statements either require ownership or (for replace-style DDL) require the caller to be a member of the owning role.

Concretely, in one of our services:

  • A change-data-capture relay owns a cdc schema whose DDL it generates at boot.
  • An async-worker framework owns an awa schema and uses awa_owner (NOLOGIN) as the owning role, with service login roles granted membership.

With the current policy shape the only way I've found to express this is:

  1. Let pgroles apply create the schema under default_owner.
  2. Run a post-apply SQL step that does ALTER SCHEMA … OWNER TO and sets ALTER DEFAULT PRIVILEGES FOR ROLE for the new owner.

That works but moves a small piece of the policy out of the declarative file and into a side-car SQL script, which is awkward to keep consistent.

What I've considered

  • Just making the service login role the owner. Works but conflates ownership with the connecting identity, and rules out having multiple login roles (old + new during a rollout) both act as owner via membership — which is the obvious zero-downtime story.
  • A dedicated {schema}_owner NOLOGIN role with login roles granted membership. This is a clean pattern and works fine at the SQL level; I'd just like pgroles to know about it so the ownership and its default privileges are part of the converged state.

Shape of the feature

Either:

  1. owner: field on each schema entry (as sketched above). Falls back to default_owner if omitted.
  2. Ownership expressed on the owning role instead, e.g. owns_schemas: [cdc, awa] on a role entry.

Either would let pgroles also set ALTER DEFAULT PRIVILEGES FOR ROLE {owner} per profile so editor/viewer grants keep applying to future objects without a separate SQL step.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions