Clarive
Clarive Release 7.22 is now available. Read the release notes or download it now.

Checking a Clarive Installation Against Its Files With cla diff

In Clarive 7.22 a build can compare production with the HCL files in Git and fail when someone has changed it by hand.

Once an installation's configuration is in Git, the files and the installation can drift apart. Someone fixes a hostname on PROD from the browser, or a colleague adds a step to a pipeline and never exports it. The files hold one thing and the installation now does another. Nothing breaks right away. It surfaces weeks later as an unexpected line in the plan of the next import, one that would put the old value back.

Clarive 7.22 has a command for finding that out early. cla diff is the third piece of the new HCL tooling, after the export and import covered in the previous post. This post is about it and the two checks that sit next to it in a build pipeline.

What cla diff shows

cla diff reads a directory of HCL files, reads the installation and runs the same comparison cla import does. Instead of a plan, it prints a diff:

--- clarive/generic_server.front-desk
+++ local/generic_server.front-desk
-hostname = "front.example.invalid"
+hostname = "front-new.example.invalid"

The clarive/ side is what the installation holds, and the local/ side is what the files hold. With no path, cla -c prod diff reads cla-objects/prod, the directory cla -c prod export writes to.

An object that exists only in the files is reported as one an import would create. An object that exists only in the installation is reported too, and that is all. cla diff never proposes deleting anything. That is deliberate. --filter narrows the report with the same patterns cla export takes, so cla diff ./cla-objects --filter 'role.*' looks only at roles.

The comparison is about meaning. Before anything is compared, both sides are put into the same form. An attribute the file leaves out is read as its default, and a reference is compared as an address instead of as a stored id. A file that omits a setting the installation holds at its default value is not a difference, and neither is a server that carries a different internal number on each machine, since the comparison only ever sees it by its address. What shows up is what would change.

Secrets are compared as well, and a change to one is reported, but the value shows as (sensitive) on both sides. The files hold the credential encoded, and to compare it cla diff has to decode it first. Printing it at that point would put a live credential on the terminal and into the log of whatever ran the command.

The exit codes are the ones git diff uses, so the command drops into an existing pipeline unchanged. It exits with 0 when there are no differences, 1 when there are and 2 when something couldn't be read or understood. The documentation's example is a single line:

cla diff ./cla-objects || echo "configuration has drifted"

A conflict or an unreadable file still exits with 2 when --filter hides the object it came from. It is a reason not to import, whether or not anyone asked to look at that object.

A plan that can be filed

cla import-plan answers a different question. cla diff tells a build whether the installation matches the files. import-plan shows what an import would do about it, and whether it could do it at all. It is the import itself with the apply step never constructed, so what it prints is what cla import would do, worked out the same way.

  1. CREATE    generic_server.back-office (15 attrs)
  2. UPDATE    generic_server.front-desk (1 attr)
1 create, 1 update, 2 unchanged

Its exit codes are chosen for another job. import-plan exits with 0 when the plan is sound, whether or not it has changes in it, and with 2 when the files can't be read or the plan has conflicts. That makes it a validity check. A pipeline can fail on a broken file without failing every time there is legitimate work waiting to be imported.

With --out, it also writes the plan as JSON. This one is from the documentation:

JSON
{
  "counts": { "CREATE": 1, "UNCHANGED": 4 },
  "entries": [
    { "address": "generic_server.back-office",
      "action": "CREATE",
      "changes": [ { "attr": "hostname", "old": null, "new": "back.example.invalid" },
                   { "attr": "password", "old": null, "new": "(sensitive)", "sensitive": 1 } ] }
  ]
}

The file is stable. The same configuration and the same files always produce the same JSON, so it can be committed next to the files or attached to a change request. Secrets appear in it as (sensitive), with a "sensitive": 1 beside them as the reason. If the file can't be written, the command exits with 2 instead of reporting a success nobody could check.

The walkthrough in the documentation files one before a migration to PROD:

cla -c prod import-plan ./cla-objects/test --out migration-plan.json
git add migration-plan.json && git commit -m 'planned migration to PROD'

Whoever approves the change sees every address, the action on each and every attribute that would change, in a file that will read the same next month, when somebody wants to know what the migration was supposed to do.

One format for every file

The third check is about the files themselves. cla fmt rewrites HCL into the canonical style, the one cla export writes and the Rule Designer saves. People edit these files by hand, and hand edits wander in small ways. An extra space before an =, two blank lines where one would do, a list broken across lines. None of it changes what the file means, and all of it shows up in git diff and buries the lines that matter.

The style has few rules. Nesting is two spaces unless --indent asks for another width. The = signs line up inside each run of adjacent attributes. Top-level blocks are separated by exactly one blank line, and lists stay on one line while they fit. Heredocs keep their contents exactly as written, and comments stay where they were attached and in the style they were written in, so a # is never turned into a //.

cla fmt --check writes nothing. It prints the files that would change and exits with 1 if there are any. A file that doesn't parse is never rewritten. The command prints the file, line and column where the problem is. Then it moves on to the next file, and the run ends with exit code 2.

One detail catches people out. cla fmt doesn't follow the per-environment directories that cla export and cla import use. After cla -c prod export the files are in cla-objects/prod, and a bare cla fmt looking in cla-objects finds nothing, reports that there are no HCL files and exits with 2. Name the directory or pass --recursive.

Three lines in CI

The command line guide puts the three together in a job that runs on every commit to the repository holding the files:

cla fmt --check --recursive ./cla-objects/test || exit 1
cla -c prod import-plan ./cla-objects/test || exit 1
cla -c prod diff ./cla-objects/test || echo 'PROD has drifted'

Each line uses its exit code differently. The first fails the build when a file isn't in canonical form or doesn't parse, so neither kind reaches a review. The second fails it when the files couldn't be imported into PROD at all, because an object is declared twice or a reference points at something that isn't there. A plan with pending changes passes, since pending changes are the reason for the commit. The third line fails nothing. It prints a warning when PROD and the files disagree.

That third line is the one that catches a change made by hand. Run on a schedule as well as on every commit, it reports drift even in a week when nobody commits anything. When it fires, someone has to decide which side is right. If the change on PROD should stay, it belongs in the files too, and a commit can carry it. If it shouldn't, the next import puts the files' version back, and the plan shows exactly that before anyone confirms it.

For a first migration the guide runs cla diff straight after the import. An exit code of 0 means PROD now matches the files. From then on the same command, unchanged, is the check that it still does.

The guide also has a table of exit codes for every HCL command, which is the page to keep open while writing the job. If you'd like help setting up these checks for your own installations, get in touch.

We use cookies to improve your experience. By continuing to visit this site you agree to our use of cookies. Learn more