Skip to content

feat: allow granting rds_replication - #20

Merged
pascal-botpress merged 2 commits into
pb/rm-esbuildfrom
pb/rds-replication
Aug 27, 2026
Merged

feat: allow granting rds_replication#20
pascal-botpress merged 2 commits into
pb/rm-esbuildfrom
pb/rds-replication

Conversation

@pascal-botpress

@pascal-botpress pascal-botpress commented Aug 26, 2026

Copy link
Copy Markdown
Member

Contributes to KKN-927

Copilot AI lite review requested due to automatic review settings August 26, 2026 19:50
@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces a PostgreSQL role-membership custom resource so constructs can grant predefined roles such as rds_replication.

  • Adds GRANT and REVOKE query helpers and custom-resource lifecycle handling.
  • Exports a new RoleMembership construct with configurable removal behavior.
  • Routes the new custom-resource type through the shared Lambda handler.

Confidence Score: 3/5

The PR should not merge until membership identities cannot collide and same-stack role creation is reliably ordered before the grant.

Distinct valid memberships can be treated as the same resource and silently retain incorrect privileges, while missing construct dependencies can make deployments invoke GRANT before the referenced roles exist.

Files Needing Attention: cdk-postgresql/lib/role-membership.handler.ts, cdk-postgresql/lib/role-membership.ts

Important Files Changed

Filename Overview
cdk-postgresql/lib/role-membership.handler.ts Implements membership lifecycle operations, but ambiguous physical-ID construction can silently skip valid membership updates.
cdk-postgresql/lib/role-membership.ts Defines the public construct, but same-stack roles supplied by name have no inferred deployment ordering.
cdk-postgresql/lib/postgres.ts Adds correctly identifier-escaped GRANT and REVOKE helpers.
cdk-postgresql/lib/handler.ts Correctly routes the new custom-resource type to its handler.
cdk-postgresql/lib/index.ts Exports the new public RoleMembership API.

Reviews (1): Last reviewed commit: "feat: allow granting rds_replication" | Re-trigger Greptile

Comment thread cdk-postgresql/lib/role-membership.handler.ts
Comment thread cdk-postgresql/lib/role-membership.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class support for PostgreSQL role membership grants/revokes via a new CDK Construct + Lambda custom resource, enabling use cases like granting rds_replication on RDS/Aurora where direct replication privileges can’t be granted.

Changes:

  • Introduces RoleMembership Construct and PredefinedRoleName union for common built-in roles.
  • Adds a new custom resource handler (Custom::Postgresql-RoleMembership) and routes it from the shared Lambda entry handler.
  • Adds SQL helpers in postgres.ts to GRANT/REVOKE role memberships.

Reviewed changes

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

Show a summary per file
File Description
cdk-postgresql/lib/role-membership.ts New CDK Construct defining the RoleMembership custom resource and its typed props.
cdk-postgresql/lib/role-membership.handler.ts New Lambda handler implementing Create/Update/Delete for role membership grants/revokes.
cdk-postgresql/lib/postgres.ts Adds grantRoleMembership / revokeRoleMembership SQL helpers.
cdk-postgresql/lib/index.ts Exports the new RoleMembership Construct from the library entrypoint.
cdk-postgresql/lib/handler.ts Routes the new Custom::Postgresql-RoleMembership resource type to its handler.
Suppressed comments (2)

cdk-postgresql/lib/postgres.ts:81

  • revokeRoleMembership is not idempotent: if the membership was already removed (manual intervention/drift) the stack delete or replacement cleanup can fail. Consider treating "not a member" as success, while still throwing on unexpected errors.
  await client.query(
    `REVOKE ${escapeIdentifier(role)} FROM ${escapeIdentifier(member)}`
  );
};

cdk-postgresql/lib/role-membership.handler.ts:109

  • If postgres.revokeRoleMembership throws, the PG client connection will never be closed. Wrapping the query in a try/finally avoids leaking connections and reduces the risk of Lambda hangs/timeouts during stack deletion/replacement.
  console.log(`Revoking ${role} from ${member}`);
  const client = await getConnectedClient(connection);

  await postgres.revokeRoleMembership({ client, role, member });


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cdk-postgresql/lib/postgres.ts
Comment thread cdk-postgresql/lib/role-membership.handler.ts
Comment thread cdk-postgresql/lib/role-membership.ts
@pascal-botpress
pascal-botpress force-pushed the pb/rds-replication branch 2 times, most recently from e1d9ba6 to e53f9b1 Compare August 26, 2026 20:36
greptile-apps[bot]

This comment was marked as outdated.

greptile-apps[bot]

This comment was marked as outdated.

Comment thread cdk-postgresql/lib/postgres.ts Outdated
Comment on lines +78 to +80
await client.query(
`REVOKE ${escapeIdentifier(role)} FROM ${escapeIdentifier(member)}`
);

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.

REVOKE is idempotent for a membership that isn't there, but it is not tolerant of a role that isn't there — that case is a hard error, so the Delete path can wedge a stack.

Verified against real containers (PG 16.14 and PG 13), quoted the same way escapeIdentifier quotes:

REVOKE "replicator" FROM "myuser"      (not a member) -> WARNING: ... has not been granted ...   exit=0
REVOKE "ghostrole"  FROM "myuser"      (no such role) -> ERROR: role "ghostrole" does not exist   exit=1
REVOKE "replicator" FROM "ghostmember"               -> ERROR: role "ghostmember" does not exist  exit=1

So handleDelete fails in exactly the situation the new doc comment on RoleMembership describes — no CloudFormation dependency between a Role and its membership:

  1. Stack teardown. With no dependency between the two, CFN's deletion order between them is unspecified. If DROP USER runs first, the REVOKE errors and the resource lands in DELETE_FAILED — manual --skip-resources to get out.
  2. Rollback of a grant that ran too early. Create fails with role "..." does not exist, CFN rolls back with a Delete, and the REVOKE hits the same error. A recoverable deploy failure turns into a stuck rollback.

Suggest treating 42704 as non-fatal, mirroring the 0LP01 handling already in createDatabase above:

Suggested change
await client.query(
`REVOKE ${escapeIdentifier(role)} FROM ${escapeIdentifier(member)}`
);
try {
await client.query(
`REVOKE ${escapeIdentifier(role)} FROM ${escapeIdentifier(member)}`
);
} catch (e) {
if (!util.types.isNativeError(e)) {
throw e;
}
if (!isDatabaseError(e) || e.code !== "42704") {
throw new VError(e, "unexpected error while revoking role membership");
}
console.warn(e.message);
}

Nothing is needed on the GRANT side: a missing role there should fail the deploy, and re-granting an existing membership is already a NOTICE (exit=0) on both 13 and 16 — so the earlier idempotency comment on grantRoleMembership was a false positive.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks, it's now fixed

@pascal-botpress
pascal-botpress merged commit 45ded21 into master Aug 27, 2026
1 check passed
@linear-code

linear-code Bot commented Aug 27, 2026

Copy link
Copy Markdown

KKN-927

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.

3 participants