Clarive HCL
Clarive HCL is the text representation of a Clarive installation's
configuration. cla export writes it, cla import reads it, and the HCL views
in the UI show one object at a time in the same form.
generic_server "web01" {
hostname = "web01.example.invalid"
connect_timeout = 60
proxy = generic_server.bastion
}
pipeline "deploy-foo" {
desc = "Deploys foo"
when = "promote"
step "PRE" {
sh "Restart app" {
host = generic_server.web01
run = "systemctl restart app"
on_error = "continue"
}
}
}
Two objects, one file, no database identifiers anywhere. generic_server.web01
in the pipeline resolves against whatever web01 is on the installation being
imported into.
Pages¶
- Syntax — lexical surface, expressions, the constructs that are refused, canonical formatting.
- Addresses and references — the address grammar,
both spellings, clash suffixes,
moved {},removed {}, filter patterns. - Resources — the
resourcefamily and the full attribute table for every CI class. - Roles, users and groups — the
role,userandgroupfamilies, grants and bounds. - Rules — rule types, the flat operation syntax, the op alias table, modifiers, conditions, form defaults.
- Other object families — what addresses but does not yet encode.
- Command line —
cla export,cla import,cla import-plan,cla diff,cla fmt. - In the UI — the Rule Designer HCL view and the HCL tabs.
- Diagnostics — every code, its message, and what to do about it.
Philosophy¶
Six decisions shape the whole language. Each one is a constraint on what you can write, and knowing the reason predicts the cases these pages do not cover.
Structure over attributes¶
Anything that is a list of things is a block, not an attribute holding a structure.
role "deploy-manager" {
grant {
action = "action.job.create"
bounds {
bl = "PROD"
}
}
}
not
role "deploy-manager" {
actions = [
{
action = "action.job.create"
bounds = [
{
bl = "PROD"
},
]
},
]
}
Blocks diff one line at a time, nest without quoting, and take comments.
A stored structure printed literally is a single opaque line in git diff.
The same reasoning produces the flat operation syntax in rules: a rule node is
stored as { key, attributes, data: {...} }, and it is written as one block
with one flat attribute list, because a data {} wrapper would be a level of
nesting that carries no information.
Addresses, not identifiers¶
Every reference is an address. A mid (generic_server-17), a role id, a
category sequence number: none of them appear in a file, in any position.
clax_agent "local" {
server = generic_server.localhost
}
An id means something only on the installation that issued it. A file full of ids is a backup, not a configuration; it can be restored but it cannot be moved. This is enforced rather than merely intended — an id-shaped value under an id-shaped key that cannot be resolved to an address is refused and the attribute is dropped, with warning HCL106. An id embedded in an opaque payload, such as a mid mentioned inside a Perl body, is reported with HCL107 rather than rewritten, because rewriting it would be a guess the import cannot undo.
Defaults stay out¶
An attribute sitting at the value its form or its class would have written anyway is not written.
The Rule Designer saves every field of an operation's form whether or not anyone touched it, so a step given four values is stored with eighteen. The compact form keeps the four. The omission is symmetric: the import puts back exactly what the export left out, from the same table. Absence in a file means "the default", never "delete it".
There is a worked before/after example at the end of the
cla export page.
The elision is release-local. A compact file reads back identically against the
defaults table of the release that wrote it; if the table changes, an old
compact file imports with the new defaults. --all-attributes removes that
dependency for rules — use it for archives and for files that will cross a
release boundary.
Two separate tables do this: Clarive::HCL::Schema::attr_specs for CI class
attributes, and Clarive::HCL::OpDefaults for rule operation parameters.
--all-attributes currently reaches only the second — see
Resources.
Deterministic output¶
Canonical form is the only form any Clarive tool produces. Two installations holding the same configuration produce byte-identical files. Attribute order is derived from the class, never from the stored document; clash suffixes are assigned by creation order; a file whose content has not changed is not rewritten.
The point is git diff. A representation that reorders on every export cannot
be reviewed, and a review that everyone skims is not a review.
Strict by default¶
An unknown block is an error. An unresolved reference is an error. A rule that does not compile is refused rather than written. Nothing is deleted because a file failed to mention it.
Each strictness has an escape hatch, and each hatch is opt-in:
| Situation | Default | Hatch |
|---|---|---|
| Attribute the class does not declare | warning HCL205, stored as is | --lax silences it |
| Reference to something not present | error HCL230, import stops | --allow-missing downgrades to a warning and skips the dependant |
| Live object absent from the files | nothing happens | --prune proposes a delete |
| One named object to delete | nothing happens | removed { at = ... } |
Credentials are never in the clear¶
Sensitive attributes are wrapped in b64() by default. 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. enc() with --secret-key is real
encryption.
A sensitive attribute absent from a file means "keep what is there", not "blank
it". That makes --secret omit a usable export mode: the structure travels and
the credentials stay put.
User credentials are never exported in any mode, and never imported in any
mode. A password on a user block is dropped with warning
HCL204.
What a round trip looks like¶
Both directions normalise the same way before anything is compared. An attribute left out of a file is understood as its class default, and a reference is compared as an address rather than as a stored id, so a file that omits an attribute the installation stores at its default is not a difference.
Quick start¶
cla -c myconfig export --path ./cla-objects
cla fmt --recursive ./cla-objects
git add cla-objects && git commit -m 'baseline'
Change a file, then:
cla -c myconfig diff ./cla-objects
cla -c myconfig import-plan ./cla-objects --diff
cla -c myconfig import ./cla-objects
What is not HCL¶
Clarive HCL is not Terraform HCL, and the overlap is smaller than it looks.
There are no variables, no expressions, no for, no conditionals, no dynamic
blocks, and ${...} inside a string is literal text rather than an
interpolation. The complete list of divergences is in
Syntax.