@intentius/chant-lexicon-pulumi 0.124.54 → 0.124.55

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.
Files changed (146) hide show
  1. package/README.md +42 -3
  2. package/dist/adopt.d.ts +68 -0
  3. package/dist/adopt.d.ts.map +1 -0
  4. package/dist/adopt.js +162 -0
  5. package/dist/capability-plugin.d.ts +3 -0
  6. package/dist/capability-plugin.d.ts.map +1 -1
  7. package/dist/capability-plugin.js +8 -1
  8. package/dist/change-set.d.ts +27 -4
  9. package/dist/change-set.d.ts.map +1 -1
  10. package/dist/change-set.js +15 -5
  11. package/dist/ci-setup.d.ts +53 -0
  12. package/dist/ci-setup.d.ts.map +1 -0
  13. package/dist/ci-setup.js +146 -0
  14. package/dist/composites/pulumi-adopt-op.d.ts +106 -0
  15. package/dist/composites/pulumi-adopt-op.d.ts.map +1 -0
  16. package/dist/composites/pulumi-adopt-op.js +132 -0
  17. package/dist/engine-events.d.ts +222 -0
  18. package/dist/engine-events.d.ts.map +1 -0
  19. package/dist/engine-events.js +280 -0
  20. package/dist/exit-codes.d.ts +53 -0
  21. package/dist/exit-codes.d.ts.map +1 -0
  22. package/dist/exit-codes.js +67 -0
  23. package/dist/index.d.ts +12 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +15 -2
  26. package/dist/init-templates.d.ts.map +1 -1
  27. package/dist/init-templates.js +5 -3
  28. package/dist/integrity.json +3 -3
  29. package/dist/lint/post-synth/pul015.d.ts +2 -1
  30. package/dist/lint/post-synth/pul015.d.ts.map +1 -1
  31. package/dist/lock-rows.d.ts +32 -0
  32. package/dist/lock-rows.d.ts.map +1 -0
  33. package/dist/lock-rows.js +36 -0
  34. package/dist/manifest.json +1 -1
  35. package/dist/member-engine.d.ts +27 -2
  36. package/dist/member-engine.d.ts.map +1 -1
  37. package/dist/member-engine.js +58 -7
  38. package/dist/member-outputs.d.ts.map +1 -1
  39. package/dist/member-outputs.js +4 -4
  40. package/dist/op/activities/index.d.ts +9 -1
  41. package/dist/op/activities/index.d.ts.map +1 -1
  42. package/dist/op/activities/index.js +8 -1
  43. package/dist/op/activities/pulumi-adopt.d.ts +104 -0
  44. package/dist/op/activities/pulumi-adopt.d.ts.map +1 -0
  45. package/dist/op/activities/pulumi-adopt.js +216 -0
  46. package/dist/op/activities/pulumi.d.ts.map +1 -1
  47. package/dist/op/activities/pulumi.js +2 -1
  48. package/dist/op/activity-contracts.d.ts +73 -0
  49. package/dist/op/activity-contracts.d.ts.map +1 -1
  50. package/dist/op/activity-contracts.js +49 -0
  51. package/dist/op/builders.d.ts +9 -0
  52. package/dist/op/builders.d.ts.map +1 -1
  53. package/dist/op/builders.js +20 -0
  54. package/dist/plan-shape.d.ts +47 -0
  55. package/dist/plan-shape.d.ts.map +1 -0
  56. package/dist/plan-shape.js +94 -0
  57. package/dist/plugin.d.ts.map +1 -1
  58. package/dist/plugin.js +25 -4
  59. package/dist/plunge-timings.d.ts +43 -0
  60. package/dist/plunge-timings.d.ts.map +1 -0
  61. package/dist/plunge-timings.js +106 -0
  62. package/dist/pulumi-up.d.ts +376 -0
  63. package/dist/pulumi-up.d.ts.map +1 -0
  64. package/dist/pulumi-up.js +823 -0
  65. package/dist/release-copy.d.ts +47 -0
  66. package/dist/release-copy.d.ts.map +1 -0
  67. package/dist/release-copy.js +131 -0
  68. package/dist/review-stack.d.ts +92 -0
  69. package/dist/review-stack.d.ts.map +1 -0
  70. package/dist/review-stack.js +222 -0
  71. package/dist/rules/pul015.ts +2 -1
  72. package/dist/run.d.ts +250 -7
  73. package/dist/run.d.ts.map +1 -1
  74. package/dist/run.js +432 -26
  75. package/dist/stack-export-snapshot.d.ts +125 -0
  76. package/dist/stack-export-snapshot.d.ts.map +1 -0
  77. package/dist/stack-export-snapshot.js +233 -0
  78. package/dist/stack-ops.d.ts +175 -0
  79. package/dist/stack-ops.d.ts.map +1 -0
  80. package/dist/stack-ops.js +400 -0
  81. package/dist/stack-references.d.ts +46 -0
  82. package/dist/stack-references.d.ts.map +1 -0
  83. package/dist/stack-references.js +88 -0
  84. package/dist/trace-facts.d.ts +34 -0
  85. package/dist/trace-facts.d.ts.map +1 -0
  86. package/dist/trace-facts.js +81 -0
  87. package/dist/workspace-member.d.ts +13 -2
  88. package/dist/workspace-member.d.ts.map +1 -1
  89. package/dist/workspace-member.js +14 -9
  90. package/package.json +2 -2
  91. package/src/adopt.ts +202 -0
  92. package/src/capability-plugin.ts +15 -6
  93. package/src/change-set.ts +29 -5
  94. package/src/ci-setup.test.ts +123 -0
  95. package/src/ci-setup.ts +176 -0
  96. package/src/composites/pulumi-adopt-op.test.ts +223 -0
  97. package/src/composites/pulumi-adopt-op.ts +191 -0
  98. package/src/composites/pulumi-watch-op.test.ts +2 -2
  99. package/src/engine-events.test.ts +134 -0
  100. package/src/engine-events.ts +390 -0
  101. package/src/engine-graph.test.ts +1 -1
  102. package/src/exit-codes.test.ts +21 -0
  103. package/src/exit-codes.ts +71 -0
  104. package/src/index.ts +75 -1
  105. package/src/init-templates.ts +5 -3
  106. package/src/lint/post-synth/pul015.ts +2 -1
  107. package/src/lock-rows.test.ts +60 -0
  108. package/src/lock-rows.ts +56 -0
  109. package/src/member-engine.test.ts +112 -0
  110. package/src/member-engine.ts +73 -6
  111. package/src/member-outputs.test.ts +2 -2
  112. package/src/member-outputs.ts +9 -8
  113. package/src/op/activities/index.ts +19 -1
  114. package/src/op/activities/pulumi-adopt.ts +334 -0
  115. package/src/op/activities/pulumi.ts +2 -1
  116. package/src/op/activity-contracts.ts +75 -0
  117. package/src/op/builders.ts +37 -0
  118. package/src/plan-shape.test.ts +91 -0
  119. package/src/plan-shape.ts +105 -0
  120. package/src/plugin.test.ts +1 -1
  121. package/src/plugin.ts +28 -4
  122. package/src/plunge-timings.test.ts +241 -0
  123. package/src/plunge-timings.ts +115 -0
  124. package/src/plunge-up-failed.test.ts +54 -0
  125. package/src/pr-note.test.ts +87 -0
  126. package/src/pulumi-up.test.ts +470 -0
  127. package/src/pulumi-up.ts +1032 -0
  128. package/src/release-copy.test.ts +74 -0
  129. package/src/release-copy.ts +142 -0
  130. package/src/review-stack.test.ts +254 -0
  131. package/src/review-stack.ts +266 -0
  132. package/src/run.test.ts +176 -3
  133. package/src/run.ts +501 -30
  134. package/src/stack-export-snapshot.test.ts +190 -0
  135. package/src/stack-export-snapshot.ts +312 -0
  136. package/src/stack-ops.test.ts +286 -0
  137. package/src/stack-ops.ts +552 -0
  138. package/src/stack-references.test.ts +96 -0
  139. package/src/stack-references.ts +110 -0
  140. package/src/testdata/pulumi-stream/README.md +16 -0
  141. package/src/testdata/pulumi-stream/preview-stream.ndjson +42 -0
  142. package/src/testdata/pulumi-stream/up-failed-plunge.ndjson +5 -0
  143. package/src/trace-facts.test.ts +38 -0
  144. package/src/trace-facts.ts +83 -0
  145. package/src/workspace-member.test.ts +2 -1
  146. package/src/workspace-member.ts +30 -12
package/README.md CHANGED
@@ -1,16 +1,55 @@
1
1
  # @intentius/chant-lexicon-pulumi
2
2
 
3
- Pulumi lexicon for [chant](https://intentius.io/chant/). It declares no resource types. It supplies the `pulumi` workspace member kind and reads a Pulumi program four ways:
3
+ Pulumi lexicon for [chant](https://intentius.io/chant/). It adds your Pulumi program to a chant workspace as it is, on stock Pulumi or on [plunge](https://github.com/INTENTIUS/plunge), a Pulumi fork that keeps chant's ownership markers on each resource. It declares no resource types. It supplies the `pulumi` workspace member kind and reads a Pulumi program four ways:
4
4
 
5
5
  | Read | Source | Module |
6
6
  |---|---|---|
7
- | the member's graph | the `pulumi stack export` checked in as `stack-export.json` | `src/workspace-member.ts` |
7
+ | the member's graph | the `pulumi stack export` the last `pulumi-up` apply wrote to `refs/chant/snapshots`, else the one checked in as `stack-export.json` | `src/workspace-member.ts`, `src/stack-export-snapshot.ts` |
8
8
  | its change-set part | `pulumi preview --json` | `src/change-set.ts` |
9
9
  | its drift records | `pulumi refresh --preview-only --json --target <urn>`, one resource at a time | `src/drift-record.ts`, `src/drift-read.ts` |
10
10
  | its stack outputs, for another member's parameters | `pulumi stack output --json --show-secrets=false` (applied) and `pulumi preview --json` (planned) | `src/stack-outputs.ts`, `src/member-outputs.ts` |
11
11
  | engine facts, for a member on plunge | `plunge graph --json --stack <stack>` (`describeResources`), or the checked-in `engine-graph.json` (the graph read) | `src/describe-resources.ts`, `src/engine-graph.ts` |
12
12
 
13
- The graph read never runs `pulumi`. The change set and drift run `preview` and `refresh --preview-only`, and the outputs read adds `stack output --json --show-secrets=false`. A member on plunge runs `plunge preview`, `plunge stack output --json --show-secrets=false` and `plunge drift --resource <urn> --stream`. For its engine facts it runs one of exactly four argv: `plunge graph --json`, `plunge graph --json --stack <stack>`, `plunge graph --json --versions` and `plunge graph --json --stack <stack> --versions` ([#4529](https://github.com/INTENTIUS/chant/issues/4529)). An outputs or secrets flag on `plunge graph` is refused. That is the whole list, as ruled on [#4476](https://github.com/INTENTIUS/chant/issues/4476) (2026-10-25), which widened the [#4450](https://github.com/INTENTIUS/chant/issues/4450) ruling's `preview` and `refresh --preview-only` to keep the outputs read and the plunge commands. Nothing else runs (`runAllowedPulumi` and `runAllowedPlunge` refuse any other argv), so chant never changes a stack. With `CHANT_PULUMI_APPLIED_OUTPUTS=export`, the outputs read runs nothing either.
13
+ The graph read never runs `pulumi`. The change set and drift run `preview` and `refresh --preview-only`, and the outputs read adds `stack output --json --show-secrets=false`. A member on plunge runs `plunge preview`, `plunge stack output --json --show-secrets=false` and `plunge drift --resource <urn> --stream`. For its engine facts it runs one of exactly four argv: `plunge graph --json`, `plunge graph --json --stack <stack>`, `plunge graph --json --versions` and `plunge graph --json --stack <stack> --versions` ([#4529](https://github.com/INTENTIUS/chant/issues/4529)). An outputs or secrets flag on `plunge graph` is refused. That is the whole list, as ruled on [#4476](https://github.com/INTENTIUS/chant/issues/4476) (2026-10-25), which widened the [#4450](https://github.com/INTENTIUS/chant/issues/4450) ruling's `preview` and `refresh --preview-only` to keep the outputs read and the plunge commands. Nothing else runs on a read (`runAllowedPulumi` and `runAllowedPlunge` refuse any other argv, and a preview with `--show-secrets` or `--import-file`), so no read changes a stack. With `CHANT_PULUMI_APPLIED_OUTPUTS=export`, the outputs read runs nothing either.
14
+
15
+ ## Applies: the `pulumi-up` capability
16
+
17
+ A stack can be a component step, `{ kind: "pulumi-up", dir, stack }`, that plans in a pull request and applies on merge like a `terraform-apply` root ([#4541](https://github.com/INTENTIUS/chant/issues/4541), `src/pulumi-up.ts`). The ruling on INTENTIUS/plywood#1 widened #4450 and #4476 for it alone, from an apply job holding the apply identity. Its `up` goes through `runApplyPulumi` or `runApplyPlunge`, each of which takes one fixed argv (`PULUMI_APPLY_COMMANDS` in `src/run.ts`):
18
+
19
+ | Engine | Argv |
20
+ |---|---|
21
+ | plunge | `plunge preview --json --show-sames --stack <s> --save-plan <file>`, then `plunge up --plan <file> --yes --json --stack <s>` |
22
+ | stock Pulumi | `pulumi preview --json --show-sames --stack <s>`, then `pulumi up --yes --json --stack <s>` |
23
+
24
+ Both forms may add `--config-file <file>`, then `--target <urn>` for each target, then `--policy-pack <dir>` with an optional `--policy-pack-config <file>`, in that order. Any other flag on `up` is refused.
25
+
26
+ A step pinned to its own engine release sets `engineVersion` (and `engineRoot` when its image keeps engines elsewhere than `/opt/chant`). Every run of that step, preview, `up`, stack output and export, then uses `<engineRoot>/<engine>-<engineVersion>/<engine>`, the path `pulumiInstallLine` and `plungeInstallLine` install to ([#4592](https://github.com/INTENTIUS/chant/issues/4592), `src/ci-setup.ts`). The argv is checked against the same forms, and a step without `engineVersion` runs the binary on PATH.
27
+
28
+ The CI install lines take a version, download the release's Linux archive and its checksums file, and fail unless the archive matches its line there. For the default releases (Pulumi 3.272.0, plunge 0.1.0) the line must also hold the sha256 the lexicon records per architecture. Each release goes under its own versioned directory and is linked into `/usr/local/bin`, and a line skips the download when that directory already holds the binary, or for Pulumi when the `pulumi` on PATH is that version.
29
+
30
+ The preview streams its engine events (`PULUMI_ENABLE_STREAMING_JSON_PREVIEW=true`, `src/engine-events.ts`), so one run gives the change set and CrossGuard's findings. A mandatory violation denies the plan. On plunge the saved plan's stamp refuses a plan whose records moved (plunge exit 40, chant's moved code 4). On stock Pulumi the apply previews again, then compares the steps in `up`'s event stream with the approved shape (`src/plan-shape.ts`), and a step outside it fails the apply. A member on the resource lock grain, the default on plunge, leaves `same` and `read` steps out of its digest. The step's `config` is written into a scratch copy of `Pulumi.<stack>.yaml` and passed with `--config-file`, never `--config`, which writes the working tree's file. A release cut records the stack's directory (`releaseInputs`), unless its backend is a file path relative to that directory. Under a release, a promote or rollback previews and applies the recorded copy, after `pulumi install` (or `plunge install`) in the copy when its runtime needs one (`src/release-copy.ts`); a Node copy with no recorded lockfile is refused by name.
31
+
32
+ After `up`, the capability runs `stack export --stack <s>`, replaces every secret with `[secret]`, drops the secrets provider's state, and writes the export through chant's index store as `<env>/<stack>__pulumi-export` ([#4546](https://github.com/INTENTIUS/chant/issues/4546), `refs/chant/snapshots` by default). The graph, the preview's prior state and the drift read take that export first, so a stack chant applies needs no checked-in `stack-export.json`. A failed export leaves the apply standing and says why in the step's `exportSnapshot` output.
33
+
34
+ A program that reads another stack through `StackReference` orders its stack after that one ([#4547](https://github.com/INTENTIUS/chant/issues/4547), `src/stack-references.ts`). The capability's `declaredReads` reads each step's export for `pulumi:pulumi:StackReference` resources, matches each `name` to the other `pulumi-up` step with that `Pulumi.yaml` name and stack, and reports a read of each output the export recorded that is not secret. Core writes those reads into the step's `stackReferences` input and adds a `dependsOn`, so the waves put the referenced stack first, a failed upstream holds the reader, and a reader waits while the upstream's plan cannot say a value it reads. A reference to a stack no step applies adds no edge, and the plan's artifact names it under `stackReferences.outside`.
35
+
36
+ ## Adoption: `PulumiAdoptOp`
37
+
38
+ `PulumiAdoptOp` (`src/composites/pulumi-adopt-op.ts`, [#4562](https://github.com/INTENTIUS/chant/issues/4562)) adopts stock stacks onto plunge's per-resource records, the pulumi twin of `TerraformAdoptOp`. Its phases are Check (the shared bucket check on the backend's bucket), Export (`pulumi stack export --stack <s>` into a scratch file), then per stack Preview (`plunge stack adopt --json`), a gate bound to the preview's digest, and Adopt (`plunge stack adopt --json --yes`, through `runAdoptPlunge`). `from: { backend }` or `from: { file }` adopts an export with `--file`, the route from Pulumi Cloud. Gates and the run are recorded on the workspace ledger.
39
+
40
+ ## Gated stack operations
41
+
42
+ `src/stack-ops.ts` ([#4578](https://github.com/INTENTIUS/chant/issues/4578)) runs three stack operations, each only after its gated request (#4551) is approved on the workspace ledger by someone other than the requester, under the op `pulumi-stack-ops`. The first call records the request and returns the `chant approve` command; the same call after the approval spends it, runs once, and records how it ended.
43
+
44
+ | Function | Request | What runs after approval |
45
+ |---|---|---|
46
+ | `gatedStackExport` | `stack-export` of `<stack>` | `pulumi stack export --stack <s>` (or plunge's), written mode 600 to a file outside every git working tree; refused in CI |
47
+ | `gatedStackCancel` | `stack-cancel` of `<stack>` with its lock id | `pulumi cancel --yes --stack <s>`, once the lock is still the approved one (read from a `file://` backend's lock directory); refused on plunge, which has no stack lock |
48
+ | `gatedStackMove` | `stack-move` of `move:<source>:<dest>` with the URNs | `pulumi state move --source <s> --dest <d> --yes <urn>...`, or `plunge stack move --stack <s> --dest <d> <urn>... --json --yes` after plunge's plan; both stacks preview first, and the move runs only when the previews change nothing but the moved resources |
49
+
50
+ `pulumiStackLockIds({ dir, stack })` ([#4615](https://github.com/INTENTIUS/chant/issues/4615)) reads the lock ids a stack holds now, the same read `gatedStackCancel` checks with, so a product can offer the id to approve. It answers `checked: false` on a backend other than `file://`, and no ids for a plunge stack.
51
+
52
+ The writes go through `runGatedPulumi` and `runGatedPlunge` in `src/run.ts`, whose fixed forms (`PULUMI_GATED_COMMANDS`, `PLUNGE_GATED_COMMANDS`) are the only ones allowed.
14
53
 
15
54
  ## Drift: `chant workspace drift`
16
55
 
@@ -0,0 +1,68 @@
1
+ import type { BucketTarget } from "@intentius/chant/bucket-check";
2
+ /** `AdoptView.status`. */
3
+ export type PlungeAdoptStatus = "would-adopt" | "adopted" | "already-adopted" | "refused";
4
+ export interface PlungeAdoptRefusal {
5
+ code: string;
6
+ urn?: string;
7
+ reason: string;
8
+ }
9
+ export interface PlungeAdoptResource {
10
+ urn: string;
11
+ type: string;
12
+ id: string;
13
+ /** `marker`, `record`, `pending` or `already-adopted`. */
14
+ fate: string;
15
+ adoptedAs?: string;
16
+ marked?: boolean;
17
+ reason?: string;
18
+ }
19
+ /** The parts of plunge's `AdoptView` chant reads. */
20
+ export interface PlungeAdoptView {
21
+ stack: string;
22
+ status: PlungeAdoptStatus;
23
+ checkpoint: {
24
+ source?: string;
25
+ digest?: string;
26
+ };
27
+ resources: PlungeAdoptResource[];
28
+ refusals: PlungeAdoptRefusal[];
29
+ }
30
+ /** The adopt document, or undefined when `text` is not one. */
31
+ export declare function parsePlungeAdoptView(text: string | undefined): PlungeAdoptView | undefined;
32
+ /** The gate's digest over an adopt document (see the module comment). */
33
+ export declare function pulumiAdoptDigest(view: PlungeAdoptView): string;
34
+ /** Counts by fate. */
35
+ export declare function adoptFates(view: PlungeAdoptView): Record<string, number>;
36
+ /** The stack's backend URL and where it came from, first that answers: the step's, `plywood.yml`'s, `PULUMI_BACKEND_URL`, `Pulumi.yaml`'s. */
37
+ export declare function pulumiStackBackend(dir: string, opts?: {
38
+ configured?: string;
39
+ env?: Record<string, string | undefined>;
40
+ }): {
41
+ url: string;
42
+ source: string;
43
+ } | undefined;
44
+ /** The stock backend at the same root: the URL without plunge's `plunge+` prefix. */
45
+ export declare function stockBackendUrl(url: string): string;
46
+ /** What the bucket check reads for a backend, or why it does not run. */
47
+ export type AdoptBucketTarget = {
48
+ target: BucketTarget;
49
+ namespaces: string[];
50
+ waived: string[];
51
+ } | {
52
+ skip: string;
53
+ } | {
54
+ refuse: string;
55
+ };
56
+ /**
57
+ * The bucket behind a DIY backend URL, for the shared check (#4558). An
58
+ * `s3://` URL gives the bucket, its `region` (else `AWS_REGION`), its
59
+ * `endpoint` with static keys from the environment, and the path as the
60
+ * namespace a lifecycle rule must cover; `gs://` gives the bucket. A
61
+ * `file://` backend has no bucket, and the check is skipped with that
62
+ * reason. An `azblob://` URL does not name the subscription and resource
63
+ * group the check reads, so it is refused unless the step names the bucket
64
+ * itself. `allow_insecure` on the URL and `PLUNGE_ALLOW_INSECURE` are read as
65
+ * plunge reads them.
66
+ */
67
+ export declare function adoptBucketTarget(url: string, env?: Record<string, string | undefined>): AdoptBucketTarget;
68
+ //# sourceMappingURL=adopt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../src/adopt.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAMlE,0BAA0B;AAC1B,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE1F,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;CAChC;AAMD,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CA+B1F;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAK/D;AAED,sBAAsB;AACtB,wBAAgB,UAAU,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxE;AAED,8IAA8I;AAC9I,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CAAO,GAC3E;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAgB7C;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,yEAAyE;AACzE,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvB;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,iBAAiB,CAkDvH"}
package/dist/adopt.js ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Adopting a stock Pulumi stack onto plunge's per-resource records (#4562):
3
+ * the pieces `PulumiAdoptOp` (`./composites/pulumi-adopt-op.ts`) and its
4
+ * activities (`./op/activities/pulumi-adopt.ts`) share.
5
+ *
6
+ * - {@link parsePlungeAdoptView}: the document `plunge stack adopt --json`
7
+ * prints (plunge `pkg/backend/plunge` `AdoptView`, its site's
8
+ * reference/adopt-json page): `status` is `would-adopt`, `adopted`,
9
+ * `already-adopted` or `refused`, with each resource's fate and every
10
+ * refusal.
11
+ * - {@link pulumiAdoptDigest}: what the Op's gate binds. It covers the
12
+ * stack, the checkpoint's digest and source, and each resource's URN,
13
+ * type, ID and fate, and leaves `status` out, so the preview an approver
14
+ * saw and the `--yes` run that follows digest the same when nothing moved.
15
+ * - {@link pulumiStackBackend} and {@link adoptBucketTarget}: the stack's
16
+ * backend URL, and the bucket the shared check (#4558) reads from it.
17
+ */
18
+ import { existsSync, readFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
21
+ import { parseYAML } from "@intentius/chant/yaml";
22
+ import { readPlywoodEngine } from "./member-engine.js";
23
+ import { isObject } from "./values.js";
24
+ const STATUSES = new Set(["would-adopt", "adopted", "already-adopted", "refused"]);
25
+ const str = (v) => (typeof v === "string" ? v : undefined);
26
+ /** The adopt document, or undefined when `text` is not one. */
27
+ export function parsePlungeAdoptView(text) {
28
+ if (!text)
29
+ return undefined;
30
+ let doc;
31
+ try {
32
+ doc = JSON.parse(text);
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ if (!isObject(doc) || typeof doc.status !== "string" || !STATUSES.has(doc.status))
38
+ return undefined;
39
+ const checkpoint = isObject(doc.checkpoint) ? doc.checkpoint : {};
40
+ const resources = (Array.isArray(doc.resources) ? doc.resources : []).filter(isObject).map((r) => ({
41
+ urn: str(r.urn) ?? "",
42
+ type: str(r.type) ?? "",
43
+ id: str(r.id) ?? "",
44
+ fate: str(r.fate) ?? "",
45
+ ...(str(r.adoptedAs) ? { adoptedAs: str(r.adoptedAs) } : {}),
46
+ ...(typeof r.marked === "boolean" ? { marked: r.marked } : {}),
47
+ ...(str(r.reason) ? { reason: str(r.reason) } : {}),
48
+ }));
49
+ const refusals = (Array.isArray(doc.refusals) ? doc.refusals : []).filter(isObject).map((r) => ({ code: str(r.code) ?? "", ...(str(r.urn) ? { urn: str(r.urn) } : {}), reason: str(r.reason) ?? "" }));
50
+ return {
51
+ stack: str(doc.stack) ?? "",
52
+ status: doc.status,
53
+ checkpoint: { ...(str(checkpoint.source) ? { source: str(checkpoint.source) } : {}), ...(str(checkpoint.digest) ? { digest: str(checkpoint.digest) } : {}) },
54
+ resources,
55
+ refusals,
56
+ };
57
+ }
58
+ /** The gate's digest over an adopt document (see the module comment). */
59
+ export function pulumiAdoptDigest(view) {
60
+ const resources = view.resources
61
+ .map((r) => ({ urn: r.urn, type: r.type, id: r.id, fate: r.fate === "already-adopted" ? (r.adoptedAs ?? r.fate) : r.fate }))
62
+ .sort((a, b) => (a.urn < b.urn ? -1 : a.urn > b.urn ? 1 : 0));
63
+ return computePlanDigest("plunge-adopt", { stack: view.stack, checkpoint: view.checkpoint.digest ?? null, resources });
64
+ }
65
+ /** Counts by fate. */
66
+ export function adoptFates(view) {
67
+ const out = {};
68
+ for (const r of view.resources)
69
+ out[r.fate] = (out[r.fate] ?? 0) + 1;
70
+ return out;
71
+ }
72
+ /** The stack's backend URL and where it came from, first that answers: the step's, `plywood.yml`'s, `PULUMI_BACKEND_URL`, `Pulumi.yaml`'s. */
73
+ export function pulumiStackBackend(dir, opts = {}) {
74
+ if (opts.configured)
75
+ return { url: opts.configured, source: "the step's backend" };
76
+ const plywood = readPlywoodEngine(dir);
77
+ if (plywood?.backend)
78
+ return { url: plywood.backend, source: plywood.file };
79
+ const env = (opts.env ?? process.env).PULUMI_BACKEND_URL;
80
+ if (env)
81
+ return { url: env, source: "PULUMI_BACKEND_URL" };
82
+ const file = join(dir, "Pulumi.yaml");
83
+ if (existsSync(file)) {
84
+ try {
85
+ const url = parseYAML(readFileSync(file, "utf-8")).backend?.url;
86
+ if (typeof url === "string")
87
+ return { url, source: "Pulumi.yaml" };
88
+ }
89
+ catch {
90
+ // No backend named.
91
+ }
92
+ }
93
+ return undefined;
94
+ }
95
+ /** The stock backend at the same root: the URL without plunge's `plunge+` prefix. */
96
+ export function stockBackendUrl(url) {
97
+ return url.replace(/^plunge\+/, "");
98
+ }
99
+ /**
100
+ * The bucket behind a DIY backend URL, for the shared check (#4558). An
101
+ * `s3://` URL gives the bucket, its `region` (else `AWS_REGION`), its
102
+ * `endpoint` with static keys from the environment, and the path as the
103
+ * namespace a lifecycle rule must cover; `gs://` gives the bucket. A
104
+ * `file://` backend has no bucket, and the check is skipped with that
105
+ * reason. An `azblob://` URL does not name the subscription and resource
106
+ * group the check reads, so it is refused unless the step names the bucket
107
+ * itself. `allow_insecure` on the URL and `PLUNGE_ALLOW_INSECURE` are read as
108
+ * plunge reads them.
109
+ */
110
+ export function adoptBucketTarget(url, env = process.env) {
111
+ const stock = stockBackendUrl(url);
112
+ let u;
113
+ try {
114
+ u = new URL(stock);
115
+ }
116
+ catch {
117
+ return { refuse: `the backend ${url} is not a URL the bucket check can read` };
118
+ }
119
+ const waived = [
120
+ ...(u.searchParams.get("allow_insecure") ?? "").split(","),
121
+ ...(env.PLUNGE_ALLOW_INSECURE ?? "").split(","),
122
+ ]
123
+ .map((s) => s.trim())
124
+ .filter(Boolean);
125
+ const prefix = u.pathname.replace(/^\/+/, "");
126
+ const namespaces = prefix ? [prefix.endsWith("/") ? prefix : `${prefix}/`] : [];
127
+ switch (u.protocol) {
128
+ case "file:":
129
+ return { skip: `the backend ${url} is a local file path, which has no bucket to check` };
130
+ case "s3:": {
131
+ const region = u.searchParams.get("region") ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION;
132
+ if (!region)
133
+ return { refuse: `the backend ${url} names no region and AWS_REGION is not set, so the bucket check cannot read ${u.hostname}` };
134
+ const endpoint = u.searchParams.get("endpoint") ?? undefined;
135
+ if (endpoint && !(env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY)) {
136
+ return { refuse: `the backend ${url} names the endpoint ${endpoint}, and the bucket check reads an endpoint only with static keys (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)` };
137
+ }
138
+ const target = {
139
+ store: "s3",
140
+ bucket: u.hostname,
141
+ region,
142
+ ...(endpoint
143
+ ? {
144
+ endpoint: /^[a-z]+:\/\//.test(endpoint) ? endpoint : `https://${endpoint}`,
145
+ credentials: {
146
+ accessKeyId: env.AWS_ACCESS_KEY_ID,
147
+ secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
148
+ ...(env.AWS_SESSION_TOKEN ? { sessionToken: env.AWS_SESSION_TOKEN } : {}),
149
+ },
150
+ }
151
+ : {}),
152
+ };
153
+ return { target, namespaces, waived };
154
+ }
155
+ case "gs:":
156
+ return { target: { store: "gcs", bucket: u.hostname }, namespaces, waived };
157
+ case "azblob:":
158
+ return { refuse: `the backend ${url} does not name the storage account's subscription and resource group; set the step's bucket to the azblob target` };
159
+ default:
160
+ return { refuse: `the backend ${url} is not a bucket plunge stores records in (s3, gs, azblob or file)` };
161
+ }
162
+ }
@@ -1,3 +1,6 @@
1
1
  import { type CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
2
+ export declare const PULUMI_VERB_FAMILIES: {
3
+ readonly apply: readonly ["pulumi-up"];
4
+ };
2
5
  export declare const pulumiCapabilityPlugin: CapabilityPlugin;
3
6
  //# sourceMappingURL=capability-plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../src/capability-plugin.ts"],"names":[],"mappings":"AASA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAIzG,eAAO,MAAM,sBAAsB,EAAE,gBAWpC,CAAC"}
1
+ {"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../src/capability-plugin.ts"],"names":[],"mappings":"AAUA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAKzG,eAAO,MAAM,oBAAoB;aAC/B,KAAK,YAAG,WAAW;CACX,CAAC;AAEX,eAAO,MAAM,sBAAsB,EAAE,gBAcpC,CAAC"}
@@ -1,12 +1,19 @@
1
1
  import { ownPackageVersion } from "@intentius/chant/components/capability-plugin";
2
2
  import { pulumiStackOutputSource } from "./member-outputs.js";
3
+ import { pulumiUpCapability } from "./pulumi-up.js";
4
+ export const PULUMI_VERB_FAMILIES = {
5
+ apply: ["pulumi-up"],
6
+ };
3
7
  export const pulumiCapabilityPlugin = {
4
8
  name: "pulumi",
5
9
  get version() {
6
10
  return ownPackageVersion(import.meta.url);
7
11
  },
8
12
  capabilities() {
9
- return [];
13
+ return [pulumiUpCapability];
14
+ },
15
+ families() {
16
+ return PULUMI_VERB_FAMILIES;
10
17
  },
11
18
  engineOutputSources() {
12
19
  return [pulumiStackOutputSource];
@@ -68,7 +68,9 @@
68
68
  * each step's op, URN, provider, id, inputs before and after, `diffReasons`,
69
69
  * `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
70
70
  * `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
71
- * outputs, which at preview are the last deployment's or unknown.
71
+ * outputs, which at preview are the last deployment's or unknown. For a
72
+ * member on the resource lock grain the `pulumi-up` capability passes
73
+ * `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
72
74
  *
73
75
  * The fixtures under `testdata/pulumi-preview` are recorded from pulumi
74
76
  * 3.272.0 against the plunge corpus's aws-ts program on floci (#4475). What
@@ -88,7 +90,7 @@
88
90
  * `change-set` types, `lifecycle/plan-digest`, `pulumi-urn` and `./values`,
89
91
  * so it bundles without TypeScript, node's process modules or zod (#3421).
90
92
  */
91
- import type { ChangeSetPart } from "@intentius/chant/change-set";
93
+ import type { ChangeSetAction, ChangeSetDisruption, ChangeSetPart } from "@intentius/chant/change-set";
92
94
  import { type Json } from "./values.js";
93
95
  /** One resource state on a preview step, `apitype.ResourceV3`, as far as the adapter reads it. */
94
96
  export interface PulumiStepState {
@@ -127,12 +129,31 @@ export interface PulumiExportedResource {
127
129
  }
128
130
  /** The resources of a `pulumi stack export` document (`deployment.resources`). Not one: an empty list. */
129
131
  export declare function pulumiExportResources(doc: unknown): PulumiExportedResource[];
132
+ /** Step ops that change nothing: what {@link PulumiPreviewDigestOptions.changedOnly} leaves out of the digest. */
133
+ export declare const PULUMI_UNCHANGED_OPS: ReadonlySet<string>;
134
+ export interface PulumiPreviewDigestOptions {
135
+ /**
136
+ * Leave `same` and `read` steps out (#4541, ruling B): set for a member on
137
+ * the resource lock grain, so a pull request that lands on other resources
138
+ * of the stack does not move this one's digest. plunge's plan stamp still
139
+ * refuses a moved record among the steps that change.
140
+ */
141
+ changedOnly?: boolean;
142
+ }
130
143
  /**
131
144
  * The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
132
145
  * what each step proposes, sorted by URN and op, so two renderings of one
133
- * preview agree. See the module comment for what it leaves out.
146
+ * preview agree. See the module comment for what it leaves out. With
147
+ * `changedOnly`, `same` and `read` steps are left out too, under the label
148
+ * `pulumi-preview-changed`, so the two digests never collide.
134
149
  */
135
- export declare function pulumiPreviewDigest(preview: unknown): string;
150
+ export declare function pulumiPreviewDigest(preview: unknown, opts?: PulumiPreviewDigestOptions): string;
151
+ /** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
152
+ export declare function pulumiStepsAction(steps: PulumiStep[]): {
153
+ action: ChangeSetAction;
154
+ disruption?: ChangeSetDisruption;
155
+ importing?: true;
156
+ } | undefined;
136
157
  export interface PulumiPreviewPartInput {
137
158
  /** The workspace member's name, which names its graph nodes (`<member>/<key>`). */
138
159
  member: string;
@@ -150,6 +171,8 @@ export interface PulumiPreviewPartInput {
150
171
  targeted?: boolean;
151
172
  /** The preview failed: the member is failed with this error, and keeps whatever steps it printed. */
152
173
  error?: string;
174
+ /** Digest options (#4541, ruling B): `changedOnly` for a member on the resource lock grain. */
175
+ digest?: PulumiPreviewDigestOptions;
153
176
  }
154
177
  /**
155
178
  * A Pulumi member's part, from its `pulumi preview --json`. Throws when the
@@ -1 +1 @@
1
- {"version":3,"file":"change-set.d.ts","sourceRoot":"","sources":["../src/change-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AAEH,OAAO,KAAK,EAMV,aAAa,EACd,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAA6E,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhH,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;CAC9E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,EAAE,GAAG,SAAS,CAGlE;AAED,+EAA+E;AAC/E,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,EAAE,CAI5E;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAe5D;AAyFD,MAAM,WAAW,sBAAsB;IACrC,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,aAAa,CAyH9E"}
1
+ {"version":3,"file":"change-set.d.ts","sourceRoot":"","sources":["../src/change-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AAEH,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EAGnB,aAAa,EACd,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAA6E,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhH,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;CAC9E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,EAAE,GAAG,SAAS,CAGlE;AAED,+EAA+E;AAC/E,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,EAAE,CAI5E;AAID,kHAAkH;AAClH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAA6B,CAAC;AAEnF,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,0BAA+B,GAAG,MAAM,CAgBnG;AAID,+GAA+G;AAC/G,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IAAC,SAAS,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CAElJ;AAsFD,MAAM,WAAW,sBAAsB;IACrC,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,aAAa,CAyH9E"}
@@ -68,7 +68,9 @@
68
68
  * each step's op, URN, provider, id, inputs before and after, `diffReasons`,
69
69
  * `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
70
70
  * `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
71
- * outputs, which at preview are the last deployment's or unknown.
71
+ * outputs, which at preview are the last deployment's or unknown. For a
72
+ * member on the resource lock grain the `pulumi-up` capability passes
73
+ * `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
72
74
  *
73
75
  * The fixtures under `testdata/pulumi-preview` are recorded from pulumi
74
76
  * 3.272.0 against the plunge corpus's aws-ts program on floci (#4475). What
@@ -104,13 +106,18 @@ export function pulumiExportResources(doc) {
104
106
  return list.filter((r) => isObject(r) && typeof r.urn === "string" && typeof r.type === "string");
105
107
  }
106
108
  const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
109
+ /** Step ops that change nothing: what {@link PulumiPreviewDigestOptions.changedOnly} leaves out of the digest. */
110
+ export const PULUMI_UNCHANGED_OPS = new Set(["same", "read"]);
107
111
  /**
108
112
  * The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
109
113
  * what each step proposes, sorted by URN and op, so two renderings of one
110
- * preview agree. See the module comment for what it leaves out.
114
+ * preview agree. See the module comment for what it leaves out. With
115
+ * `changedOnly`, `same` and `read` steps are left out too, under the label
116
+ * `pulumi-preview-changed`, so the two digests never collide.
111
117
  */
112
- export function pulumiPreviewDigest(preview) {
118
+ export function pulumiPreviewDigest(preview, opts = {}) {
113
119
  const steps = (pulumiSteps(preview) ?? [])
120
+ .filter((s) => !opts.changedOnly || !PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
114
121
  .map((s) => ({
115
122
  op: s.op ?? null,
116
123
  urn: s.urn ?? null,
@@ -123,10 +130,13 @@ export function pulumiPreviewDigest(preview) {
123
130
  detailedDiff: s.detailedDiff ?? null,
124
131
  }))
125
132
  .sort((a, b) => byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`));
126
- return computePlanDigest("pulumi-preview", steps);
133
+ return computePlanDigest(opts.changedOnly ? "pulumi-preview-changed" : "pulumi-preview", steps);
127
134
  }
128
135
  const REPLACE_OPS = new Set(["replace", "create-replacement", "delete-replaced"]);
129
136
  /** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
137
+ export function pulumiStepsAction(steps) {
138
+ return actionOf(steps);
139
+ }
130
140
  function actionOf(steps) {
131
141
  const ops = steps.map((s) => s.op ?? "");
132
142
  if (ops.includes("import-replacement"))
@@ -339,7 +349,7 @@ export function pulumiPreviewPart(input) {
339
349
  ...scope,
340
350
  status: failed ? "failed" : "planned",
341
351
  ...(failed ? { error: input.error } : {}),
342
- planDigest: failed ? null : pulumiPreviewDigest(input.preview),
352
+ planDigest: failed ? null : pulumiPreviewDigest(input.preview, input.digest),
343
353
  holes,
344
354
  },
345
355
  entries,
@@ -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"}