chant change-set summary
Synopsis
Section titled “Synopsis”chant change-set summary <change-set.json> [--format text|json|markdown] [--limit <chars>]Description
Section titled “Description”chant change-set summary reads a change-set document and groups its members by the change each one plans (#3188). A module bump across two hundred roots reads as a few groups. Every destroy, replacement, failed member and hole is listed by name, outside any group. Reviewers read the summary. No gate binds it; a gate binds the document’s digest.
The document is usually the document a composeChangeSet step returned. The command reads that one file. It needs no project, no plugins and no network.
$ chant change-set summary change-set.json5 members: 2 groups, 0 destroys or replacements.
Group 79cd3ea7ffaf: 4 members, identical change: + module.shared.aws_sqs_queue.events ~ module.shared.aws_iam_role.app: tags, tags_all ~ module.shared.aws_s3_bucket.data: replication_configuration, tags, tags_all ~ module.shared.aws_sqs_queue.work: tags, tags_all, visibility_timeout_seconds members: estates/e01, estates/e02, estates/e03, estates/e05
Group 36dca470b879: 1 member (outlier), group 79cd3ea7ffaf's change plus: ~ module.shared.aws_sqs_queue.extra: tags, tags_all members: estates/e04That is choudoufu’s real plan of a shared-module bump over five estates, where e04 owns one more queue.
A document with one member is grouped by instance instead. Instances group only with instances of the same for_each or count expansion.
What groups together
Section titled “What groups together”Two members group when their normalized changes are equal as multisets. A normalized change keeps its action, its address and the attributes it writes, with these parts taken out:
- every instance key in the address, so
logs[0]andlogs["eu"]are bothlogs - the member’s own names wherever they stand as a whole word: its scope (the estate or environment) and the last segment of its name, each also with
-and_swapped - for an instance, its own instance keys, under the same whole-word rule
- choudoufu’s
tofu-estatemarker when it holds the member’s own estate, and itstofu-addressmarker when it holds the resource’s own address
A name is stripped from module call names and the resource name, and never from the resource type. A name shorter than three characters must also touch a - or _, so the key 1 is stripped from web-1 and kept in 10.1.0.0/16. Nothing else is stripped. Two instance sizes are two changes. So are two CIDR blocks, two policy documents, and two tag values that do not carry the member’s name. no-op entries are dropped. A sensitive attribute is compared by its path only, because the document carries no value for it.
chant and choudoufu’s live-summary follow the same rules. Each keeps its own code, and both run one table of test vectors in CI: packages/core/src/__fixtures__/plan-summary/normalization-vectors.json in this repository. A change to the rules changes that file first.
What is never grouped
Section titled “What is never grouped”- A member that failed to plan is listed with its reason and is in no group.
- Every
deleteandreplacein the document is listed by member and real address, failed members’ included. A destroy is also part of its member’s change, so a member that destroys something never groups with one that does not. - A forget is the plan action
forget. It happens when aremovedblock ordestroy = falsestops tracking an object that keeps running. It has its own list and never counts as a destroy. A member that forgets an object does not group with one that destroys it. - An import (an
importblock) is listed on its own with what else the plan does to the object, which is nothing when the import changes nothing. An import is part of its entry’s change, so it never groups with the same change made without one. - A triggered action comes from a Terraform
actionblock, in the plan’saction_invocations. It is listed as a side effect of apply and names its trigger. Members that run different actions do not group. - Every hole is listed with its member and the planner’s reason. A hole is an address the planner could not read.
Output
Section titled “Output”--format text is the default. It prints the named lists first and the groups after them.
--format json (or --json) prints the summary document. It follows https://intentius.io/chant/schemas/workspace/plan-summary/v1/plan-summary.schema.json, shipped at src/workspace/plan-summary.schema.json in @intentius/chant.
| Field | Holds |
|---|---|
$schema, contract | the schema’s $id, and 1 |
changeSet | the digest of the change-set document summarized |
unit | member, or instance for a one-member document |
units | the number of members, failed ones included, or of changing instances |
groups | largest first, each { id, resource, units, outlier, noChanges, changes, extends, plus, destroys, sideEffects, provisional }; provisional groups come after the real ones |
failed | { member, scope, reason } for each member that failed |
destroys | { member, address, type, action, deposed } for each delete and replace |
forgets | { member, address, type, deposed } for each forget |
imports | { member, address, type, action } for each import |
sideEffects | { member, address, type, trigger, event } for each triggered action |
holes | { member, address, type, reason } |
A group’s id is twelve hex characters of a hash over its normalized changes. The same change gets the same id in every run and on every commit, so a report can link one group across commits. changes lists each distinct change once, with count for a change one member makes several times. When a group holds the whole of the largest group’s change and more, extends names that group and plus lists the rest. Sometimes a change has the same address and action as one in the largest group but other values. It then carries differsFrom, naming that group, and differsIn, naming the attributes. Values are never printed.
A group of provisional members, planned before what they read applied, has provisional: true. The flag is part of its id, so the same change planned for real and planned as a preview never share a group, and a provisional group is never described against a real one.
--format markdown prints a note for a merge request or pull request, of at most --limit characters counted as Unicode code points. The default is 65,536, the most a GitHub issue or pull request comment may hold; the API refuses a longer one. A GitLab note may hold 1,000,000 characters (GitLab Notes API), so --limit 1000000 suits a GitLab-only pipeline.
Its blocks keep the text order. When they do not fit, whole groups are dropped from the end. A last line then says how many groups and members it left out. The destroy, forget, import, triggered action, failure and hole lines are dropped only when they alone exceed the limit, from the end, and the last line counts them too.
Options
Section titled “Options”| Option | Meaning |
|---|---|
--format text|json|markdown | the output, text by default |
--json | the same as --format json |
--limit <chars> | the markdown note’s size, at least 200; 65,536 by default |
In a bundle
Section titled “In a bundle”The grouping is groupChangeSet in @intentius/chant/plan-summary, with renderPlanSummaryText and renderPlanSummaryMarkdown. It imports chant’s plan hashing code and nothing else, so a tool that runs it in CI from one bundled file needs no TypeScript toolchain. change-set-bundle.test.ts holds that.