module

Krikri::RoleLoader

RoleLoader - resolves and loads roles: entries for a play.

A role is a directory (roles/<name>/) with a conventional layout: tasks/main.yml, handlers/main.yml, defaults/main.yml, vars/main.yml, meta/main.yml, files/, templates/

Role search path mirrors Ansible's common case: <playbook_dir>/roles/<name>, then ./roles/<name> relative to the working directory. Ansible also searches ANSIBLE_ROLES_PATH and a few other locations; not implemented.

Constants

ROLE_METADATA_ATTRIBUTES = Set {"allow_duplicates", "any_errors_fatal", "argument_specs", "become", "become_exe", "become_flags", "become_method", "become_user", "check_mode", "collections", "connection", "debugger", "dependencies", "diff", "environment", "galaxy_info", "ignore_errors", "ignore_unreachable", "module_defaults", "name", "no_log", "port", "remote_user", "run_once", "throttle", "timeout", "vars"}

ansible-core's RoleMetadata field-attribute set (taken from the installed core's RoleMetadata.fattributes, not from anything krikri implements): Ansible refuses any top-level meta/main.yml key outside it at role load, before any task runs, raising on the FIRST offending key in document order.

Class methods

argument_spec_file(role_dir : String) : String | Nil

Ansible accepts .yml/.yaml/.json for argument_specs (same extension list as find_main_file), in that order, and does NOT combine them or consult meta/main when one exists - only when none of the three exists do the specs come from the meta main file's argument_specs: key (stephdewit.sshca ships meta/argument_specs.yaml, issue #638).

Source
collections_paths(playbook_dir : String = ".") : Array(String)

Public: PluginManager's connection-plugin resolution (fix "the connection plugin 'X' was not found") searches the same controller collection locations for plugins/connection/<name>.py.

Source
find_main_file(dir : String) : String | Nil

Loads a role's defaults/ or vars/ - Ansible supports EITHER a single main.yml file OR a main/ directory of multiple *.yml files (same convention tasks/main/ uses), merged together in alphabetical filename order (later files win on a key collision - matching Ansible's own main/ directory loading, which reads files in sorted order and merges each into the accumulated dict). Only ONE of the two forms is ever present for a given role. Real bug found benchmarking kyl191.openvpn (round 160): its own defaults/main/openvpn.yml (no defaults/main.yml at all) was never read - load_vars_file alone always looked for exactly defaults/main.yml, silently returning an empty hash for a role using the directory form - every one of its own defaults (openvpn_server_network, openvpn_server_ipv6_network, ...) came back undefined, tripping the role's own "fail if both tunnel networks are disabled" validation check that Ansible never reaches (both are non-empty by default).

Source
load_include_vars_file(path : String) : Hash(String, JSON::Any)

include_vars (unlike defaults/ or vars/ loading) refuses a file whose top level is anything but a mapping - e.g. a tasks-style list - with "<path> must be stored as a dictionary/hash". An empty file is fine.

Source
load_roles(roles_yaml : Array(YAML::Any), play : Play, playbook_dir : String, source_map : YamlSourceMap | Nil = nil, roles_prefix : String = "roles") : Tuple(Array(Task), Array(Task))

Loads every entry in a play's roles: list (plus their meta/main.yml dependencies, recursively), in order. Returns {tasks, handlers} to prepend to the play - Ansible runs role tasks before the play's own tasks: (pre_tasks:/post_tasks: aren't implemented).

Source
load_single_role(name : String, invocation_vars : Hash(String, JSON::Any), invocation_tags : Array(String), play : Play, playbook_dir : String, tasks_from : String | Nil = nil, parent_names : Array(String) = [] of String, parent_paths : Array(String) = [] of String, parent_defaults : Hash(String, JSON::Any) = Hash(String, JSON::Any).new, parent_default_origins : Hash(String, VarOrigin) = Hash(String, VarOrigin).new, invocation_var_origins : Hash(String, VarOrigin) = Hash(String, VarOrigin).new, vars_from : String | Nil = nil) : Tuple(Array(Task), Array(Task))

Loads a single role by name (plus its meta/main.yml dependencies) - used by TaskExecutor#execute_include_role for include_role:, the dynamic (execution-time) counterpart to a static roles: entry. Each call gets its own fresh "seen" set, so - matching include_role's allow_duplicates: true default - repeated include_role calls for the same role name each load it again rather than being silently deduplicated the way a role listed twice under roles: would be. allow_duplicates: false on the include itself isn't honored (dedup only happens within a single call's own meta dependency chain).

Source
load_vars_file(path : String) : Hash(String, JSON::Any)

Public: TaskExecutor#execute_include_vars loads the same shape of YAML vars file that roles do, and must parse it identically (including Vault decryption of individual values).

Source
load_vars_file_main(dir : String) : Hash(String, JSON::Any)
Source
preload_static_import_role(name : String, play : Play, playbook_dir : String, tasks_from : String | Nil, vars_from : String | Nil = nil) : Nil

A STATIC import_role:'s target role is compiled by real ansible at PLAYBOOK LOAD time: the whole static import tree is expanded before any play runs, ignoring when:, so a removed bare include: anywhere in that tree refuses the whole run (rc=1) instead of surfacing as a per-task "Failed to load role" at runtime - or never surfacing at all when a false when: skips the branch (issue #598, ovirt.infra). This pre-loads the role's entry tasks (and handlers, which real loads with the role's data) purely to surface that refusal: only RemovedActionError propagates, every other parse failure keeps today's behaviour (the runtime load still owns it), and the parsed tasks are discarded, so runtime behaviour is unchanged. A role not found on disk pre-loads nothing - the runtime load reports that failure exactly as before.

Source
role_exists?(name : String, playbook_dir : String) : Bool
Source
role_search_display(playbook_dir : String, collections : Array(String) = [] of String) : String

The role search path list exactly as ansible-core's RoleDefinition._load_role_path builds and REPORTS it (definition.py: "The role '<name>' was not found in: <paths>"): the playbook dir's own roles/ subtree first, then the configured roles paths (ANSIBLE_ROLES_PATH or the ~/.ansible:/usr/share:/ /etc/ansible defaults), then the playbook dir itself. Live-verified against 2.21.6: a missing role errors as "The role 'x' was not found in: /work/roles:/root/.ansible/roles:/usr/share/ansible/roles: /etc/ansible/roles:/work". When the play declares collections:, ansible reports those collection names (plus "ansible.legacy") as a prefix in front of the paths - live-verified vs 2.21.6 with collections: [community.general, ansible.posix]: "community.general:ansible.posix:ansible.legacy:/pb/roles:...:/pb" Display-only - resolve_role_dir above keeps its own (superset) search order.

Source
role_search_display_dependency(playbook_dir : String, declaring_role_dir : String) : String

The search-path list a role's OWN meta/main.yml DEPENDENCY reports when IT names a missing role - ansible-core's RoleDefinition loads dependencies through the collection-aware loader, which prefixes the joined list with the "ansible.legacy" collection name and appends the declaring role's own parent directory as an extra search root before the playbook dir (live-verified vs 2.21.6: play-level roles: [missing] -> "/pb/roles:/home/.../.ansible/roles:/usr/share/ansible/roles: /etc/ansible/roles:/pb" meta/main.yml dependencies: [missing] -> "ansible.legacy:/pb/roles:/home/.../.ansible/roles: /usr/share/ansible/roles:/etc/ansible/roles:/pb/roles:/pb" with the role installed via ANSIBLE_ROLES_PATH=/rl: "ansible.legacy:/pb/roles:/rl:/rl:/pb"

  • the inserted entry is the declaring role dir's own parent). Display only - resolve_role_dir's own (superset) search order is unchanged.
Source
role_tasks_file_missing?(name : String, playbook_dir : String, tasks_from : String) : Bool

Whether a role's tasks/<tasks_from> file does not exist - public so PlaybookParser can reproduce real's parse-time refusal of a STATIC import_role: whose tasks_from: points nowhere (see RoleTasksFromFileError's own comment). tasks_from is taken literally, exactly as real builds the path (an explicit extension is honored, a bare name gets ".yml"/".yaml"/".json" tried in that order by #resolve_role_tasks_path).

Source

Nested types