98 lines
5.5 KiB
Markdown
98 lines
5.5 KiB
Markdown
# 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](testing.md) 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.
|