Skip to content

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 resource family and the full attribute table for every CI class.
  • Roles, users and groups — the role, user and group families, 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

MongoDB master_doc, rule

Encoder ids → addresses

.hcl files canonical form

Decoder addresses → ids

plan, confirm, apply — into a different installation

cla export cla import

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.