@infracraft/pulumi 1.23.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/neon/role.cjs +20 -2
- package/dist/neon/role.cjs.map +1 -1
- package/dist/neon/role.d.cts +19 -0
- package/dist/neon/role.d.cts.map +1 -1
- package/dist/neon/role.d.mts +19 -0
- package/dist/neon/role.d.mts.map +1 -1
- package/dist/neon/role.mjs +20 -2
- package/dist/neon/role.mjs.map +1 -1
- package/package.json +1 -1
package/dist/neon/role.cjs
CHANGED
|
@@ -69,13 +69,30 @@ var NeonRoleResourceProvider = class {
|
|
|
69
69
|
_pulumi_pulumi.log.warn(`Failed to delete Neon role "${props.name}" (may already be deleted)`);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Rotates the password in place when `passwordVersion` changes. This is the
|
|
74
|
+
* only updatable input (everything else replaces), so an update firing means
|
|
75
|
+
* a rotation was requested.
|
|
76
|
+
*/
|
|
77
|
+
async update(_id, olds, news) {
|
|
78
|
+
let password = olds.password;
|
|
79
|
+
if (olds.passwordVersion !== news.passwordVersion) {
|
|
80
|
+
_pulumi_pulumi.log.info(`Rotating password for Neon role "${news.name}" (passwordVersion ${olds.passwordVersion ?? "unset"} → ${news.passwordVersion ?? "unset"})`);
|
|
81
|
+
password = await resetRolePassword(new require_neon_client.NeonClient(news.apiKey), news.projectId, news.branchId, news.name);
|
|
82
|
+
}
|
|
83
|
+
return { outs: {
|
|
84
|
+
...news,
|
|
85
|
+
password
|
|
86
|
+
} };
|
|
87
|
+
}
|
|
72
88
|
async diff(_id, olds, news) {
|
|
73
89
|
const replaces = [];
|
|
74
90
|
if (olds.projectId !== news.projectId) replaces.push("projectId");
|
|
75
91
|
if (olds.branchId !== news.branchId) replaces.push("branchId");
|
|
76
92
|
if (olds.name !== news.name) replaces.push("name");
|
|
93
|
+
const rotates = olds.passwordVersion !== news.passwordVersion;
|
|
77
94
|
return {
|
|
78
|
-
changes: replaces.length > 0,
|
|
95
|
+
changes: replaces.length > 0 || rotates,
|
|
79
96
|
replaces,
|
|
80
97
|
deleteBeforeReplace: true
|
|
81
98
|
};
|
|
@@ -113,7 +130,8 @@ var NeonRole = class extends _pulumi_pulumi.ComponentResource {
|
|
|
113
130
|
projectId: project.id,
|
|
114
131
|
branchId: branch.id,
|
|
115
132
|
name: args.name,
|
|
116
|
-
resetPassword: args.resetPassword ?? false
|
|
133
|
+
resetPassword: args.resetPassword ?? false,
|
|
134
|
+
passwordVersion: args.passwordVersion
|
|
117
135
|
}, { parent: this });
|
|
118
136
|
this.password = resource.password;
|
|
119
137
|
this.registerOutputs({ password: this.password });
|
package/dist/neon/role.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.cjs","names":["NeonClient","pulumi"],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { NeonBranch } from \"./branch\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n\n\t/**\n\t * When the role already exists (adopted), reset its password to a fresh value\n\t * instead of revealing the inherited one. Use on copy-on-write branches to\n\t * isolate the credential from the parent branch. Ignored when the role is\n\t * freshly created (a new role already gets its own password).\n\t */\n\tresetPassword: boolean;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for the reset_password endpoint (nested under `role`). */\ninterface ResetPasswordResponse {\n\trole?: { password?: string };\n\tpassword?: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Resets an existing role's password to a fresh value and returns it.\n * Used to isolate an adopted (copy-on-write) role from the parent branch's credential.\n */\nasync function resetRolePassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.post<ResetPasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reset_password`,\n\t\t{},\n\t);\n\n\tconst password = result.role?.password ?? result.password;\n\n\tif (!password) {\n\t\tthrow new Error(\n\t\t\t`Neon reset_password returned no password for role \"${name}\"`,\n\t\t);\n\t}\n\n\treturn password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class NeonRoleResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else if (inputs.resetPassword) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Resetting password for adopted Neon role \"${inputs.name}\" to isolate it from the parent branch`,\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password =\n\t\t\texists && inputs.resetPassword\n\t\t\t\t? await resetRolePassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t)\n\t\t\t\t: await revealPassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonRoleResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tbranchId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tresetPassword: pulumi.Input<boolean>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\t// `password` is an output-only secret. Declaring it via additionalSecretOutputs\n\t\t// (rather than a pulumi.secret(undefined) input placeholder) is the only reliable\n\t\t// way to mark a dynamic-provider output secret: the placeholder gives the property\n\t\t// both a secret-undefined input and a real output, and a downstream dynamic resource\n\t\t// consuming it can race onto the undefined, producing \"Unexpected struct type\"\n\t\t// during protobuf serialization (Pulumi #16041, #3012).\n\t\tsuper(\n\t\t\tnew NeonRoleResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, password: undefined },\n\t\t\t{ ...opts, additionalSecretOutputs: [\"password\"] },\n\t\t);\n\t}\n}\n\n/** Options type for NeonRole — replaces Pulumi's native `provider` field. */\ntype NeonRoleOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n\n\t/** Neon branch context. */\n\tbranch: NeonBranch;\n};\n\n/** Args for NeonRole. */\nexport interface NeonRoleArgs {\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * When the role is adopted (already exists on the branch), reset its password to a\n\t * fresh value instead of inheriting the parent's. Set this on copy-on-write\n\t * (non-production) branches to isolate the credential from production. Defaults to\n\t * `false` (adopt and reveal the existing password). The reset runs once, at creation.\n\t */\n\tresetPassword?: pulumi.Input<boolean>;\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"owner\", {\n * name: \"neondb_owner\",\n * }, { provider, project, branch });\n * ```\n */\nexport class NeonRole extends pulumi.ComponentResource {\n\t/** Role password (secret). */\n\tpublic readonly password: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonRoleArgs, opts: NeonRoleOptions) {\n\t\tconst { provider, project, branch, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Role\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonRoleResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tname: args.name,\n\t\t\t\tresetPassword: args.resetPassword ?? false,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.password = resource.password;\n\n\t\tthis.registerOutputs({ password: this.password });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"role.cjs","names":["NeonClient","pulumi"],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { NeonBranch } from \"./branch\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n\n\t/**\n\t * When the role already exists (adopted), reset its password to a fresh value\n\t * instead of revealing the inherited one. Use on copy-on-write branches to\n\t * isolate the credential from the parent branch. Ignored when the role is\n\t * freshly created (a new role already gets its own password).\n\t */\n\tresetPassword: boolean;\n\n\t/**\n\t * Rotation handle: bumping this number resets the role's password IN PLACE on\n\t * the next `up` (an update, never a replace — replacing would try to delete\n\t * the role, which Neon refuses for default roles and which would drop real\n\t * grants for others). Leave unset until the first rotation is needed.\n\t */\n\tpasswordVersion?: number;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for the reset_password endpoint (nested under `role`). */\ninterface ResetPasswordResponse {\n\trole?: { password?: string };\n\tpassword?: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Resets an existing role's password to a fresh value and returns it.\n * Used to isolate an adopted (copy-on-write) role from the parent branch's credential.\n */\nasync function resetRolePassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.post<ResetPasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reset_password`,\n\t\t{},\n\t);\n\n\tconst password = result.role?.password ?? result.password;\n\n\tif (!password) {\n\t\tthrow new Error(\n\t\t\t`Neon reset_password returned no password for role \"${name}\"`,\n\t\t);\n\t}\n\n\treturn password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class NeonRoleResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else if (inputs.resetPassword) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Resetting password for adopted Neon role \"${inputs.name}\" to isolate it from the parent branch`,\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password =\n\t\t\texists && inputs.resetPassword\n\t\t\t\t? await resetRolePassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t)\n\t\t\t\t: await revealPassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Rotates the password in place when `passwordVersion` changes. This is the\n\t * only updatable input (everything else replaces), so an update firing means\n\t * a rotation was requested.\n\t */\n\tasync update(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tlet password = olds.password;\n\n\t\tif (olds.passwordVersion !== news.passwordVersion) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Rotating password for Neon role \"${news.name}\" (passwordVersion ${olds.passwordVersion ?? \"unset\"} → ${news.passwordVersion ?? \"unset\"})`,\n\t\t\t);\n\n\t\t\tconst client = new NeonClient(news.apiKey);\n\n\t\t\tpassword = await resetRolePassword(\n\t\t\t\tclient,\n\t\t\t\tnews.projectId,\n\t\t\t\tnews.branchId,\n\t\t\t\tnews.name,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, password } };\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\tconst rotates = olds.passwordVersion !== news.passwordVersion;\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || rotates,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonRoleResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tbranchId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tresetPassword: pulumi.Input<boolean>;\n\t\t\tpasswordVersion?: pulumi.Input<number>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\t// `password` is an output-only secret. Declaring it via additionalSecretOutputs\n\t\t// (rather than a pulumi.secret(undefined) input placeholder) is the only reliable\n\t\t// way to mark a dynamic-provider output secret: the placeholder gives the property\n\t\t// both a secret-undefined input and a real output, and a downstream dynamic resource\n\t\t// consuming it can race onto the undefined, producing \"Unexpected struct type\"\n\t\t// during protobuf serialization (Pulumi #16041, #3012).\n\t\tsuper(\n\t\t\tnew NeonRoleResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, password: undefined },\n\t\t\t{ ...opts, additionalSecretOutputs: [\"password\"] },\n\t\t);\n\t}\n}\n\n/** Options type for NeonRole — replaces Pulumi's native `provider` field. */\ntype NeonRoleOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n\n\t/** Neon branch context. */\n\tbranch: NeonBranch;\n};\n\n/** Args for NeonRole. */\nexport interface NeonRoleArgs {\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * When the role is adopted (already exists on the branch), reset its password to a\n\t * fresh value instead of inheriting the parent's. Set this on copy-on-write\n\t * (non-production) branches to isolate the credential from production. Defaults to\n\t * `false` (adopt and reveal the existing password). The reset runs once, at creation.\n\t */\n\tresetPassword?: pulumi.Input<boolean>;\n\n\t/**\n\t * Rotation handle: bump to rotate the role's password in place on the next\n\t * `up`. Everything that consumes `password` (connection strings, service env\n\t * vars, dependent redeploys) cascades automatically.\n\t */\n\tpasswordVersion?: pulumi.Input<number>;\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"owner\", {\n * name: \"neondb_owner\",\n * }, { provider, project, branch });\n * ```\n */\nexport class NeonRole extends pulumi.ComponentResource {\n\t/** Role password (secret). */\n\tpublic readonly password: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonRoleArgs, opts: NeonRoleOptions) {\n\t\tconst { provider, project, branch, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Role\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonRoleResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tname: args.name,\n\t\t\t\tresetPassword: args.resetPassword ?? false,\n\t\t\t\tpasswordVersion: args.passwordVersion,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.password = resource.password;\n\n\t\tthis.registerOutputs({ password: this.password });\n\t}\n}\n"],"mappings":"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"}
|
package/dist/neon/role.d.cts
CHANGED
|
@@ -22,6 +22,13 @@ interface NeonRoleInputs {
|
|
|
22
22
|
* freshly created (a new role already gets its own password).
|
|
23
23
|
*/
|
|
24
24
|
resetPassword: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Rotation handle: bumping this number resets the role's password IN PLACE on
|
|
27
|
+
* the next `up` (an update, never a replace — replacing would try to delete
|
|
28
|
+
* the role, which Neon refuses for default roles and which would drop real
|
|
29
|
+
* grants for others). Leave unset until the first rotation is needed.
|
|
30
|
+
*/
|
|
31
|
+
passwordVersion?: number;
|
|
25
32
|
}
|
|
26
33
|
/** Persisted state for the Neon role. */
|
|
27
34
|
interface NeonRoleOutputs extends NeonRoleInputs {
|
|
@@ -40,6 +47,12 @@ declare class NeonRoleResourceProvider implements pulumi.dynamic.ResourceProvide
|
|
|
40
47
|
create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
41
48
|
read(id: string, props: NeonRoleOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
42
49
|
delete(_id: string, props: NeonRoleOutputs): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Rotates the password in place when `passwordVersion` changes. This is the
|
|
52
|
+
* only updatable input (everything else replaces), so an update firing means
|
|
53
|
+
* a rotation was requested.
|
|
54
|
+
*/
|
|
55
|
+
update(_id: string, olds: NeonRoleOutputs, news: NeonRoleInputs): Promise<pulumi.dynamic.UpdateResult>;
|
|
43
56
|
diff(_id: string, olds: NeonRoleOutputs, news: NeonRoleInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
44
57
|
}
|
|
45
58
|
/** Options type for NeonRole — replaces Pulumi's native `provider` field. */
|
|
@@ -59,6 +72,12 @@ interface NeonRoleArgs {
|
|
|
59
72
|
* `false` (adopt and reveal the existing password). The reset runs once, at creation.
|
|
60
73
|
*/
|
|
61
74
|
resetPassword?: pulumi.Input<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Rotation handle: bump to rotate the role's password in place on the next
|
|
77
|
+
* `up`. Everything that consumes `password` (connection strings, service env
|
|
78
|
+
* vars, dependent redeploys) cascades automatically.
|
|
79
|
+
*/
|
|
80
|
+
passwordVersion?: pulumi.Input<number>;
|
|
62
81
|
}
|
|
63
82
|
/**
|
|
64
83
|
* Manages a Neon database role with adopt-or-create semantics.
|
package/dist/neon/role.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.d.cts","names":[],"sources":["../../src/neon/role.ts"],"mappings":";;;;;;;;UAOiB,cAAA;;EAEhB,MAAA;EAF8B;EAK9B,SAAA;EAL8B;EAQ9B,QAAA;EAHA;EAMA,IAAA;EAAA
|
|
1
|
+
{"version":3,"file":"role.d.cts","names":[],"sources":["../../src/neon/role.ts"],"mappings":";;;;;;;;UAOiB,cAAA;;EAEhB,MAAA;EAF8B;EAK9B,SAAA;EAL8B;EAQ9B,QAAA;EAHA;EAMA,IAAA;EAAA;;;;AAgBe;AACf;EATA,aAAA;;;AAcQ;AAuFT;;;EA7FC,eAAA;AAAA;;UAIS,eAAA,SAAwB,cAAc;EA2IpC;EAzIX,QAAQ;AAAA;;;;;;;;;cAuFI,wBAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CAAO,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EA4CvD,IAAA,CACL,EAAA,UACA,KAAA,EAAO,eAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAgBpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,eAAA,GAAkB,OAAA;EAjExC;;;;;EAoFL,MAAA,CACL,GAAA,UACA,IAAA,EAAM,eAAA,EACN,IAAA,EAAM,cAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAqBpB,IAAA,CACL,GAAA,UACA,IAAA,EAAM,eAAA,EACN,IAAA,EAAM,cAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAyDtB,eAAA,GAAkB,IAAA,CAAK,MAAA,CAAO,wBAAA;EA5H5B,mCA8HN,QAAA,EAAU,YAAA,EA5HF;EA+HR,OAAA,EAAS,WAAA,EA9HN;EAiIH,MAAA,EAAQ,UAAA;AAAA;;UAIQ,YAAA;EArHH;EAuHb,IAAA,EAAM,MAAA,CAAO,KAAA;EAvHa;;;;;;EA+H1B,aAAA,GAAgB,MAAA,CAAO,KAAA;EAzGtB;;;;;EAgHD,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;AAtFW;AAuBpC;cA6EY,QAAA,SAAiB,MAAA,CAAO,iBAAA;;WAEpB,QAAA,EAAU,MAAA,CAAO,MAAA;cAErB,IAAA,UAAc,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,eAAA;AAAA"}
|
package/dist/neon/role.d.mts
CHANGED
|
@@ -22,6 +22,13 @@ interface NeonRoleInputs {
|
|
|
22
22
|
* freshly created (a new role already gets its own password).
|
|
23
23
|
*/
|
|
24
24
|
resetPassword: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Rotation handle: bumping this number resets the role's password IN PLACE on
|
|
27
|
+
* the next `up` (an update, never a replace — replacing would try to delete
|
|
28
|
+
* the role, which Neon refuses for default roles and which would drop real
|
|
29
|
+
* grants for others). Leave unset until the first rotation is needed.
|
|
30
|
+
*/
|
|
31
|
+
passwordVersion?: number;
|
|
25
32
|
}
|
|
26
33
|
/** Persisted state for the Neon role. */
|
|
27
34
|
interface NeonRoleOutputs extends NeonRoleInputs {
|
|
@@ -40,6 +47,12 @@ declare class NeonRoleResourceProvider implements pulumi.dynamic.ResourceProvide
|
|
|
40
47
|
create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
41
48
|
read(id: string, props: NeonRoleOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
42
49
|
delete(_id: string, props: NeonRoleOutputs): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Rotates the password in place when `passwordVersion` changes. This is the
|
|
52
|
+
* only updatable input (everything else replaces), so an update firing means
|
|
53
|
+
* a rotation was requested.
|
|
54
|
+
*/
|
|
55
|
+
update(_id: string, olds: NeonRoleOutputs, news: NeonRoleInputs): Promise<pulumi.dynamic.UpdateResult>;
|
|
43
56
|
diff(_id: string, olds: NeonRoleOutputs, news: NeonRoleInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
44
57
|
}
|
|
45
58
|
/** Options type for NeonRole — replaces Pulumi's native `provider` field. */
|
|
@@ -59,6 +72,12 @@ interface NeonRoleArgs {
|
|
|
59
72
|
* `false` (adopt and reveal the existing password). The reset runs once, at creation.
|
|
60
73
|
*/
|
|
61
74
|
resetPassword?: pulumi.Input<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Rotation handle: bump to rotate the role's password in place on the next
|
|
77
|
+
* `up`. Everything that consumes `password` (connection strings, service env
|
|
78
|
+
* vars, dependent redeploys) cascades automatically.
|
|
79
|
+
*/
|
|
80
|
+
passwordVersion?: pulumi.Input<number>;
|
|
62
81
|
}
|
|
63
82
|
/**
|
|
64
83
|
* Manages a Neon database role with adopt-or-create semantics.
|
package/dist/neon/role.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.d.mts","names":[],"sources":["../../src/neon/role.ts"],"mappings":";;;;;;;;UAOiB,cAAA;;EAEhB,MAAA;EAF8B;EAK9B,SAAA;EAL8B;EAQ9B,QAAA;EAHA;EAMA,IAAA;EAAA
|
|
1
|
+
{"version":3,"file":"role.d.mts","names":[],"sources":["../../src/neon/role.ts"],"mappings":";;;;;;;;UAOiB,cAAA;;EAEhB,MAAA;EAF8B;EAK9B,SAAA;EAL8B;EAQ9B,QAAA;EAHA;EAMA,IAAA;EAAA;;;;AAgBe;AACf;EATA,aAAA;;;AAcQ;AAuFT;;;EA7FC,eAAA;AAAA;;UAIS,eAAA,SAAwB,cAAc;EA2IpC;EAzIX,QAAQ;AAAA;;;;;;;;;cAuFI,wBAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CAAO,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EA4CvD,IAAA,CACL,EAAA,UACA,KAAA,EAAO,eAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAgBpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,eAAA,GAAkB,OAAA;EAjExC;;;;;EAoFL,MAAA,CACL,GAAA,UACA,IAAA,EAAM,eAAA,EACN,IAAA,EAAM,cAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAqBpB,IAAA,CACL,GAAA,UACA,IAAA,EAAM,eAAA,EACN,IAAA,EAAM,cAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAyDtB,eAAA,GAAkB,IAAA,CAAK,MAAA,CAAO,wBAAA;EA5H5B,mCA8HN,QAAA,EAAU,YAAA,EA5HF;EA+HR,OAAA,EAAS,WAAA,EA9HN;EAiIH,MAAA,EAAQ,UAAA;AAAA;;UAIQ,YAAA;EArHH;EAuHb,IAAA,EAAM,MAAA,CAAO,KAAA;EAvHa;;;;;;EA+H1B,aAAA,GAAgB,MAAA,CAAO,KAAA;EAzGtB;;;;;EAgHD,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;AAtFW;AAuBpC;cA6EY,QAAA,SAAiB,MAAA,CAAO,iBAAA;;WAEpB,QAAA,EAAU,MAAA,CAAO,MAAA;cAErB,IAAA,UAAc,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,eAAA;AAAA"}
|
package/dist/neon/role.mjs
CHANGED
|
@@ -67,13 +67,30 @@ var NeonRoleResourceProvider = class {
|
|
|
67
67
|
pulumi.log.warn(`Failed to delete Neon role "${props.name}" (may already be deleted)`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Rotates the password in place when `passwordVersion` changes. This is the
|
|
72
|
+
* only updatable input (everything else replaces), so an update firing means
|
|
73
|
+
* a rotation was requested.
|
|
74
|
+
*/
|
|
75
|
+
async update(_id, olds, news) {
|
|
76
|
+
let password = olds.password;
|
|
77
|
+
if (olds.passwordVersion !== news.passwordVersion) {
|
|
78
|
+
pulumi.log.info(`Rotating password for Neon role "${news.name}" (passwordVersion ${olds.passwordVersion ?? "unset"} → ${news.passwordVersion ?? "unset"})`);
|
|
79
|
+
password = await resetRolePassword(new NeonClient(news.apiKey), news.projectId, news.branchId, news.name);
|
|
80
|
+
}
|
|
81
|
+
return { outs: {
|
|
82
|
+
...news,
|
|
83
|
+
password
|
|
84
|
+
} };
|
|
85
|
+
}
|
|
70
86
|
async diff(_id, olds, news) {
|
|
71
87
|
const replaces = [];
|
|
72
88
|
if (olds.projectId !== news.projectId) replaces.push("projectId");
|
|
73
89
|
if (olds.branchId !== news.branchId) replaces.push("branchId");
|
|
74
90
|
if (olds.name !== news.name) replaces.push("name");
|
|
91
|
+
const rotates = olds.passwordVersion !== news.passwordVersion;
|
|
75
92
|
return {
|
|
76
|
-
changes: replaces.length > 0,
|
|
93
|
+
changes: replaces.length > 0 || rotates,
|
|
77
94
|
replaces,
|
|
78
95
|
deleteBeforeReplace: true
|
|
79
96
|
};
|
|
@@ -111,7 +128,8 @@ var NeonRole = class extends pulumi.ComponentResource {
|
|
|
111
128
|
projectId: project.id,
|
|
112
129
|
branchId: branch.id,
|
|
113
130
|
name: args.name,
|
|
114
|
-
resetPassword: args.resetPassword ?? false
|
|
131
|
+
resetPassword: args.resetPassword ?? false,
|
|
132
|
+
passwordVersion: args.passwordVersion
|
|
115
133
|
}, { parent: this });
|
|
116
134
|
this.password = resource.password;
|
|
117
135
|
this.registerOutputs({ password: this.password });
|
package/dist/neon/role.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.mjs","names":[],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { NeonBranch } from \"./branch\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n\n\t/**\n\t * When the role already exists (adopted), reset its password to a fresh value\n\t * instead of revealing the inherited one. Use on copy-on-write branches to\n\t * isolate the credential from the parent branch. Ignored when the role is\n\t * freshly created (a new role already gets its own password).\n\t */\n\tresetPassword: boolean;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for the reset_password endpoint (nested under `role`). */\ninterface ResetPasswordResponse {\n\trole?: { password?: string };\n\tpassword?: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Resets an existing role's password to a fresh value and returns it.\n * Used to isolate an adopted (copy-on-write) role from the parent branch's credential.\n */\nasync function resetRolePassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.post<ResetPasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reset_password`,\n\t\t{},\n\t);\n\n\tconst password = result.role?.password ?? result.password;\n\n\tif (!password) {\n\t\tthrow new Error(\n\t\t\t`Neon reset_password returned no password for role \"${name}\"`,\n\t\t);\n\t}\n\n\treturn password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class NeonRoleResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else if (inputs.resetPassword) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Resetting password for adopted Neon role \"${inputs.name}\" to isolate it from the parent branch`,\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password =\n\t\t\texists && inputs.resetPassword\n\t\t\t\t? await resetRolePassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t)\n\t\t\t\t: await revealPassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonRoleResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tbranchId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tresetPassword: pulumi.Input<boolean>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\t// `password` is an output-only secret. Declaring it via additionalSecretOutputs\n\t\t// (rather than a pulumi.secret(undefined) input placeholder) is the only reliable\n\t\t// way to mark a dynamic-provider output secret: the placeholder gives the property\n\t\t// both a secret-undefined input and a real output, and a downstream dynamic resource\n\t\t// consuming it can race onto the undefined, producing \"Unexpected struct type\"\n\t\t// during protobuf serialization (Pulumi #16041, #3012).\n\t\tsuper(\n\t\t\tnew NeonRoleResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, password: undefined },\n\t\t\t{ ...opts, additionalSecretOutputs: [\"password\"] },\n\t\t);\n\t}\n}\n\n/** Options type for NeonRole — replaces Pulumi's native `provider` field. */\ntype NeonRoleOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n\n\t/** Neon branch context. */\n\tbranch: NeonBranch;\n};\n\n/** Args for NeonRole. */\nexport interface NeonRoleArgs {\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * When the role is adopted (already exists on the branch), reset its password to a\n\t * fresh value instead of inheriting the parent's. Set this on copy-on-write\n\t * (non-production) branches to isolate the credential from production. Defaults to\n\t * `false` (adopt and reveal the existing password). The reset runs once, at creation.\n\t */\n\tresetPassword?: pulumi.Input<boolean>;\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"owner\", {\n * name: \"neondb_owner\",\n * }, { provider, project, branch });\n * ```\n */\nexport class NeonRole extends pulumi.ComponentResource {\n\t/** Role password (secret). */\n\tpublic readonly password: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonRoleArgs, opts: NeonRoleOptions) {\n\t\tconst { provider, project, branch, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Role\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonRoleResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tname: args.name,\n\t\t\t\tresetPassword: args.resetPassword ?? false,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.password = resource.password;\n\n\t\tthis.registerOutputs({ password: this.password });\n\t}\n}\n"],"mappings":";;;;;;;;AAwDA,eAAe,WACd,QACA,WACA,UACA,MACmB;CAKnB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,OAC7C,GAEc,MAAM,MAAM,MAAM,EAAE,SAAS,IAAI;AAChD;;;;AAKA,eAAe,eACd,QACA,WACA,UACA,MACkB;CAKlB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,SAAS,KAAK,iBAC3D,GAEc;AACf;;;;;AAMA,eAAe,kBACd,QACA,WACA,UACA,MACkB;CAClB,MAAM,SAAS,MAAM,OAAO,KAC3B,aAAa,UAAU,YAAY,SAAS,SAAS,KAAK,kBAC1D,CAAC,CACF;CAEA,MAAM,WAAW,OAAO,MAAM,YAAY,OAAO;CAEjD,IAAI,CAAC,UACJ,MAAM,IAAI,MACT,sDAAsD,KAAK,EAC5D;CAGD,OAAO;AACR;;;;;;;;;AAUA,IAAa,2BAAb,MAEA;CACC,MAAM,OAAO,QAA8D;EAC1E,MAAM,SAAS,IAAI,WAAW,OAAO,MAAM;EAE3C,MAAM,SAAS,MAAM,WACpB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR;EAEA,IAAI,CAAC,QACJ,MAAM,OAAO,KACZ,aAAa,OAAO,UAAU,YAAY,OAAO,SAAS,SAC1D,EAAE,MAAM,EAAE,MAAM,OAAO,KAAK,EAAE,CAC/B;OACM,IAAI,OAAO,eACjB,OAAO,IAAI,KACV,6CAA6C,OAAO,KAAK,uCAC1D;OAEA,OAAO,IAAI,KAAK,gCAAgC,OAAO,KAAK,EAAE;EAG/D,MAAM,WACL,UAAU,OAAO,gBACd,MAAM,kBACN,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR,IACC,MAAM,eACN,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR;EAEH,OAAO;GACN,IAAI,GAAG,OAAO,SAAS,GAAG,OAAO;GACjC,MAAM;IAAE,GAAG;IAAQ;GAAS;EAC7B;CACD;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,WAAW,MAAM,eACtB,IAHkB,WAAW,MAAM,MAG9B,GACL,MAAM,WACN,MAAM,UACN,MAAM,IACP;EAEA,OAAO;GACN;GACA,OAAO;IAAE,GAAG;IAAO;GAAS;EAC7B;CACD;CAEA,MAAM,OAAO,KAAa,OAAuC;EAChE,MAAM,SAAS,IAAI,WAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OACZ,aAAa,MAAM,UAAU,YAAY,MAAM,SAAS,SAAS,MAAM,MACxE;EACD,QAAQ;GACP,OAAO,IAAI,KACV,+BAA+B,MAAM,KAAK,2BAC3C;EACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAGrB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,mBAAN,cAA+B,OAAO,QAAQ,SAAS;CAGtD,YACC,MACA,MAOA,MACC;EAOD,MACC,IAAI,yBAAyB,GAC7B,MACA;GAAE,GAAG;GAAM,UAAU;EAAU,GAC/B;GAAE,GAAG;GAAM,yBAAyB,CAAC,UAAU;EAAE,CAClD;CACD;AACD;;;;;;;;;;;;AAuCA,IAAa,WAAb,cAA8B,OAAO,kBAAkB;CAItD,YAAY,MAAc,MAAoB,MAAuB;EACpE,MAAM,EAAE,UAAU,SAAS,QAAQ,GAAG,eAAe;EAErD,MAAM,wBAAwB,MAAM,CAAC,GAAG,UAAU;EAElD,MAAM,WAAW,IAAI,iBACpB,GAAG,KAAK,YACR;GACC,QAAQ,SAAS;GACjB,WAAW,QAAQ;GACnB,UAAU,OAAO;GACjB,MAAM,KAAK;GACX,eAAe,KAAK,iBAAiB;EACtC,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,WAAW,SAAS;EAEzB,KAAK,gBAAgB,EAAE,UAAU,KAAK,SAAS,CAAC;CACjD;AACD"}
|
|
1
|
+
{"version":3,"file":"role.mjs","names":[],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { NeonBranch } from \"./branch\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n\n\t/**\n\t * When the role already exists (adopted), reset its password to a fresh value\n\t * instead of revealing the inherited one. Use on copy-on-write branches to\n\t * isolate the credential from the parent branch. Ignored when the role is\n\t * freshly created (a new role already gets its own password).\n\t */\n\tresetPassword: boolean;\n\n\t/**\n\t * Rotation handle: bumping this number resets the role's password IN PLACE on\n\t * the next `up` (an update, never a replace — replacing would try to delete\n\t * the role, which Neon refuses for default roles and which would drop real\n\t * grants for others). Leave unset until the first rotation is needed.\n\t */\n\tpasswordVersion?: number;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for the reset_password endpoint (nested under `role`). */\ninterface ResetPasswordResponse {\n\trole?: { password?: string };\n\tpassword?: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Resets an existing role's password to a fresh value and returns it.\n * Used to isolate an adopted (copy-on-write) role from the parent branch's credential.\n */\nasync function resetRolePassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.post<ResetPasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reset_password`,\n\t\t{},\n\t);\n\n\tconst password = result.role?.password ?? result.password;\n\n\tif (!password) {\n\t\tthrow new Error(\n\t\t\t`Neon reset_password returned no password for role \"${name}\"`,\n\t\t);\n\t}\n\n\treturn password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class NeonRoleResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else if (inputs.resetPassword) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Resetting password for adopted Neon role \"${inputs.name}\" to isolate it from the parent branch`,\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password =\n\t\t\texists && inputs.resetPassword\n\t\t\t\t? await resetRolePassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t)\n\t\t\t\t: await revealPassword(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\tinputs.projectId,\n\t\t\t\t\t\tinputs.branchId,\n\t\t\t\t\t\tinputs.name,\n\t\t\t\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Rotates the password in place when `passwordVersion` changes. This is the\n\t * only updatable input (everything else replaces), so an update firing means\n\t * a rotation was requested.\n\t */\n\tasync update(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tlet password = olds.password;\n\n\t\tif (olds.passwordVersion !== news.passwordVersion) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Rotating password for Neon role \"${news.name}\" (passwordVersion ${olds.passwordVersion ?? \"unset\"} → ${news.passwordVersion ?? \"unset\"})`,\n\t\t\t);\n\n\t\t\tconst client = new NeonClient(news.apiKey);\n\n\t\t\tpassword = await resetRolePassword(\n\t\t\t\tclient,\n\t\t\t\tnews.projectId,\n\t\t\t\tnews.branchId,\n\t\t\t\tnews.name,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, password } };\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\tconst rotates = olds.passwordVersion !== news.passwordVersion;\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || rotates,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonRoleResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tbranchId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tresetPassword: pulumi.Input<boolean>;\n\t\t\tpasswordVersion?: pulumi.Input<number>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\t// `password` is an output-only secret. Declaring it via additionalSecretOutputs\n\t\t// (rather than a pulumi.secret(undefined) input placeholder) is the only reliable\n\t\t// way to mark a dynamic-provider output secret: the placeholder gives the property\n\t\t// both a secret-undefined input and a real output, and a downstream dynamic resource\n\t\t// consuming it can race onto the undefined, producing \"Unexpected struct type\"\n\t\t// during protobuf serialization (Pulumi #16041, #3012).\n\t\tsuper(\n\t\t\tnew NeonRoleResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, password: undefined },\n\t\t\t{ ...opts, additionalSecretOutputs: [\"password\"] },\n\t\t);\n\t}\n}\n\n/** Options type for NeonRole — replaces Pulumi's native `provider` field. */\ntype NeonRoleOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n\n\t/** Neon branch context. */\n\tbranch: NeonBranch;\n};\n\n/** Args for NeonRole. */\nexport interface NeonRoleArgs {\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * When the role is adopted (already exists on the branch), reset its password to a\n\t * fresh value instead of inheriting the parent's. Set this on copy-on-write\n\t * (non-production) branches to isolate the credential from production. Defaults to\n\t * `false` (adopt and reveal the existing password). The reset runs once, at creation.\n\t */\n\tresetPassword?: pulumi.Input<boolean>;\n\n\t/**\n\t * Rotation handle: bump to rotate the role's password in place on the next\n\t * `up`. Everything that consumes `password` (connection strings, service env\n\t * vars, dependent redeploys) cascades automatically.\n\t */\n\tpasswordVersion?: pulumi.Input<number>;\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"owner\", {\n * name: \"neondb_owner\",\n * }, { provider, project, branch });\n * ```\n */\nexport class NeonRole extends pulumi.ComponentResource {\n\t/** Role password (secret). */\n\tpublic readonly password: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonRoleArgs, opts: NeonRoleOptions) {\n\t\tconst { provider, project, branch, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Role\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonRoleResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tname: args.name,\n\t\t\t\tresetPassword: args.resetPassword ?? false,\n\t\t\t\tpasswordVersion: args.passwordVersion,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.password = resource.password;\n\n\t\tthis.registerOutputs({ password: this.password });\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infracraft/pulumi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Native Pulumi providers for Railway, Neon, Vercel, and Fly.io with adopt-or-create semantics and deploy orchestration. No Terraform bridge.",
|