Skip to content

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 diff returns 1 when there is anything to do. Use it as a drift gate.
  • cla import-plan returns 0 even when there are pending changes, and 2 only when the files are bad. Use it as a validity gate.
  • cla fmt --check returns 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

files

Catalog

Resolver

Plan

live objects

confirm skipped with -y

Apply

link refs second pass

both sides normalised the same way before anything is compared import-plan and diff stop here Any CONFLICT or ERROR entry refuses the whole apply (HCL250). There is no rollback — the plan-and-confirm step is the safety.

  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 --prune the 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 --prune in the same run do not undo each other.
  • Addresses come from the encoder, not from a hand-rolled name lookup.

TEST to PROD

TEST install cla -c test export

git repository cla-objects/test reviewed, tagged

PROD install cla -c prod import

cla diff — has PROD drifted from the files?

source of truth the review gate target Addresses mean the same thing on both sides; ids do not exist in the middle box.

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 omit and 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 --prune on 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 export output, 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-check lists 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.