Files
ansible-workspace-dnd/docs/ansible.md

5.5 KiB

Ansible House Style

This policy is normative for designing, implementing, and reviewing Ansible roles and collections in this workspace.

Role Implementation

  • Always use the smallest clear sequence of declarative tasks that reaches the required end state.
  • Prefer ansible.builtin modules where they directly fit the task. community.general is an approved, stable dependency and its modules may be selected freely when appropriate; declare and pin the collection wherever a role or collection depends on it.
  • Do not strengthen the specification. Implement the requested invariants without inventing additional guarantees for integrity, provenance, drift recovery, corruption detection, rollback, or similar behavior.
  • Prefer convergence over state inspection. Declare what should be true and let the relevant module reconcile it.
  • Trust Ansible's abstraction boundary. A module that manages state owns the checks necessary to manage that state. Inspect it beforehand only when the observed state is independently required.
  • State inspection is justified when its result is necessary to decide whether an imperative operation must run, such as checking whether an arbitrary build command's output exists.
  • Production role tasks establish required application state; they do not test the role's own implementation. Put implementation verification in repository-local tests unless the specification explicitly requires runtime validation during convergence.
  • For an unavoidable command or shell task, declare the operation's change semantics as required by ansible-lint. A conditionally executed mutation may use changed_when: true; a read-only command should use changed_when: false. This describes an imperative operation and does not replace the idempotence of a state-management module.
  • Apply become: true to individual tasks that require elevated privileges. Do not require callers to escalate an entire play merely to use a role.
  • Complexity requires a requirement. Every additional task, conditional, registered variable, or state probe must correspond to behavior the role is required to provide.
  • Persisted role-private state is a design smell. Fingerprints, checksum databases, stamps, manifests, and similar bookkeeping require specific justification.
  • Assume convergence first. Before adding machinery for an edge case, determine whether rerunning the ordinary desired-state operations already repairs it.
  • Consider whether a conditional task or a notified handler best expresses the dependency. A when condition keeps triggers visible at the action site but grows with their number. Notifications keep the action independent of the number of notifiers, but distribute the control flow and defer execution until the next handler flush. An explicit ansible.builtin.meta: flush_handlers affects all pending handlers in the play.
  • Treat rakestraw-ansible as a style reference for direct tasks, shallow control flow, and concise variable use, while retaining current practices such as fully qualified collection names.

Application Configuration Interfaces

  • Expose application configuration through documented, field-specific role variables suitable for host_vars, group_vars, play variables, and composing roles.
  • Give structurally required fields concrete defaults. Define optional application settings as null unless the role intentionally owns the corresponding policy.
  • Treat a null optional variable as "omit this field from the rendered configuration." Do not copy documented upstream defaults into role defaults merely to write them back to the application.
  • Let the application supply its built-in defaults for omitted fields. This avoids freezing a duplicate defaults database in Ansible and permits upstream default changes to take effect naturally.
  • Use is not none when conditionally rendering optional values. Do not use truthiness, because false, zero, an empty string, or an empty collection may be an intentional value.
  • Emit a nested configuration section only when at least one of its descendant variables is non-null.
  • Serialize strings and collections with appropriate Ansible or Jinja filters. Do not construct YAML quoting manually.
  • Keep argument specifications aligned with the public variables. Optional nullable variables are not required; use a permissive type only when the native application accepts more than one scalar type.
  • Prefer one field-specific interface. Do not provide a parallel raw mapping, template override, or merge mechanism unless a concrete requirement defines its precedence and behavior.
  • Application roles expose native configuration choices. The suite-level role supplies cross-application and deployment-specific policy through those variables.
  • Manage the resulting file with one ordinary ansible.builtin.template task. Do not add configuration assembly facts, preliminary file inspection, or production validation unless independently required.
  • Configuration tests should use explicitly supplied representative values to exercise conditional rendering, null omission, intentional falsey values, native configuration validity, and caller-facing composition boundaries. They should follow the testing house style rather than snapshotting the current default document.

Role defaults define what Ansible intends to manage, not a mirror of every application default. A concrete default means the role deliberately owns that policy; a null default leaves the policy with the application.