Command line
Five commands. Per-command reference pages exist at
cla export, cla import,
cla import-plan, cla diff and
cla fmt; this page is the operational view across all of
them.
cla -c prod export --path ./cla-objects/prod
cla fmt --recursive ./cla-objects/prod
cla -c prod diff ./cla-objects/prod
cla -c prod import-plan ./cla-objects/prod --diff --out plan.json
cla -c prod import ./cla-objects/prod -y
Flag matrix¶
Flags are not shared between commands even when they share a name. This table is the fastest way to avoid passing one that will be silently ignored.
| Flag | export | import | import-plan | diff | fmt |
|---|---|---|---|---|---|
| positional paths | ignored | yes | yes | yes | yes |
--filter PATTERN |
selects what is encoded | — | — | narrows what is shown | — |
--path DIR |
yes | — | — | — | — |
--file NAME |
yes | — | — | — | — |
--pack |
yes | — | — | — | — |
--split[=LIST] |
yes | — | — | — | — |
--stdout |
print instead of writing | — | — | — | print one formatted file |
--internal |
yes | yes | yes | — | — |
--external LIST |
yes | — | — | — | — |
--all-external |
yes | — | — | — | — |
--no-deps |
yes | — | — | — | — |
--no-moved |
yes | yes | yes | — | — |
--all-attributes |
rules only | — | — | — | — |
--secret MODE |
yes | — | — | — | — |
--secret-key KEY |
yes | yes | yes | yes | — |
--indent N |
yes | — | — | — | yes |
--lax |
— | yes | yes | yes | — |
--prune |
— | yes | yes | — | — |
--allow-missing |
— | yes | yes | — | — |
--dry-run |
— | yes | accepted, inert | — | — |
--diff |
— | yes | yes | — | — |
-y |
— | yes | accepted, inert | — | — |
--out FILE |
— | accepted, inert | yes | — | — |
--check |
— | — | — | — | yes |
--recursive |
— | — | — | — | yes |
cla export takes no positional arguments — cla export ./dir silently
ignores the path. Use --path.
Only cla fmt rejects an unknown option (exit 80). The others ignore one.
cla diff constructs the importer with exactly four settings, so anything else
typed on its command line is dropped: cla diff --internal,
cla diff --allow-missing and cla diff --prune all do nothing and report
nothing.
Two options are declared but never read
cla import --plan FILE and cla import -p exist as options and have no
effect. The plan-writing option is --out, and it is only honoured by
cla import-plan. cla export --deps and cla export --moved are dead
too; only the --no- spellings work.
cla export --no-file is worse than inert: it prints Option --no-file
requires the object store, which is not built yet and exits 2 before
anything runs.
Default directories¶
| Command | With -c prod |
With no config |
|---|---|---|
cla export |
./cla-objects/prod |
./cla-objects |
cla import, cla import-plan, cla diff |
./cla-objects/prod |
./cla-objects |
cla fmt |
./cla-objects, non-recursive |
./cla-objects, non-recursive |
The environment name is the config name with any directory and extension
stripped: -c /etc/cla/prod.yml gives prod. Failing a -c, CLARIVE_CONFIG,
CLA_ENV and CLARIVE_ENV are consulted in that order. The directory is
scoped by environment so that a TEST export and a PROD export cannot quietly
overwrite each other in one checkout.
cla fmt does not follow the environment scope
cla fmt uses the flat cla-objects directory and does not descend into
subdirectories by default. After cla -c prod export, a bare cla fmt
finds no *.hcl at the top level and exits 2 with No HCL files found.
Use cla fmt --recursive, or name the directory.
Exit codes¶
| Code | export |
import |
import-plan |
diff |
fmt |
|---|---|---|---|---|---|
| 0 | exported | applied, or nothing to do | plan is clean | no differences | formatted, or already canonical |
| 1 | — | you answered no | — | there are differences | --check found files to format |
| 2 | nothing matched, unwritable file, encoder error | no plan, unreadable path, blockers | same | same, or blockers | unreadable, unwritable or unparsable |
| 3 | — | an apply failed partway | — | — | — |
| 80 | — | — | — | — | unknown option |
The distinction that matters in CI:
cla diffreturns 1 when there is anything to do. Use it as a drift gate.cla import-planreturns 0 even when there are pending changes, and 2 only when the files are bad. Use it as a validity gate.cla fmt --checkreturns 1 when a file is not canonical. Use it as a style gate.
--filter on cla diff only narrows what is shown. A conflict or an
unreadable file is still exit 2 even when the filter hides the object it came
from: it is a reason not to import, whether or not you asked to look at it.
The plan¶
1. CREATE generic_server.back-office (15 attrs)
2. UPDATE generic_server.front-desk (1 attr)
3. RENAME generic_server.web01 <- generic_server.oldweb
4. DELETE generic_server.gone -- Not present in the imported files
1 create, 1 update, 1 rename, 1 delete
Apply these 4 change(s)? [y/N]
Line format is NNN. ACTION address, then (N attrs) when there are attribute
changes, then <- old-address when the entry carries a rename source, then
-- reason. The counts line follows the fixed action order, lowercased, with
zero counts omitted. UNCHANGED entries are not listed, only counted.
| Action | Meaning |
|---|---|
CREATE |
nothing at that address yet |
UPDATE |
it exists and some attribute differs |
UNCHANGED |
it already matches |
RENAME |
a moved {} block says it used to be called something else, and that something else is what is actually there |
DELETE |
a removed {} block, or --prune |
SKIPPED |
it depends on something missing, and --allow-missing was given |
CONFLICT |
declared twice in the files, or the live match is ambiguous |
ERROR |
it could not be understood |
The confirmation count excludes UNCHANGED and SKIPPED.
--diff adds two lines per changed attribute:
- hostname = "front.example.invalid"
+ hostname = "front-new.example.invalid"
- password = (sensitive)
+ password = (sensitive)
undef renders as (none), a list as [a, b], any other structure as {...}.
A sensitive attribute reads (sensitive) on both sides. By the time a plan
exists the secret has been decoded back to the text that would be written, so
printing it would put a live credential on your terminal and in the log of
whatever ran the command. You still see that it changed, which is what the
plan is for.
Plan files¶
cla import-plan --out plan.json writes the plan as canonical, pretty 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 } ] }
]
}
Stable: the same configuration and the same files always produce the same JSON, so it can be committed, reviewed or attached to a change request. If the file cannot be written the command exits 2 rather than reporting a success nobody can verify.
Secrets¶
--secret applies to cla export only. Accepted values are b64 (the
default), plain, omit and encrypt.
| Mode | Emits | On import |
|---|---|---|
b64 |
password = b64("aHVudGVyMg==") |
decoded, no key needed |
plain |
password = "hunter2" |
as written |
omit |
nothing | the target keeps what it has |
encrypt |
password = enc("U2FsdGVkX1...") |
needs --secret-key |
--secret encrypt without a usable key is HCL104 Cannot
encrypt secrets: no cipher available. Use --secret=b64 instead and exit 2 —
but only if the selected objects actually contain a sensitive attribute. An
encrypt-mode export of a secret-free selection exits 0 silently.
--secret is not validated
An unrecognised value falls through to b64. --secret=plaintext,
--secret=none and --secret=xyzzy all silently produce base64, which
looks like success and is not what was asked for.
b64 is encoding, not encryption. It keeps a password out of plain sight in a
file someone might paste into a ticket, and nothing more.
--secret-key on the reading side (import, import-plan, diff) decrypts
enc(). Without it, HCL209; with the wrong one,
HCL210. Either way the plan is refused, exit 2.
Sensitive attributes absent from a file are kept as they are on the target. See Syntax.
Dependency closure¶
Whatever a selection points at comes with it. Exporting a server that has a proxy also exports the proxy, transitively, so the resulting set can always be imported somewhere else without dangling references.
$ cla -c prod export --filter 'project.foo'
12 object(s) exported, 9 pulled in as dependencies
written 12
unchanged 0
into /home/me/cla-objects/prod
--no-deps turns the closure off, and the summary still reports how many
objects would have been pulled in.
The closure runs after labels are assigned for the whole installation, so the addresses it reports already carry their clash suffixes.
A file whose content has not changed is left alone, so an export into a git working copy only touches what actually differs.
--pack and --file do not print the output path
The into line is omitted for a packed export. The file is still written
where you asked.
Pruning¶
--prune proposes deleting live objects absent from the files, scoped to the
families the files actually talk about — <family>, resource/<CI class>,
rule/<rule type>. Importing only roles can never propose deleting a resource.
Guards, all of them load-bearing:
- A requested path that does not exist stops the command before anything is
planned. Carrying on with the paths that do exist would give a plan built
from part of your configuration, and under
--prunethe missing part reads as "delete these" — so a mistyped path would delete exactly the objects it was meant to import. - An object whose address has no name segment is skipped. An empty name would
make
family.match nothing, which also reads as "delete it". - Any address an entry was renamed away from is excluded, so a
moved {}block and--prunein the same run do not undo each other. - Addresses come from the encoder, not from a hand-rolled name lookup.
TEST to PROD¶
A full walkthrough, from nothing.
1. Baseline TEST into a git working copy.
cd /srv/clarive
cla -c test export --path ./cla-objects/test
cla fmt --recursive ./cla-objects/test
git add cla-objects/test
git commit -m 'baseline: TEST configuration'
Everything exportable is written, one file per object. Secrets are base64. No database ids anywhere.
2. Narrow it, if you only want part of the installation.
cla -c test export --path ./cla-objects/test \
--filter 'project.foo,pipeline.deploy-foo' \
--filter '!generic_server.scratch-*'
The dependency closure pulls in whatever those objects point at, so the result still imports cleanly.
3. Check the files are canonical and parse.
cla fmt --check --recursive ./cla-objects/test
Exit 1 means a file needs formatting; exit 2 means one does not parse. Neither should reach a review.
4. See what PROD would get.
cla -c prod import-plan ./cla-objects/test --diff
Read the plan. Exit 2 means the files or the target cannot support the import at all — read the diagnostics before anything else. Exit 0 with entries listed means the plan is sound and there is work to do.
5. Keep a record.
cla -c prod import-plan ./cla-objects/test --out migration-plan.json
git add migration-plan.json && git commit -m 'planned migration to PROD'
6. Apply.
cla -c prod import ./cla-objects/test
You are shown the plan and asked to confirm. -y skips the question for an
unattended run; --dry-run computes and shows everything and writes nothing.
7. Confirm PROD now matches.
cla -c prod diff ./cla-objects/test
Exit 0 means no differences.
8. Keep it that way. In CI, on every commit:
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'
Things that differ between the two installations¶
- Secrets. TEST credentials are rarely PROD credentials. Export with
--secret omitand the target keeps its own; the structure still travels. - Environment-specific values. Hostnames, paths and endpoints that differ
legitimately are best excluded with
--filter '!...'and managed on each side, or kept in variables that are themselves environment-scoped. - Objects PROD has and TEST does not. They are left alone unless you pass
--prune. Do not pass--pruneon the first migration. - Release skew. If the two installations run different Clarive releases,
export with
--all-attributes. The compact form reads back identically only against the defaults table of the release that wrote it. Note that the flag currently covers rule operation parameters only, not CI class attributes — see Resources.
cla rule-check¶
Checks that every rule survives being written as HCL and read back. The Rule Designer's HCL view saves whatever its text reads back as, even when nobody touched it, so a rule that does not come back the same is changed by being saved there.
cla -c prod rule-check # every rule
cla -c prod rule-check --id 24,31 # those rules
cla -c prod rule-check --notes # and what only changes in notation
For each rule it runs the HCL view's own encode, checks and decode, lays the tree that comes back beside the stored one step by step, and compiles the generated code of both. Nothing is written.
ok rule 24 "Deploy foo" (pipeline)
FAIL rule 2 "Releases" (dashboard)
error lost at /1 dashlet.topic.number_of_topics "Foo" data.categories: [7,44,45] -> (absent)
2 rule(s) checked: 1 come back the same, 1 would be changed by a save from the HCL view
A rule fails when a step would come back as another operation, lose or change
a value its operation reads, switch on or off, or stop compiling; or when its
HCL has errors or does not read back at all. With --notes it also lists what
changes only in how it is written: opids a save issues anew, multi-line text
that comes back with LF endings and without trailing blanks, a form default
filled in where the stored step had none, and copies of values the operation
does not read.
Exit 0 when every rule comes back the same, 1 when any would be changed.
The usual failure is a value the encoder refuses to write (HCL106): an id with no address, often one pointing at something that has since been deleted. The export writes a comment in its place, and the text read back has no value there at all.
Troubleshooting¶
Every diagnostic carries a code. The full table is on the Diagnostics page; these are the ones you will actually hit.
| Symptom | Code | Fix |
|---|---|---|
Unknown block type 'Foo' |
HCL200 | the class is second-order — write resource "Foo" "bar" |
Block type 'category' is not supported yet |
HCL201 | that family addresses but does not import; remove the block |
Attribute 'hostnmae' is not declared by 'generic_server' |
HCL205 | a typo, or a legitimately unknown field — --lax silences it |
Attribute 'groups' expects references, not a literal |
HCL206 | drop the quotes: [group.developers], not ["developers"] |
Write group membership on each user, as groups = [...], not as 'members' on the group |
HCL222 | move the membership onto each user block as groups |
Attribute 'groups' lists user groups, and project.foo is not one |
HCL223 | a user can only be in group.<slug> objects |
Cannot decode enc(): no secret key given |
HCL209 | pass --secret-key |
Cannot decode enc(): wrong key |
HCL210 | wrong key, or the value was never encrypted |
Write this as user "..." {}, not as a resource |
HCL214 | use the user or group keyword |
Duplicate address X, already declared at ... |
HCL221 | the same object declared twice, often once in each spelling |
Unresolved reference X in Y |
HCL230 | add the target to the files, create it on the target, or pass --allow-missing |
Refused to write 14 for id_category |
HCL106 | an id with no address; the attribute was dropped from the export |
The value of code has generic_server-7 written inside it |
HCL107 | an id inside a script body; rewrite it to use a stash variable |
| Unknown rule operation | HCL304 | no operation registered here has that keyword — a typo, or a plugin this installation does not load |
Bare identifier bar used as a value |
HCL017 | quote it, or make it a real address |
| Unsupported HCL construct | HCL020 | no expressions; write the value out |
Two failure modes are easy to miss because they happen on the way out, and by then the file already looks fine:
- A grant that got narrower. A bound was refused as an unresolvable id and
was not written. HCL106 appears in the
cla exportoutput, not the import's. - A rule that does not come back the same. A value refused as an
unresolvable id is not in the file, so importing it, or saving the rule
from the Rule Designer's HCL view, leaves the step without it.
cla rule-checklists every rule this happens to.
Read the export summary as carefully as the import plan.
Failure partway¶
There is no rollback. Mongo offers no transaction across documents here, so the plan-and-confirm step is the safety, not a pretend undo. If an apply fails partway, the run stops and reports exactly what was applied and what was not:
2 applied, 1 failed, 3 pending
generic_server.front-desk: Cannot connect to the agent
Not applied: generic_server.back-office
Exit 3. Fix the cause and re-run; everything already applied plans as
UNCHANGED the second time.
cla help¶
cla help <command> renders the markdown page for that command.
cla <command> -h prints an auto-generated option list from the command's
attributes — which is why dead options like --plan appear there.
cla help import-plan shows the wrong page
The command name is truncated at the first dash before the document is
looked up, so cla help import-plan renders cla-import.markdown. The
cla import-plan page is only reachable through the docs browser.