@intentius/plywood 0.3.0 → 0.3.2
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/plywood.mjs +49 -16
- package/dist/plywood.mjs.map +2 -2
- package/package.json +7 -7
- package/plywood.schema.json +5 -0
package/dist/plywood.mjs
CHANGED
|
@@ -260,13 +260,16 @@ function approvalOf(doc) {
|
|
|
260
260
|
function gateOf(doc) {
|
|
261
261
|
return typeof doc.gate === "string" && GATES.includes(doc.gate) ? doc.gate : DEFAULT_GATE;
|
|
262
262
|
}
|
|
263
|
+
function previewOf(doc) {
|
|
264
|
+
return doc.preview === true;
|
|
265
|
+
}
|
|
263
266
|
function quorumOf(rules) {
|
|
264
267
|
return Object.entries(rules).map(([glob, r]) => {
|
|
265
268
|
const roles = [...r.role !== void 0 ? [r.role] : [], ...r.roles ?? []];
|
|
266
269
|
return { targets: [glob], count: r.quorum ?? 1, ...roles.length ? { roles } : {}, notAuthor: true };
|
|
267
270
|
});
|
|
268
271
|
}
|
|
269
|
-
var GATES, DEFAULT_GATE, APPROVALS, DEFAULT_APPROVAL, APPROVAL_RULE_KEYS, approval;
|
|
272
|
+
var GATES, DEFAULT_GATE, APPROVALS, DEFAULT_APPROVAL, DEFAULT_PREVIEW, APPROVAL_RULE_KEYS, approval;
|
|
270
273
|
var init_approval = __esm({
|
|
271
274
|
"src/config/approval.ts"() {
|
|
272
275
|
"use strict";
|
|
@@ -275,13 +278,19 @@ var init_approval = __esm({
|
|
|
275
278
|
DEFAULT_GATE = "on-destructive";
|
|
276
279
|
APPROVALS = ["ledger", "sealed", "pr-review"];
|
|
277
280
|
DEFAULT_APPROVAL = "ledger";
|
|
281
|
+
DEFAULT_PREVIEW = false;
|
|
278
282
|
APPROVAL_RULE_KEYS = ["role", "roles", "quorum"];
|
|
279
283
|
approval = {
|
|
280
284
|
name: "approval",
|
|
281
|
-
keys: ["gate", "approval", "approval_rules"],
|
|
285
|
+
keys: ["gate", "approval", "approval_rules", "preview"],
|
|
282
286
|
schema: {
|
|
283
287
|
gate: { type: "string", enum: [...GATES], default: DEFAULT_GATE, description: "always, on-destructive or never." },
|
|
284
288
|
approval: { type: "string", enum: [...APPROVALS], default: DEFAULT_APPROVAL, description: "ledger, sealed or pr-review." },
|
|
289
|
+
preview: {
|
|
290
|
+
type: "boolean",
|
|
291
|
+
default: DEFAULT_PREVIEW,
|
|
292
|
+
description: "true: plywood init sets approvals.preview in the waves spec, so a merged wave applies only the plans its pull request previewed, or exits 4. Known limits: plywood#55 (the second of two disjoint merges is refused) and plywood#56 (a pull request that creates an autonamed resource is refused)."
|
|
293
|
+
},
|
|
285
294
|
approval_rules: {
|
|
286
295
|
type: "object",
|
|
287
296
|
description: "Role and quorum per stack glob. Read at base.",
|
|
@@ -298,6 +307,7 @@ var init_approval = __esm({
|
|
|
298
307
|
},
|
|
299
308
|
check(doc) {
|
|
300
309
|
const out = [...oneOf(doc.gate, GATES, "gate"), ...oneOf(doc.approval, APPROVALS, "approval")];
|
|
310
|
+
if (doc.preview !== void 0 && typeof doc.preview !== "boolean") out.push(error("preview", "must be true or false"));
|
|
301
311
|
const rules = doc.approval_rules;
|
|
302
312
|
if (rules === void 0) return out;
|
|
303
313
|
if (!isObject(rules)) return [...out, error("approval_rules", "must map a stack glob to { role, quorum }")];
|
|
@@ -422,7 +432,7 @@ var init_words = __esm({
|
|
|
422
432
|
"src/words.ts"() {
|
|
423
433
|
"use strict";
|
|
424
434
|
PRODUCT = "plywood";
|
|
425
|
-
TAGLINE = "
|
|
435
|
+
TAGLINE = "Pulumi ops and governance.";
|
|
426
436
|
OP_NAME = "pulumi-up";
|
|
427
437
|
PR_GATE = "up";
|
|
428
438
|
VERBS = [
|
|
@@ -587,13 +597,13 @@ var init_control = __esm({
|
|
|
587
597
|
var version, dependencies;
|
|
588
598
|
var init_package = __esm({
|
|
589
599
|
"package.json"() {
|
|
590
|
-
version = "0.3.
|
|
600
|
+
version = "0.3.2";
|
|
591
601
|
dependencies = {
|
|
592
|
-
"@intentius/chant": "0.124.
|
|
593
|
-
"@intentius/chant-lexicon-forgejo": "0.124.
|
|
594
|
-
"@intentius/chant-lexicon-github": "0.124.
|
|
595
|
-
"@intentius/chant-lexicon-gitlab": "0.124.
|
|
596
|
-
"@intentius/chant-lexicon-pulumi": "0.124.
|
|
602
|
+
"@intentius/chant": "0.124.67",
|
|
603
|
+
"@intentius/chant-lexicon-forgejo": "0.124.67",
|
|
604
|
+
"@intentius/chant-lexicon-github": "0.124.67",
|
|
605
|
+
"@intentius/chant-lexicon-gitlab": "0.124.67",
|
|
606
|
+
"@intentius/chant-lexicon-pulumi": "0.124.67"
|
|
597
607
|
};
|
|
598
608
|
}
|
|
599
609
|
});
|
|
@@ -603,8 +613,8 @@ var image_digests_default;
|
|
|
603
613
|
var init_image_digests = __esm({
|
|
604
614
|
"src/image-digests.json"() {
|
|
605
615
|
image_digests_default = {
|
|
606
|
-
"ghcr.io/intentius/plywood-pulumi:0.3.
|
|
607
|
-
"ghcr.io/intentius/plywood-plunge:0.3.
|
|
616
|
+
"ghcr.io/intentius/plywood-pulumi:0.3.2-pulumi3.272.0": "sha256:eeb44ac18dc594acd8a56dfc1a6ed00751154f6b6bfba8bbdff97a513f4b9f84",
|
|
617
|
+
"ghcr.io/intentius/plywood-plunge:0.3.2-plunge0.2.0": "sha256:5239ffabd5f5b7a4ffc5cbbe1628a34990bc95549cd4a1bef27953d3dfb7d8a9"
|
|
608
618
|
};
|
|
609
619
|
}
|
|
610
620
|
});
|
|
@@ -2458,7 +2468,7 @@ var init_chant_product = __esm({
|
|
|
2458
2468
|
"src/chant-product.ts"() {
|
|
2459
2469
|
"use strict";
|
|
2460
2470
|
REQUIRED2 = ["assertProductWords", "productPipelineJobs", "approveLine", "runChantInProcess"];
|
|
2461
|
-
BUILT_CHANT = true ? "0.124.
|
|
2471
|
+
BUILT_CHANT = true ? "0.124.67" : "unknown";
|
|
2462
2472
|
}
|
|
2463
2473
|
});
|
|
2464
2474
|
|
|
@@ -3493,6 +3503,7 @@ function wavesSpec(input) {
|
|
|
3493
3503
|
step: { kind: "pulumi-up" },
|
|
3494
3504
|
...input.quorum && input.quorum.length > 0 ? { approvals: { quorum: input.quorum } } : {},
|
|
3495
3505
|
...input.notify ? { notify: input.notify } : {},
|
|
3506
|
+
...input.policy ? { holds: { policy: true } } : {},
|
|
3496
3507
|
waves: waves.map((w) => {
|
|
3497
3508
|
const gate = w.stacks.map((s) => s.gate ?? input.gate).reduce((a, b) => STRICTNESS[b] > STRICTNESS[a] ? b : a);
|
|
3498
3509
|
return { name: w.name, gate, runs: w.stacks.map((s) => ({ target: s.key, input: stepInput(s) })) };
|
|
@@ -3989,7 +4000,10 @@ async function drift2(args, json, table, io) {
|
|
|
3989
4000
|
try {
|
|
3990
4001
|
const attribute = await attributionFor(doc, io);
|
|
3991
4002
|
const root = dirname6(path);
|
|
3992
|
-
for (const stack of stacks2)
|
|
4003
|
+
for (const stack of stacks2) {
|
|
4004
|
+
const backend = stacksOf(doc)[stack]?.backend ?? (typeof doc.backend === "string" ? doc.backend : void 0);
|
|
4005
|
+
results.push(await withBackend(backend, () => readStack(chant, stack, driftMember(root, doc, stack), dir, { resource: resource.value, env: envFlag.value, ...attribute ? { attribute } : {} })));
|
|
4006
|
+
}
|
|
3993
4007
|
} finally {
|
|
3994
4008
|
rmSync(dir, { recursive: true, force: true });
|
|
3995
4009
|
}
|
|
@@ -4096,9 +4110,26 @@ async function readStack(chant, stack, member, dir, opts) {
|
|
|
4096
4110
|
return { stack, ok: false, drifted: 0, rows: [], error: doc?.error ? `${doc.error.code}: ${doc.error.message}` : `chant workspace drift exited ${code}` };
|
|
4097
4111
|
}
|
|
4098
4112
|
const rows = driftRows(doc);
|
|
4113
|
+
const unread = (doc.records ?? []).filter((r) => r.status === "not-observed");
|
|
4114
|
+
if (rows.length === 0 && unread.length > 0) {
|
|
4115
|
+
const first = unread[0];
|
|
4116
|
+
const why = first.unobserved?.detail ?? first.unobserved?.reason ?? "no reason given";
|
|
4117
|
+
return { stack, ok: false, drifted: 0, rows: [], error: `${unread.length} resource${unread.length === 1 ? "" : "s"} could not be read, so the stack's drift is unknown: ${first.address}: ${why}` };
|
|
4118
|
+
}
|
|
4099
4119
|
const attribution = rows.length && opts.attribute ? await attributeStack(opts.attribute, stack, doc) : void 0;
|
|
4100
4120
|
return { stack, ok: true, drifted: rows.length, rows, observedAt: observedAt(doc), ...attribution ? { attribution } : {} };
|
|
4101
4121
|
}
|
|
4122
|
+
async function withBackend(backend, read) {
|
|
4123
|
+
if (backend === void 0) return read();
|
|
4124
|
+
const before = process.env.PULUMI_BACKEND_URL;
|
|
4125
|
+
process.env.PULUMI_BACKEND_URL = backend;
|
|
4126
|
+
try {
|
|
4127
|
+
return await read();
|
|
4128
|
+
} finally {
|
|
4129
|
+
if (before === void 0) delete process.env.PULUMI_BACKEND_URL;
|
|
4130
|
+
else process.env.PULUMI_BACKEND_URL = before;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4102
4133
|
async function attributionFor(doc, io) {
|
|
4103
4134
|
const logs = driftSettingsOf(doc).auditLogs;
|
|
4104
4135
|
if (!logs) return void 0;
|
|
@@ -5729,7 +5760,7 @@ async function runInit(opts, table, io) {
|
|
|
5729
5760
|
if (opts.bucketCheck) notes.push(...await bucketStep(detected2, doc, io.env, buckets));
|
|
5730
5761
|
adopt.push(...await adoptStep(root, detected2, doc, io.env, declaration.doc, opts.dryRun, files, notes, todo));
|
|
5731
5762
|
todo.push(...todoLines(detected2, doc, forge.kind, io.env));
|
|
5732
|
-
if (forge.kind === "gitlab" && chant && stacks2.length > 0 && await hasChantWavePreview()) {
|
|
5763
|
+
if (forge.kind === "gitlab" && chant && stacks2.length > 0 && previewOf(doc) && await hasChantWavePreview()) {
|
|
5733
5764
|
todo.push(`Add a ${GITLAB_READ_TOKEN} CI/CD variable (a project access token with the read_api scope, masked and protected) so a wave can read the merge request it applies; without it the wave refuses`);
|
|
5734
5765
|
}
|
|
5735
5766
|
const failed = files.find((f) => f.status === "failed");
|
|
@@ -5798,7 +5829,9 @@ async function preCommitStep(root, config2, dryRun) {
|
|
|
5798
5829
|
}
|
|
5799
5830
|
async function wavesSpecStep(chant, stacks2, doc, settings, branch, ordered, forge) {
|
|
5800
5831
|
const runner = waveRunner(chantPipelineOptions(settings.pipeline, false).runner);
|
|
5832
|
+
const rego = isObject(doc.policy) && (typeof doc.policy.rego === "string" || doc.policy.source !== void 0 || doc.policy.command !== void 0);
|
|
5801
5833
|
const base = wavesSpec({
|
|
5834
|
+
...rego ? { policy: true } : {},
|
|
5802
5835
|
stacks: stacks2,
|
|
5803
5836
|
ordered,
|
|
5804
5837
|
gate: gateOf(doc),
|
|
@@ -5809,7 +5842,7 @@ async function wavesSpecStep(chant, stacks2, doc, settings, branch, ordered, for
|
|
|
5809
5842
|
...settings.pipeline.notify ? { notify: { ...notifyForChant(settings.pipeline.notify, chant) } } : {}
|
|
5810
5843
|
});
|
|
5811
5844
|
const named = chant.productWavesSpec ? chant.productWavesSpec(base, PRODUCT_WORDS) : base;
|
|
5812
|
-
return await hasChantWavePreview() ? withWavePreview(named, forge) : named;
|
|
5845
|
+
return previewOf(doc) && await hasChantWavePreview() ? withWavePreview(named, forge) : named;
|
|
5813
5846
|
}
|
|
5814
5847
|
async function workflowStep(chant, root, doc, settings, forge, opts, spec, table, io) {
|
|
5815
5848
|
const output = WORKFLOW_FILE[forge];
|
|
@@ -7918,7 +7951,7 @@ var execChild = (bin, args, opts) => new Promise((done) => {
|
|
|
7918
7951
|
|
|
7919
7952
|
// src/cli.ts
|
|
7920
7953
|
init_words();
|
|
7921
|
-
var VERSION3 = true ? "0.3.
|
|
7954
|
+
var VERSION3 = true ? "0.3.2" : "0.0.0-dev";
|
|
7922
7955
|
var stdio = {
|
|
7923
7956
|
out: (s) => process.stdout.write(s + "\n"),
|
|
7924
7957
|
err: (s) => process.stderr.write(s + "\n")
|