@intentius/chant-lexicon-pulumi 0.124.54 → 0.124.56
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/README.md +42 -3
- package/dist/adopt.d.ts +68 -0
- package/dist/adopt.d.ts.map +1 -0
- package/dist/adopt.js +162 -0
- package/dist/capability-plugin.d.ts +3 -0
- package/dist/capability-plugin.d.ts.map +1 -1
- package/dist/capability-plugin.js +8 -1
- package/dist/change-set.d.ts +27 -4
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +15 -5
- package/dist/check-step.d.ts +61 -0
- package/dist/check-step.d.ts.map +1 -0
- package/dist/check-step.js +270 -0
- package/dist/ci-setup.d.ts +53 -0
- package/dist/ci-setup.d.ts.map +1 -0
- package/dist/ci-setup.js +146 -0
- package/dist/composites/pulumi-adopt-op.d.ts +106 -0
- package/dist/composites/pulumi-adopt-op.d.ts.map +1 -0
- package/dist/composites/pulumi-adopt-op.js +132 -0
- package/dist/engine-events.d.ts +222 -0
- package/dist/engine-events.d.ts.map +1 -0
- package/dist/engine-events.js +280 -0
- package/dist/exit-codes.d.ts +53 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +67 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -2
- package/dist/init-templates.d.ts.map +1 -1
- package/dist/init-templates.js +5 -3
- package/dist/integrity.json +3 -3
- package/dist/lint/post-synth/pul015.d.ts +2 -1
- package/dist/lint/post-synth/pul015.d.ts.map +1 -1
- package/dist/lock-rows.d.ts +32 -0
- package/dist/lock-rows.d.ts.map +1 -0
- package/dist/lock-rows.js +36 -0
- package/dist/manifest.json +1 -1
- package/dist/member-engine.d.ts +27 -2
- package/dist/member-engine.d.ts.map +1 -1
- package/dist/member-engine.js +58 -7
- package/dist/member-outputs.d.ts +10 -3
- package/dist/member-outputs.d.ts.map +1 -1
- package/dist/member-outputs.js +15 -7
- package/dist/op/activities/index.d.ts +9 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +8 -1
- package/dist/op/activities/pulumi-adopt.d.ts +104 -0
- package/dist/op/activities/pulumi-adopt.d.ts.map +1 -0
- package/dist/op/activities/pulumi-adopt.js +216 -0
- package/dist/op/activities/pulumi.d.ts.map +1 -1
- package/dist/op/activities/pulumi.js +2 -1
- package/dist/op/activity-contracts.d.ts +73 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +49 -0
- package/dist/op/builders.d.ts +9 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +20 -0
- package/dist/plan-shape.d.ts +47 -0
- package/dist/plan-shape.d.ts.map +1 -0
- package/dist/plan-shape.js +94 -0
- package/dist/planned-output-changes.d.ts +60 -0
- package/dist/planned-output-changes.d.ts.map +1 -0
- package/dist/planned-output-changes.js +94 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +38 -4
- package/dist/plunge-timings.d.ts +43 -0
- package/dist/plunge-timings.d.ts.map +1 -0
- package/dist/plunge-timings.js +106 -0
- package/dist/preview-tips.d.ts +71 -0
- package/dist/preview-tips.d.ts.map +1 -0
- package/dist/preview-tips.js +76 -0
- package/dist/pulumi-up.d.ts +384 -0
- package/dist/pulumi-up.d.ts.map +1 -0
- package/dist/pulumi-up.js +836 -0
- package/dist/release-copy.d.ts +47 -0
- package/dist/release-copy.d.ts.map +1 -0
- package/dist/release-copy.js +131 -0
- package/dist/review-stack.d.ts +92 -0
- package/dist/review-stack.d.ts.map +1 -0
- package/dist/review-stack.js +222 -0
- package/dist/rules/pul015.ts +2 -1
- package/dist/run.d.ts +269 -7
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +473 -26
- package/dist/stack-export-snapshot.d.ts +125 -0
- package/dist/stack-export-snapshot.d.ts.map +1 -0
- package/dist/stack-export-snapshot.js +233 -0
- package/dist/stack-ops.d.ts +175 -0
- package/dist/stack-ops.d.ts.map +1 -0
- package/dist/stack-ops.js +400 -0
- package/dist/stack-references.d.ts +46 -0
- package/dist/stack-references.d.ts.map +1 -0
- package/dist/stack-references.js +88 -0
- package/dist/trace-facts.d.ts +34 -0
- package/dist/trace-facts.d.ts.map +1 -0
- package/dist/trace-facts.js +81 -0
- package/dist/workspace-member.d.ts +13 -2
- package/dist/workspace-member.d.ts.map +1 -1
- package/dist/workspace-member.js +14 -9
- package/package.json +2 -2
- package/src/adopt.ts +202 -0
- package/src/capability-plugin.ts +15 -6
- package/src/change-set.ts +29 -5
- package/src/check-step.test.ts +191 -0
- package/src/check-step.ts +316 -0
- package/src/ci-setup.test.ts +123 -0
- package/src/ci-setup.ts +176 -0
- package/src/composites/pulumi-adopt-op.test.ts +223 -0
- package/src/composites/pulumi-adopt-op.ts +191 -0
- package/src/composites/pulumi-watch-op.test.ts +2 -2
- package/src/engine-events.test.ts +134 -0
- package/src/engine-events.ts +390 -0
- package/src/engine-graph.test.ts +1 -1
- package/src/exit-codes.test.ts +21 -0
- package/src/exit-codes.ts +71 -0
- package/src/index.ts +77 -1
- package/src/init-templates.ts +5 -3
- package/src/lint/post-synth/pul015.ts +2 -1
- package/src/lock-rows.test.ts +60 -0
- package/src/lock-rows.ts +56 -0
- package/src/member-engine.test.ts +112 -0
- package/src/member-engine.ts +73 -6
- package/src/member-outputs.test.ts +30 -8
- package/src/member-outputs.ts +31 -12
- package/src/op/activities/index.ts +19 -1
- package/src/op/activities/pulumi-adopt.ts +334 -0
- package/src/op/activities/pulumi.ts +2 -1
- package/src/op/activity-contracts.ts +75 -0
- package/src/op/builders.ts +37 -0
- package/src/plan-shape.test.ts +91 -0
- package/src/plan-shape.ts +105 -0
- package/src/planned-output-changes.test.ts +85 -0
- package/src/planned-output-changes.ts +141 -0
- package/src/plugin.test.ts +1 -1
- package/src/plugin.ts +42 -4
- package/src/plunge-timings.test.ts +241 -0
- package/src/plunge-timings.ts +115 -0
- package/src/plunge-up-failed.test.ts +54 -0
- package/src/pr-note.test.ts +87 -0
- package/src/preview-tips.test.ts +135 -0
- package/src/preview-tips.ts +128 -0
- package/src/pulumi-up.test.ts +470 -0
- package/src/pulumi-up.ts +1053 -0
- package/src/release-copy.test.ts +74 -0
- package/src/release-copy.ts +142 -0
- package/src/review-stack.test.ts +254 -0
- package/src/review-stack.ts +266 -0
- package/src/run-plan-flags.test.ts +53 -0
- package/src/run.test.ts +176 -3
- package/src/run.ts +544 -30
- package/src/stack-export-snapshot.test.ts +190 -0
- package/src/stack-export-snapshot.ts +312 -0
- package/src/stack-ops.test.ts +286 -0
- package/src/stack-ops.ts +552 -0
- package/src/stack-references.test.ts +96 -0
- package/src/stack-references.ts +110 -0
- package/src/testdata/pulumi-stream/README.md +16 -0
- package/src/testdata/pulumi-stream/preview-stream.ndjson +42 -0
- package/src/testdata/pulumi-stream/up-failed-plunge.ndjson +5 -0
- package/src/trace-facts.test.ts +38 -0
- package/src/trace-facts.ts +83 -0
- package/src/workspace-member.test.ts +2 -1
- package/src/workspace-member.ts +30 -12
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pulumi lexicon's steps in chant's check stage (#4633). `chant ci
|
|
3
|
+
* check` runs them after the `ci.hooks.check` hooks, for each workspace
|
|
4
|
+
* member of kind `pulumi` (`LexiconPlugin.checkSteps`,
|
|
5
|
+
* `@intentius/chant/components/check-steps`), so a product's check
|
|
6
|
+
* (`plywood check`) gets them through chant with nothing of its own:
|
|
7
|
+
*
|
|
8
|
+
* - `pulumi-install`: `pulumi install` (`plunge install` for a member on
|
|
9
|
+
* plunge, `./member-engine.ts`) in the member's directory, which fetches
|
|
10
|
+
* the program's dependencies and plugins the way `Pulumi.yaml` says;
|
|
11
|
+
* - `stack-config`: each stack's `Pulumi.<stack>.yaml` checked against the
|
|
12
|
+
* project's config schema in `Pulumi.yaml`, with no command run (a key
|
|
13
|
+
* with a type and no default must be set, a value must read as its type,
|
|
14
|
+
* and a `secret: true` key must be encrypted). The stacks are the ones the
|
|
15
|
+
* project lists by their files, plus the member's declared stack;
|
|
16
|
+
* - `pulumi-about`: `pulumi about --json`, which reports the engine, its
|
|
17
|
+
* plugins and the backend for the report.
|
|
18
|
+
*
|
|
19
|
+
* The argv go through `runCheckPulumi` (`./run.ts`), which refuses any
|
|
20
|
+
* other. The environment is the check job's ({@link CheckStepsContext.env}):
|
|
21
|
+
* no forge token, no OIDC request variables, no cloud credential, so neither
|
|
22
|
+
* command can write a stack. A failure names the member and stack, and the
|
|
23
|
+
* check exits with chant's `failed` code.
|
|
24
|
+
*
|
|
25
|
+
* The runner is injectable, so the tests run no binary.
|
|
26
|
+
*/
|
|
27
|
+
import { execFile } from "node:child_process";
|
|
28
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { promisify } from "node:util";
|
|
31
|
+
import { parseYAML } from "@intentius/chant/yaml";
|
|
32
|
+
import { pulumiMemberEngine } from "./member-engine.js";
|
|
33
|
+
import { engineEnv, pulumiRunFailure, runCheckPulumi } from "./run.js";
|
|
34
|
+
const execFileAsync = promisify(execFile);
|
|
35
|
+
/** The lexicon name the report carries. */
|
|
36
|
+
const LEXICON = "pulumi";
|
|
37
|
+
/** A runner for `bin` with exactly `env` (the check job's), never the process's whole environment. */
|
|
38
|
+
export function checkRunner(bin, env) {
|
|
39
|
+
return (args, opts) => execFileAsync(bin, args, { cwd: opts.cwd, maxBuffer: 64 * 1024 * 1024, env: engineEnv({ ...env, ...opts.env }) });
|
|
40
|
+
}
|
|
41
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
42
|
+
/** The key in its full form: a bare name belongs to the project. */
|
|
43
|
+
function fullKey(project, key) {
|
|
44
|
+
return key.includes(":") ? key : `${project}:${key}`;
|
|
45
|
+
}
|
|
46
|
+
/** Read `Pulumi.yaml`'s schema from its parsed document. A short-form entry (`key: value`) is a value with no type. */
|
|
47
|
+
export function pulumiProjectSchema(doc) {
|
|
48
|
+
const d = isRecord(doc) ? doc : {};
|
|
49
|
+
const project = typeof d.name === "string" ? d.name : "";
|
|
50
|
+
const config = isRecord(d.config) ? d.config : {};
|
|
51
|
+
const keys = [];
|
|
52
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
53
|
+
const key = fullKey(project, name);
|
|
54
|
+
const schema = isRecord(entry) && ("type" in entry || "default" in entry || "value" in entry || "secret" in entry || "description" in entry || "items" in entry);
|
|
55
|
+
if (!schema) {
|
|
56
|
+
keys.push({ key, secret: false, hasDefault: true });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const e = entry;
|
|
60
|
+
keys.push({
|
|
61
|
+
key,
|
|
62
|
+
...(typeof e.type === "string" ? { type: e.type } : {}),
|
|
63
|
+
...(isRecord(e.items) && typeof e.items.type === "string" ? { items: { type: e.items.type } } : {}),
|
|
64
|
+
secret: e.secret === true,
|
|
65
|
+
hasDefault: "default" in e || "value" in e,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return { project, keys, stackConfigDir: typeof d.stackConfigDir === "string" && d.stackConfigDir.length > 0 ? d.stackConfigDir : "." };
|
|
69
|
+
}
|
|
70
|
+
const isSecure = (v) => isRecord(v) && typeof v.secure === "string";
|
|
71
|
+
/** Whether a scalar stack value reads as `type`. Values are strings once Pulumi reads them, so `"3"` is an integer. */
|
|
72
|
+
function readsAs(type, value) {
|
|
73
|
+
switch (type) {
|
|
74
|
+
case "string":
|
|
75
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
76
|
+
case "integer":
|
|
77
|
+
return (typeof value === "number" && Number.isInteger(value)) || (typeof value === "string" && /^-?[0-9]+$/.test(value.trim()));
|
|
78
|
+
case "boolean":
|
|
79
|
+
return typeof value === "boolean" || value === "true" || value === "false";
|
|
80
|
+
case "array":
|
|
81
|
+
return Array.isArray(value);
|
|
82
|
+
default:
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* What is wrong with one stack's config (`config:` of its
|
|
88
|
+
* `Pulumi.<stack>.yaml`, parsed) against the project's schema, as lines
|
|
89
|
+
* naming the stack and key. Empty when it passes. Keys of other namespaces
|
|
90
|
+
* (a provider's `aws:region`) are not checked: the project declares no type
|
|
91
|
+
* for them.
|
|
92
|
+
*/
|
|
93
|
+
export function pulumiStackConfigProblems(schema, stack, stackDoc) {
|
|
94
|
+
const raw = isRecord(stackDoc) && isRecord(stackDoc.config) ? stackDoc.config : {};
|
|
95
|
+
const values = new Map();
|
|
96
|
+
for (const [k, v] of Object.entries(raw))
|
|
97
|
+
values.set(fullKey(schema.project, k), v);
|
|
98
|
+
const problems = [];
|
|
99
|
+
for (const k of schema.keys) {
|
|
100
|
+
if (!k.type && !k.secret)
|
|
101
|
+
continue;
|
|
102
|
+
if (!values.has(k.key)) {
|
|
103
|
+
if (k.type && !k.hasDefault)
|
|
104
|
+
problems.push(`stack ${stack} is missing the configuration value ${k.key} (type ${k.type}, no default)`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const value = values.get(k.key);
|
|
108
|
+
if (k.secret && !isSecure(value)) {
|
|
109
|
+
problems.push(`stack ${stack} sets ${k.key} in plain text, and the project declares it secret; set it with pulumi config set --secret`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (isSecure(value) || !k.type)
|
|
113
|
+
continue;
|
|
114
|
+
if (!readsAs(k.type, value)) {
|
|
115
|
+
problems.push(`stack ${stack} sets ${k.key} to ${JSON.stringify(value)}, which is not of type ${k.type}`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (k.type === "array" && k.items?.type && Array.isArray(value)) {
|
|
119
|
+
const bad = value.findIndex((item) => !isSecure(item) && !readsAs(k.items.type, item));
|
|
120
|
+
if (bad >= 0)
|
|
121
|
+
problems.push(`stack ${stack} sets ${k.key}[${bad}] to ${JSON.stringify(value[bad])}, which is not of type ${k.items.type}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return problems;
|
|
125
|
+
}
|
|
126
|
+
/** The project's file in `dir`, `Pulumi.yaml` or `Pulumi.yml`. */
|
|
127
|
+
function projectFile(dir) {
|
|
128
|
+
return ["Pulumi.yaml", "Pulumi.yml"].map((f) => join(dir, f)).find((f) => existsSync(f));
|
|
129
|
+
}
|
|
130
|
+
/** The stacks the project lists by their `Pulumi.<stack>.yaml` files in `dir`, sorted. */
|
|
131
|
+
export function listedStacks(dir) {
|
|
132
|
+
if (!existsSync(dir))
|
|
133
|
+
return [];
|
|
134
|
+
return readdirSync(dir)
|
|
135
|
+
.map((f) => /^Pulumi\.(.+)\.ya?ml$/.exec(f)?.[1])
|
|
136
|
+
.filter((s) => s !== undefined)
|
|
137
|
+
.sort();
|
|
138
|
+
}
|
|
139
|
+
/** The stack file for `stack` in `dir`, if there is one. */
|
|
140
|
+
function stackFile(dir, stack) {
|
|
141
|
+
return [`Pulumi.${stack}.yaml`, `Pulumi.${stack}.yml`].map((f) => join(dir, f)).find((f) => existsSync(f));
|
|
142
|
+
}
|
|
143
|
+
/** Read `pulumi about --json`'s document. Undefined when it is not JSON. */
|
|
144
|
+
export function pulumiAboutDetail(engine, stdout) {
|
|
145
|
+
if (!stdout)
|
|
146
|
+
return undefined;
|
|
147
|
+
let doc;
|
|
148
|
+
try {
|
|
149
|
+
doc = JSON.parse(stdout);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
if (!isRecord(doc))
|
|
155
|
+
return undefined;
|
|
156
|
+
const cli = isRecord(doc.cli) ? doc.cli : {};
|
|
157
|
+
const plugins = Array.isArray(doc.plugins)
|
|
158
|
+
? doc.plugins.filter(isRecord).map((p) => ({ name: String(p.name ?? ""), ...(typeof p.version === "string" ? { version: p.version } : {}) })).filter((p) => p.name)
|
|
159
|
+
: [];
|
|
160
|
+
const b = isRecord(doc.backend) ? doc.backend : undefined;
|
|
161
|
+
const backend = b
|
|
162
|
+
? { ...(typeof b.name === "string" ? { name: b.name } : {}), ...(typeof b.url === "string" ? { url: b.url } : {}), ...(typeof b.user === "string" ? { user: b.user } : {}) }
|
|
163
|
+
: undefined;
|
|
164
|
+
return { engine, ...(typeof cli.version === "string" ? { version: cli.version } : {}), plugins, ...(backend ? { backend } : {}) };
|
|
165
|
+
}
|
|
166
|
+
// ── The steps ───────────────────────────────────────────────────────
|
|
167
|
+
/** The last lines of what a failed run printed, for its reason. */
|
|
168
|
+
function tail(text) {
|
|
169
|
+
const lines = (text ?? "").trim().split("\n").filter(Boolean);
|
|
170
|
+
return lines.slice(-3).join(" | ");
|
|
171
|
+
}
|
|
172
|
+
/** The steps for one member, in order: install, each stack's config, about. */
|
|
173
|
+
export async function pulumiMemberCheck(member, ctx, opts = {}) {
|
|
174
|
+
const fields = member.fields ?? {};
|
|
175
|
+
const declaredEngine = fields.engine === "pulumi" || fields.engine === "plunge" ? fields.engine : undefined;
|
|
176
|
+
const declaredStack = typeof fields.stack === "string" && fields.stack.length > 0 ? fields.stack : undefined;
|
|
177
|
+
const { engine } = pulumiMemberEngine(member.dir, declaredEngine, { env: ctx.env });
|
|
178
|
+
const run = (opts.runner ?? checkRunner)(engine, ctx.env);
|
|
179
|
+
const out = [];
|
|
180
|
+
const base = { lexicon: LEXICON, member: member.name };
|
|
181
|
+
const file = projectFile(member.dir);
|
|
182
|
+
if (!file) {
|
|
183
|
+
out.push({ ...base, step: "pulumi-install", exit: 1, outcome: "failed", reason: `member ${member.name} has no Pulumi.yaml in ${member.dir}` });
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
// pulumi install
|
|
187
|
+
let installed = true;
|
|
188
|
+
try {
|
|
189
|
+
await runCheckPulumi(run, ["install"], member.dir);
|
|
190
|
+
out.push({ ...base, step: "pulumi-install", exit: 0, outcome: "passed" });
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
installed = false;
|
|
194
|
+
const f = pulumiRunFailure(err);
|
|
195
|
+
const why = f.missingBinary ? `${engine} is not installed in the check job` : tail(f.stderr) || tail(f.stdout) || f.message;
|
|
196
|
+
out.push({ ...base, step: "pulumi-install", exit: f.exitCode ?? 1, outcome: "failed", reason: `${engine} install failed for member ${member.name}: ${why}` });
|
|
197
|
+
}
|
|
198
|
+
// Stack config, run by nothing but this module.
|
|
199
|
+
let schema;
|
|
200
|
+
try {
|
|
201
|
+
schema = pulumiProjectSchema(parseYAML(readFileSync(file, "utf-8")));
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
out.push({ ...base, step: "stack-config", exit: 1, outcome: "failed", reason: `member ${member.name}: ${file} does not read as YAML (${err instanceof Error ? err.message : String(err)})` });
|
|
205
|
+
}
|
|
206
|
+
if (schema) {
|
|
207
|
+
const stackDir = join(member.dir, schema.stackConfigDir);
|
|
208
|
+
const stacks = [...new Set([...listedStacks(stackDir), ...(declaredStack ? [declaredStack] : [])])].sort();
|
|
209
|
+
if (stacks.length === 0)
|
|
210
|
+
out.push({ ...base, step: "stack-config", exit: 0, outcome: "skipped", reason: `member ${member.name} lists no stack (no Pulumi.<stack>.yaml in ${stackDir})` });
|
|
211
|
+
for (const stack of stacks) {
|
|
212
|
+
const at = stackFile(stackDir, stack);
|
|
213
|
+
let doc = {};
|
|
214
|
+
if (at) {
|
|
215
|
+
try {
|
|
216
|
+
doc = parseYAML(readFileSync(at, "utf-8"));
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
out.push({ ...base, step: "stack-config", stack, exit: 1, outcome: "failed", reason: `member ${member.name}, stack ${stack}: ${at} does not read as YAML (${err instanceof Error ? err.message : String(err)})` });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const problems = pulumiStackConfigProblems(schema, stack, doc);
|
|
224
|
+
out.push(problems.length === 0
|
|
225
|
+
? { ...base, step: "stack-config", stack, exit: 0, outcome: "passed" }
|
|
226
|
+
: { ...base, step: "stack-config", stack, exit: 1, outcome: "failed", reason: `member ${member.name}: ${problems.join("; ")}`, detail: { problems } });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// pulumi about --json
|
|
230
|
+
if (!installed) {
|
|
231
|
+
out.push({ ...base, step: "pulumi-about", exit: 0, outcome: "skipped", reason: `member ${member.name}: ${engine} install failed, so about did not run` });
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
const aboutArgs = declaredStack ? ["about", "--json", "--stack", declaredStack] : ["about", "--json"];
|
|
235
|
+
const stackPart = declaredStack ? { stack: declaredStack } : {};
|
|
236
|
+
try {
|
|
237
|
+
const { stdout } = await runCheckPulumi(run, aboutArgs, member.dir);
|
|
238
|
+
const detail = pulumiAboutDetail(engine, stdout);
|
|
239
|
+
out.push(detail
|
|
240
|
+
? { ...base, step: "pulumi-about", ...stackPart, exit: 0, outcome: "passed", detail }
|
|
241
|
+
: { ...base, step: "pulumi-about", ...stackPart, exit: 1, outcome: "failed", reason: `${engine} about --json for member ${member.name} printed no JSON` });
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
const f = pulumiRunFailure(err);
|
|
245
|
+
// about exits non-zero when one of its reads fails (no backend login in a check job), and still prints what it read.
|
|
246
|
+
const detail = pulumiAboutDetail(engine, f.stdout);
|
|
247
|
+
if (detail) {
|
|
248
|
+
ctx.log(`pulumi check: ${engine} about for ${member.name} exited ${f.exitCode ?? 1} and reported: ${tail(f.stderr) || "no reason"}`);
|
|
249
|
+
out.push({ ...base, step: "pulumi-about", ...stackPart, exit: f.exitCode ?? 1, outcome: "passed", detail });
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const why = f.missingBinary ? `${engine} is not installed in the check job` : tail(f.stderr) || f.message;
|
|
253
|
+
out.push({ ...base, step: "pulumi-about", ...stackPart, exit: f.exitCode ?? 1, outcome: "failed", reason: `${engine} about failed for member ${member.name}${declaredStack ? `, stack ${declaredStack}` : ""}: ${why}` });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
/** The pulumi lexicon's check steps, for `LexiconPlugin.checkSteps`. */
|
|
259
|
+
export function pulumiCheckSteps(opts = {}) {
|
|
260
|
+
return {
|
|
261
|
+
lexicon: LEXICON,
|
|
262
|
+
kinds: ["pulumi"],
|
|
263
|
+
async run(ctx) {
|
|
264
|
+
const out = [];
|
|
265
|
+
for (const member of ctx.members)
|
|
266
|
+
out.push(...(await pulumiMemberCheck(member, ctx, opts)));
|
|
267
|
+
return out;
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type CiEngine = "pulumi" | "plunge";
|
|
2
|
+
/** The Pulumi release the default lines install: the plunge corpus's, which plunge 0.1.0 is built from. */
|
|
3
|
+
export declare const PULUMI_CI_VERSION = "3.272.0";
|
|
4
|
+
/** The plunge release the default lines install (INTENTIUS/plunge, tag plunge-v<ver>), not the Pulumi version it is built from. */
|
|
5
|
+
export declare const PLUNGE_CI_VERSION = "0.1.0";
|
|
6
|
+
/** Where the lines install each release by default. */
|
|
7
|
+
export declare const CI_ENGINE_ROOT = "/opt/chant";
|
|
8
|
+
/** The archive's architecture word, as both projects name their Linux builds. */
|
|
9
|
+
export type CiArch = "x64" | "arm64";
|
|
10
|
+
export declare const CI_ARCHES: readonly CiArch[];
|
|
11
|
+
/**
|
|
12
|
+
* The sha256 of each recorded Linux archive, copied from its release's
|
|
13
|
+
* checksums file (the same sums plywood#12 records). A checksums file that
|
|
14
|
+
* lists another sum for one of these fails the install. Add a release's
|
|
15
|
+
* two lines when moving a default version.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PINNED_SHA256: Readonly<Record<string, string>>;
|
|
18
|
+
/** What a caller may set on an install line. */
|
|
19
|
+
export interface CiInstallOptions {
|
|
20
|
+
/** Where the versioned directories live. Default {@link CI_ENGINE_ROOT}. */
|
|
21
|
+
root?: string;
|
|
22
|
+
/** Recorded sums by architecture, in place of {@link PINNED_SHA256}'s for this release. */
|
|
23
|
+
sha256?: Partial<Record<CiArch, string>>;
|
|
24
|
+
}
|
|
25
|
+
/** One release's download: the base URL, the archive per architecture, and its checksums file. */
|
|
26
|
+
export interface CiRelease {
|
|
27
|
+
base: string;
|
|
28
|
+
archive(arch: CiArch): string;
|
|
29
|
+
checksums: string;
|
|
30
|
+
}
|
|
31
|
+
/** The release of `engine` at `version`. A version is a plain semver, so it is safe on a shell line. */
|
|
32
|
+
export declare function ciRelease(engine: CiEngine, version: string): CiRelease;
|
|
33
|
+
/** Where one release of one engine lives after its line runs. */
|
|
34
|
+
export declare function ciEngineDir(engine: CiEngine, version: string, root?: string): string;
|
|
35
|
+
/** The recorded sums for `version`, by architecture: the caller's, else {@link PINNED_SHA256}'s. */
|
|
36
|
+
export declare function recordedSums(engine: CiEngine, version: string, given?: Partial<Record<CiArch, string>>): Partial<Record<CiArch, string>>;
|
|
37
|
+
/**
|
|
38
|
+
* Pulumi at `version` under `<root>/pulumi-<version>/`, checked against the
|
|
39
|
+
* release checksums. Skips when the `pulumi` on PATH is that version.
|
|
40
|
+
*/
|
|
41
|
+
export declare function pulumiInstallLine(version?: string, options?: CiInstallOptions): string;
|
|
42
|
+
/**
|
|
43
|
+
* plunge at `version` under `<root>/plunge-<version>/`, its language hosts
|
|
44
|
+
* beside it, checked against the release checksums. plunge reports the
|
|
45
|
+
* Pulumi version it is built from, not its own release, so only the
|
|
46
|
+
* versioned directory tells an installed release apart.
|
|
47
|
+
*/
|
|
48
|
+
export declare function plungeInstallLine(version?: string, options?: CiInstallOptions): string;
|
|
49
|
+
/** The lines for the project at `dir`. */
|
|
50
|
+
export declare function pulumiCiSetup(ctx: {
|
|
51
|
+
dir: string;
|
|
52
|
+
}): string[];
|
|
53
|
+
//# sourceMappingURL=ci-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-setup.d.ts","sourceRoot":"","sources":["../src/ci-setup.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3C,2GAA2G;AAC3G,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,mIAAmI;AACnI,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC,uDAAuD;AACvD,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C,iFAAiF;AACjF,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;AACrC,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAAqB,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK1D,CAAC;AAEF,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1C;AAED,kGAAkG;AAClG,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,wGAAwG;AACxG,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CActE;AAED,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,SAAiB,GAAG,MAAM,CAG5F;AAED,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAUxI;AA0CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAoB,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAIrG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAoB,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAErG;AAED,0CAA0C;AAC1C,wBAAgB,aAAa,CAAC,GAAG,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,EAAE,CAE5D"}
|
package/dist/ci-setup.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine install lines a Pulumi project's pull request pipeline runs in
|
|
3
|
+
* chant's default image (#4550, `LexiconPlugin.ciSetup`): Pulumi, or plunge
|
|
4
|
+
* when the member's engine is plunge (`pulumiMemberEngine`, #4559).
|
|
5
|
+
*
|
|
6
|
+
* Each line installs one release (#4592), modelled on plywood#12's
|
|
7
|
+
* (INTENTIUS/plywood 9b24b50847, packages/plywood/src/images.ts):
|
|
8
|
+
*
|
|
9
|
+
* - it takes a version, by default {@link PULUMI_CI_VERSION} or
|
|
10
|
+
* {@link PLUNGE_CI_VERSION};
|
|
11
|
+
* - it downloads the Linux archive for the job's architecture and the
|
|
12
|
+
* release's own checksums file (Pulumi's `pulumi-<ver>-checksums.txt`,
|
|
13
|
+
* plunge's `plunge-v<ver>-checksums.txt`), and fails unless the archive's
|
|
14
|
+
* line is there and matches the archive (`sha256sum -c`);
|
|
15
|
+
* - for a release with a recorded sum ({@link PINNED_SHA256}, or the
|
|
16
|
+
* caller's `sha256`), the line must also hold that sum, so a tampered
|
|
17
|
+
* checksums file fails as a tampered archive does;
|
|
18
|
+
* - it unpacks under a versioned path, `/opt/chant/<engine>-<version>/`,
|
|
19
|
+
* with the language hosts beside the binary, and links the binary into
|
|
20
|
+
* /usr/local/bin;
|
|
21
|
+
* - it skips the download when that directory already holds the binary,
|
|
22
|
+
* and for Pulumi also when the `pulumi` on PATH is that version, so an
|
|
23
|
+
* image that carries the engine pays nothing.
|
|
24
|
+
*
|
|
25
|
+
* A product with images of its own (plywood keeps its engines under
|
|
26
|
+
* /opt/plywood) passes `root`, so the lines find what its image carries.
|
|
27
|
+
*/
|
|
28
|
+
import { pulumiMemberEngine } from "./member-engine.js";
|
|
29
|
+
/** The Pulumi release the default lines install: the plunge corpus's, which plunge 0.1.0 is built from. */
|
|
30
|
+
export const PULUMI_CI_VERSION = "3.272.0";
|
|
31
|
+
/** The plunge release the default lines install (INTENTIUS/plunge, tag plunge-v<ver>), not the Pulumi version it is built from. */
|
|
32
|
+
export const PLUNGE_CI_VERSION = "0.1.0";
|
|
33
|
+
/** Where the lines install each release by default. */
|
|
34
|
+
export const CI_ENGINE_ROOT = "/opt/chant";
|
|
35
|
+
export const CI_ARCHES = ["x64", "arm64"];
|
|
36
|
+
/**
|
|
37
|
+
* The sha256 of each recorded Linux archive, copied from its release's
|
|
38
|
+
* checksums file (the same sums plywood#12 records). A checksums file that
|
|
39
|
+
* lists another sum for one of these fails the install. Add a release's
|
|
40
|
+
* two lines when moving a default version.
|
|
41
|
+
*/
|
|
42
|
+
export const PINNED_SHA256 = {
|
|
43
|
+
"pulumi-v3.272.0-linux-x64.tar.gz": "58d545f2b98761051922008ebd381ecef95484d7f7a5ff7faa15d7a780ea84f6",
|
|
44
|
+
"pulumi-v3.272.0-linux-arm64.tar.gz": "4f0741901b8982c4326b702dabf483c63d2cf77c88ac42dd752b72541284a534",
|
|
45
|
+
"plunge-v0.1.0-linux-x64.tar.gz": "22b89da39ab7dc11268c7b625fec3d99c0793c9c47bf8d031fa94e6967ee8187",
|
|
46
|
+
"plunge-v0.1.0-linux-arm64.tar.gz": "6855611b8cf8381d73f5ddf0b5c0a0d83bdc2a5bd8c50d415e52bb05a4574f83",
|
|
47
|
+
};
|
|
48
|
+
const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
49
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
50
|
+
const ROOT = /^\/[A-Za-z0-9._/-]*$/;
|
|
51
|
+
/** The release of `engine` at `version`. A version is a plain semver, so it is safe on a shell line. */
|
|
52
|
+
export function ciRelease(engine, version) {
|
|
53
|
+
if (!VERSION.test(version))
|
|
54
|
+
throw new Error(`${engine} version ${JSON.stringify(version)} is not a release version such as 3.272.0`);
|
|
55
|
+
if (engine === "pulumi") {
|
|
56
|
+
return {
|
|
57
|
+
base: `https://github.com/pulumi/pulumi/releases/download/v${version}`,
|
|
58
|
+
archive: (arch) => `pulumi-v${version}-linux-${arch}.tar.gz`,
|
|
59
|
+
checksums: `pulumi-${version}-checksums.txt`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
base: `https://github.com/INTENTIUS/plunge/releases/download/plunge-v${version}`,
|
|
64
|
+
archive: (arch) => `plunge-v${version}-linux-${arch}.tar.gz`,
|
|
65
|
+
checksums: `plunge-v${version}-checksums.txt`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Where one release of one engine lives after its line runs. */
|
|
69
|
+
export function ciEngineDir(engine, version, root = CI_ENGINE_ROOT) {
|
|
70
|
+
if (!ROOT.test(root))
|
|
71
|
+
throw new Error(`an engine root is an absolute path of letters, digits and ._/-, not ${JSON.stringify(root)}`);
|
|
72
|
+
return `${root.replace(/\/+$/, "")}/${engine}-${version}`;
|
|
73
|
+
}
|
|
74
|
+
/** The recorded sums for `version`, by architecture: the caller's, else {@link PINNED_SHA256}'s. */
|
|
75
|
+
export function recordedSums(engine, version, given) {
|
|
76
|
+
const r = ciRelease(engine, version);
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const arch of CI_ARCHES) {
|
|
79
|
+
const sum = given ? given[arch] : PINNED_SHA256[r.archive(arch)];
|
|
80
|
+
if (sum === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
if (!SHA256.test(sum))
|
|
83
|
+
throw new Error(`the recorded sha256 for ${r.archive(arch)} is not 64 hex digits`);
|
|
84
|
+
out[arch] = sum;
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
/** The job's architecture as x64 or arm64. */
|
|
89
|
+
const JOB_ARCH = `"$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')"`;
|
|
90
|
+
/**
|
|
91
|
+
* Shell that downloads one release into `dir` and checks it against the
|
|
92
|
+
* release's checksums file and, when recorded, the sum per architecture.
|
|
93
|
+
* Both archives unpack a top directory named after the engine, which
|
|
94
|
+
* `--strip-components=1` drops.
|
|
95
|
+
*/
|
|
96
|
+
function fetchScript(engine, version, dir, sums) {
|
|
97
|
+
const r = ciRelease(engine, version);
|
|
98
|
+
const recorded = CI_ARCHES.filter((a) => sums[a]);
|
|
99
|
+
const pinCase = recorded.length > 0 ? `case "$a" in ${recorded.map((a) => `${a}) want=${sums[a]} ;;`).join(" ")} *) want= ;; esac` : "want=";
|
|
100
|
+
const file = r.archive("x64").replace("-x64.", "-$a.");
|
|
101
|
+
return [
|
|
102
|
+
`a=${JOB_ARCH}`,
|
|
103
|
+
`f="${file}"`,
|
|
104
|
+
`t="$(mktemp -d)"`,
|
|
105
|
+
`curl -fsSLo "$t/$f" "${r.base}/$f"`,
|
|
106
|
+
`curl -fsSLo "$t/sums" "${r.base}/${r.checksums}"`,
|
|
107
|
+
// The archive's line, with any ./ or * before the name taken off.
|
|
108
|
+
`line="$(sed -n "s#^\\([0-9a-f]\\{64\\}\\) *[*.]*/*$f\\$#\\1#p" "$t/sums")"`,
|
|
109
|
+
`{ [ -n "$line" ] || { echo "${engine} ${version}: $f is not in ${r.checksums}" >&2; exit 1; }; }`,
|
|
110
|
+
pinCase,
|
|
111
|
+
`{ [ -z "$want" ] || [ "$line" = "$want" ] || { echo "${engine} ${version}: ${r.checksums} lists $line for $f; chant records $want" >&2; exit 1; }; }`,
|
|
112
|
+
`echo "$line $t/$f" | sha256sum -c - >/dev/null`,
|
|
113
|
+
`mkdir -p "${dir}"`,
|
|
114
|
+
`tar -xzf "$t/$f" -C "${dir}" --strip-components=1`,
|
|
115
|
+
].join(" && ");
|
|
116
|
+
}
|
|
117
|
+
/** The line for one engine: skip when present, else fetch and check, then link into /usr/local/bin. */
|
|
118
|
+
function installLine(engine, version, options, present) {
|
|
119
|
+
const dir = ciEngineDir(engine, version, options.root);
|
|
120
|
+
const install = `(set -e; ${fetchScript(engine, version, dir, recordedSums(engine, version, options.sha256))})`;
|
|
121
|
+
const link = `ln -sf "${dir}/${engine}" /usr/local/bin/${engine}`;
|
|
122
|
+
const inDir = `[ -x "${dir}/${engine}" ]`;
|
|
123
|
+
return present ? `${present} || { { ${inDir} || ${install}; } && ${link}; }` : `{ ${inDir} || ${install}; } && ${link}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Pulumi at `version` under `<root>/pulumi-<version>/`, checked against the
|
|
127
|
+
* release checksums. Skips when the `pulumi` on PATH is that version.
|
|
128
|
+
*/
|
|
129
|
+
export function pulumiInstallLine(version = PULUMI_CI_VERSION, options = {}) {
|
|
130
|
+
ciRelease("pulumi", version);
|
|
131
|
+
const present = `{ command -v pulumi >/dev/null 2>&1 && [ "$(pulumi version 2>/dev/null)" = "v${version}" ]; }`;
|
|
132
|
+
return installLine("pulumi", version, options, present);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* plunge at `version` under `<root>/plunge-<version>/`, its language hosts
|
|
136
|
+
* beside it, checked against the release checksums. plunge reports the
|
|
137
|
+
* Pulumi version it is built from, not its own release, so only the
|
|
138
|
+
* versioned directory tells an installed release apart.
|
|
139
|
+
*/
|
|
140
|
+
export function plungeInstallLine(version = PLUNGE_CI_VERSION, options = {}) {
|
|
141
|
+
return installLine("plunge", version, options);
|
|
142
|
+
}
|
|
143
|
+
/** The lines for the project at `dir`. */
|
|
144
|
+
export function pulumiCiSetup(ctx) {
|
|
145
|
+
return [pulumiMemberEngine(ctx.dir).engine === "plunge" ? plungeInstallLine() : pulumiInstallLine()];
|
|
146
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PulumiAdoptOp composite (#4562): adopt stock Pulumi stacks onto plunge's
|
|
3
|
+
* per-resource records, behind a gate, the pulumi twin of the terraform
|
|
4
|
+
* lexicon's `TerraformAdoptOp`. It is the step after `plywood init --engine
|
|
5
|
+
* plunge` (INTENTIUS/plywood#3); plywood supplies the words.
|
|
6
|
+
*
|
|
7
|
+
* plunge refuses a stock checkpoint with no records (`AdoptionRequiredError`)
|
|
8
|
+
* rather than recreate everything, so a stack moves to plunge by `plunge
|
|
9
|
+
* stack adopt`. Adoption writes one record per resource with the
|
|
10
|
+
* checkpoint's IDs and calls no cloud API; the next `plunge up` adds the
|
|
11
|
+
* `plunge-stack` and `plunge-urn` tags in place.
|
|
12
|
+
*
|
|
13
|
+
* Phases:
|
|
14
|
+
*
|
|
15
|
+
* - **Check**: the shared bucket check (#4558) on each stack's backend
|
|
16
|
+
* bucket. A setting that fails, not waived, refuses the run before
|
|
17
|
+
* anything is exported, naming the fix. A `file://` backend is skipped.
|
|
18
|
+
* - **Export**: each stack's stock checkpoint, `pulumi stack export
|
|
19
|
+
* --stack <s>`, kept in a scratch file. It is the back-out copy (`pulumi
|
|
20
|
+
* stack import --file` on a stock backend), and on the route from Pulumi
|
|
21
|
+
* Cloud (`from: { backend }`) or from an export taken by hand (`from: {
|
|
22
|
+
* file }`) it is what plunge adopts with `--file`.
|
|
23
|
+
* - Then one wave per stack, in the order given: **Preview** runs `plunge
|
|
24
|
+
* stack adopt --json` (writes nothing), the **gate** binds that preview's
|
|
25
|
+
* `planDigest`, and **Adopt** runs `plunge stack adopt --json --yes`. A
|
|
26
|
+
* stack whose checkpoint changed after approval previews a new digest,
|
|
27
|
+
* and the old approval does not open the gate for it.
|
|
28
|
+
*
|
|
29
|
+
* Every gate is a fact on the workspace ledger: `chant run` records the
|
|
30
|
+
* pending gate citing the preview's digest and exits gated, `chant approve
|
|
31
|
+
* <op> <gate>` writes the resolution, and the next run walks to the Adopt
|
|
32
|
+
* step. The run itself is a run record on the same ledger (#4551), as for
|
|
33
|
+
* `TerraformAdoptOp`. There is no `gate: "never"`: adoption moves the stack's
|
|
34
|
+
* state onto another backend's records, which nobody should skip reviewing.
|
|
35
|
+
*
|
|
36
|
+
* ## Compensation
|
|
37
|
+
*
|
|
38
|
+
* Refused without a command, as `TerraformAdoptOp` refuses it. Backing out
|
|
39
|
+
* is `plunge stack export --file s.json` then `pulumi stack import --file
|
|
40
|
+
* s.json` on a stock backend, a decision about the stack and its backend
|
|
41
|
+
* rather than something chant should run on its own.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* import { PulumiAdoptOp } from "@intentius/chant-lexicon-pulumi";
|
|
46
|
+
*
|
|
47
|
+
* export const { op } = PulumiAdoptOp({
|
|
48
|
+
* name: "adopt-infra",
|
|
49
|
+
* stacks: [{ member: "infra", dir: "./infra", stack: "dev" }],
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
import { OpResource } from "@intentius/chant/op";
|
|
54
|
+
/** One stack to adopt. */
|
|
55
|
+
export interface PulumiAdoptStack {
|
|
56
|
+
/** The member's name. */
|
|
57
|
+
member: string;
|
|
58
|
+
/** The stack's directory, where `Pulumi.yaml` is, relative to the project root. */
|
|
59
|
+
dir: string;
|
|
60
|
+
/** The stack to adopt. */
|
|
61
|
+
stack: string;
|
|
62
|
+
/** The plunge backend URL (`plunge+s3://...`). Default: `plywood.yml`'s, `PULUMI_BACKEND_URL`, `Pulumi.yaml`'s. */
|
|
63
|
+
backend?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Where the checkpoint comes from, when it is not the stock stack at the
|
|
66
|
+
* same root: `backend`, another backend to export from (Pulumi Cloud), or
|
|
67
|
+
* `file`, an export already taken. Either way plunge adopts it with
|
|
68
|
+
* `--file`.
|
|
69
|
+
*/
|
|
70
|
+
from?: {
|
|
71
|
+
backend: string;
|
|
72
|
+
} | {
|
|
73
|
+
file: string;
|
|
74
|
+
};
|
|
75
|
+
/** The bucket to check, as `@intentius/chant/bucket-check` takes it, when the URL does not name it fully (azblob). */
|
|
76
|
+
bucket?: Record<string, unknown>;
|
|
77
|
+
/** Key prefixes a lifecycle rule must cover. Default: the backend URL's path. */
|
|
78
|
+
namespaces?: string[];
|
|
79
|
+
/** Bucket settings waived, as plunge's `allow_insecure`. An unwaivable one stays refused. */
|
|
80
|
+
allowInsecure?: string[];
|
|
81
|
+
}
|
|
82
|
+
export interface PulumiAdoptOpConfig {
|
|
83
|
+
/** Op name (kebab-case). */
|
|
84
|
+
name: string;
|
|
85
|
+
/** The stacks to adopt, one wave each, in this order. */
|
|
86
|
+
stacks: PulumiAdoptStack[];
|
|
87
|
+
/** Directory relative `dir`s resolve against. Default: the running process's cwd. */
|
|
88
|
+
cwd?: string;
|
|
89
|
+
/** The gate's name with one stack; with several, each gate is `<gateName>-<member>-<stack>`. Default: `approve-<name>`. */
|
|
90
|
+
gateName?: string;
|
|
91
|
+
/** How long a recorded pending gate stays valid, as a duration string. Default: core's own (48h). */
|
|
92
|
+
gateTimeout?: string;
|
|
93
|
+
/** Override the gate description shown to the approver. */
|
|
94
|
+
gateDescription?: string;
|
|
95
|
+
/** Rollback on a failed adoption, run as an `onFailure` phase. Refused without a command. */
|
|
96
|
+
compensate?: boolean | {
|
|
97
|
+
command?: string;
|
|
98
|
+
image?: string;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export interface PulumiAdoptOpResources {
|
|
102
|
+
/** Op resource: Check, Export, then Preview, gate and Adopt per stack. */
|
|
103
|
+
op: InstanceType<typeof OpResource>;
|
|
104
|
+
}
|
|
105
|
+
export declare function PulumiAdoptOp(config: PulumiAdoptOpConfig): PulumiAdoptOpResources;
|
|
106
|
+
//# sourceMappingURL=pulumi-adopt-op.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulumi-adopt-op.d.ts","sourceRoot":"","sources":["../../src/composites/pulumi-adopt-op.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,EAA6B,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG5E,0BAA0B;AAC1B,MAAM,WAAW,gBAAgB;IAC/B,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,mHAAmH;IACnH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,sHAAsH;IACtH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2HAA2H;IAC3H,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qGAAqG;IACrG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,OAAO,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,EAAE,EAAE,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;CACrC;AAID,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,sBAAsB,CAsFjF"}
|