class

Krikri::PlaybookParser

Inherits Reference < Object

Parser for Ansible YAML playbooks

Constants

ACTION_DEPRECATION_MUTEX = Mutex.new
ACTION_DIRECTIVE_KEYS = Set {"action", "ansible.builtin.action", "ansible.legacy.action", "local_action", "ansible.builtin.local_action", "ansible.legacy.local_action"}

Legacy action-directive keywords (action:/local_action: and their FQCN spellings) - parsed as free-form module directives, not module names (see parse_task). Deliberately NOT in SPECIAL_KEYS: they have to be captured as the task's module key to be rewritten.

AVAILABLE_PLUGINS = Set {"ansible.builtin.copy", "ansible.builtin.template", "ansible.builtin.file", "ansible.builtin.lineinfile", "ansible.builtin.replace", "ansible.builtin.service", "ansible.builtin.systemd", "ansible.builtin.hostname", "ansible.builtin.shell", "ansible.builtin.apt", "ansible.builtin.dnf", "ansible.builtin.dnf5", "ansible.builtin.yum", "ansible.builtin.package", "ansible.builtin.debug", "ansible.builtin.command", "ansible.builtin.setup", "ansible.builtin.gather_facts", "ansible.builtin.facts", "ansible.builtin.package_facts", "ansible.builtin.mount_facts", "ansible.posix.selinux", "ansible.posix.synchronize", "community.general.pam_limits", "community.general.apache2_module", "community.general.capabilities", "community.general.make", "ansible.builtin.user", "ansible.builtin.group", "ansible.builtin.git", "ansible.builtin.pip", "community.general.gem", "ansible.builtin.cron", "community.general.cronvar", "ansible.builtin.cronvar", "ansible.posix.acl", "ansible.builtin.acl", "ansible.posix.authorized_key", "ansible.builtin.authorized_key", "ansible.builtin.stat", "ansible.builtin.find", "ansible.builtin.getent", "community.general.archive", "ansible.builtin.unarchive", "ansible.builtin.yum_repository", "ansible.builtin.apt_repository", "ansible.builtin.apt_key", "ansible.builtin.rpm_key", "ansible.posix.seboolean", "community.general.seport", "community.general.sefcontext", "community.general.xml", "ansible.builtin.deb822_repository", "ansible.posix.mount", "ansible.builtin.mount", "ansible.posix.sysctl", "ansible.builtin.sysctl", "community.general.ufw", "ansible.posix.firewalld", "ansible.posix.firewalld_info", "ansible.builtin.iptables", "ansible.builtin.debconf", "ansible.builtin.async_status", "community.docker.docker_image", "community.docker.docker_network", "community.docker.docker_network_info", "community.docker.docker_container", "community.docker.docker_compose_v2", "community.mysql.mysql_db", "community.mysql.mysql_user", "community.mysql.mysql_info", "community.mysql.mysql_query", "ansible.mysql.mysql_db", "ansible.mysql.mysql_user", "ansible.mysql.mysql_info", "ansible.mysql.mysql_query", "community.postgresql.postgresql_db", "community.postgresql.postgresql_user", "community.postgresql.postgresql_privs", "community.postgresql.postgresql_query", "community.crypto.openssl_dhparam", "community.crypto.openssl_privatekey", "community.crypto.openssl_csr", "community.crypto.x509_certificate", "community.crypto.openssl_pkcs12", "community.crypto.openssh_keypair", "community.crypto.openssl_privatekey_info", "community.crypto.x509_certificate_info", "community.crypto.openssl_publickey_info", "community.crypto.openssl_csr_info", "community.crypto.openssl_publickey", "community.crypto.get_certificate", "community.general.modprobe", "community.general.kernel_blacklist", "community.general.pamd", "community.general.htpasswd", "community.general.ini_file", "community.general.timezone", "ansible.builtin.timezone", "ansible.builtin.homebrew", "community.general.npm", "community.general.alternatives", "ansible.builtin.alternatives", "community.general.filesystem", "community.general.zfs", "ansible.builtin.lvol", "community.general.lvol", "community.general.parted", "community.general.lvg", "community.general.snap", "community.general.deploy_helper", "community.general.dpkg_divert", "community.general.locale_gen", "community.general.mail", "community.general.java_cert", "virt_net", "community.general.maven_artifact", "community.general.nsupdate", "community.general.easy_install", "community.general.cpanm", "community.mysql.mysql_variables", "community.docker.docker_login", "community.docker.current_container_facts", "containers.podman.podman_image", "amazon.aws.iam_user_info", "iam_user_info", "ansible.builtin.sysvinit", "ansible.builtin.service_facts", "ansible.builtin.slurp", "ansible.builtin.reboot", "ansible.builtin.set_fact", "ansible.builtin.add_host", "ansible.builtin.get_url", "ansible.builtin.blockinfile", "ansible.builtin.uri", "ansible.builtin.assert", "ansible.builtin.fail", "ansible.builtin.wait_for", "ansible.builtin.wait_for_connection", "ansible.builtin.ping", "ansible.builtin.fetch", "ansible.builtin.pause", "ansible.builtin.script", "ansible.builtin.assemble", "ansible.builtin.tempfile", "ansible.builtin.known_hosts", "ansible.builtin.group_by", "ansible.builtin.set_stats", "ansible.builtin.dpkg_selections", "ansible.builtin.subversion", "ansible.builtin.expect", "community.general.git_config", "ansible.builtin.git_config", "community.general.sudoers", "community.general.dnf_versionlock", "yum_versionlock", "community.general.yum_versionlock", "community.docker.docker_image_build", "amazon.aws.ec2_metadata_facts", "amazon.aws.ec2_key", "amazon.aws.ec2_security_group", "amazon.aws.ec2_instance", "amazon.aws.ec2_vpc_subnet_info", "amazon.aws.ec2_vpc_net_info", "amazon.aws.ec2_ami_info", "ec2_key", "ec2_security_group", "ec2_instance", "ec2_vpc_subnet_info", "ec2_vpc_net_info", "ec2_ami_info", "community.rabbitmq.rabbitmq_plugin", "community.rabbitmq.rabbitmq_user", "ansible.builtin.py_module"}

List of available (implemented) plugins - using FQCN. Almost all of these are ansible.builtin.* (bundled with ansible-core); two exceptions verified against a ansible-core install (not assumed): authorized_key lives in the separate ansible.posix collection, and archive/unarchive live in community.general - neither ships with ansible-core itself. A Set, not an Array: this is membership-tested once per task in parse_task and again per task in validate, and a linear scan of 44 entries is the wrong shape for a lookup table even where the cost is unmeasurable.

BIGIP_ASM_POLICY_REMOVAL_MESSAGE = "The 'ansible.builtin.bigip_asm_policy' module has been removed. bigip_asm_policy has been removed please use bigip_asm_policy_manage instead. This feature was removed from ansible-core in a release after 2019-11-06."

ansible-core's own builtin-runtime tombstones for the three pre-collection F5 module names - the exact console [ERROR] text ansible-playbook 2.19.11 prints for them (live-verified for the bare, ansible.builtin.- and ansible.legacy.-qualified spellings; every spelling's message names the ansible.builtin FQCN, since real resolves the bare name into ansible.builtin's routing before the tombstone fires).

BIGIP_FACTS_REMOVAL_MESSAGE = "The 'ansible.builtin.bigip_facts' module has been removed. bigip_facts has been removed please use bigip_device_info module. This feature was removed from ansible-core in a release after 2019-11-06."
BIGIP_GTM_FACTS_REMOVAL_MESSAGE = "The 'ansible.builtin.bigip_gtm_facts' module has been removed. bigip_gtm_facts has been removed please use bigip_device_info module. This feature was removed from ansible-core in a release after 2019-11-06."
DOCKER_COMPOSE_REMOVAL_MESSAGE = "The 'community.docker.docker_compose' module has been removed. This module uses docker-compose v1, which is End of Life since July 2022. Please migrate to community.docker.docker_compose_v2. This feature was removed from collection 'community.docker' version 4.0.0."

community.docker's own removal text for docker_compose v1 (End-of-Life since July 2022; removed from community.docker in v4.0.0, docker_compose_v2 is the replacement) - ansible-playbook's exact hard-stop wording, verified live against ansible-core 2.19.11. Shared verbatim by all three tombstone spellings (bare, community.general.- and community.docker.- qualified): Ansible echoes the RESOLVED module name, never the as-written spelling, for this one.

DOCKER_CONTAINER_LIST_PARAMS = {"community.docker.docker_container", "docker_container"}

docker_container's list-typed options (command, entrypoint, volumes, ports/published_ports, and the list form of the dict-typed env / labels) travel JSON-encoded rather than on the generic comma-joined wire, because a comma inside one element is meaningful there - see #parse_module_params.

GENERIC_LOOKUP_LOOP_SKIP = ["loop", "with_items", "with_dict", "with_nested", "with_together", "with_indexed_items", "with_sequence", "with_first_found", "with_fileglob", "with_file", "with_subelements", "with_flattened", "with_community.general.flattened", "with_community.general.filetree"] of ::String

Loop keywords consumed by their own dedicated parser branches above (plus loop: itself) - a with_-prefixed key on this list never reaches the generic lookup-loop fallback below.

INCLUDE_VARS_ALL_ARGS = ["name", "hash_behaviour"]
INCLUDE_VARS_DIR_ARGS = ["dir", "depth", "files_matching", "ignore_files", "extensions", "ignore_unknown_extensions"]

Real include_vars action's own argument classification Observed behavior: VALID_DIR_ARGUMENTS, VALID_FILE_ARGUMENTS, VALID_ALL. Anything else fails the task at RUN time ("<key> is not a valid option in include_vars") - see parse_include_vars_task / execute_include_vars.

INCLUDE_VARS_FILE_ARGS = ["file", "_raw_params"]
LEGACY_KV_MERGE_DEPRECATION_MUTEX = Mutex.new
LOOP_ARRAY_WRAPPED_TEMPLATE_KEYS = ["loop", "with_items", "with_list", "with_dict", "with_indexed_items"] of ::String

The subset of those keywords whose YAML value IS the loop's item list (loop:, with_items:, with_list:, with_dict:, with_indexed_items:), and for which a single-element ARRAY holding one bare {{ ... }} is that list with the template standing in for its whole content - the resolver decides at runtime whether that turns into the list itself (loop:, with_list:) or into the list's own elements (with_items:).

with_nested:/with_together: are deliberately absent: their value is a list of independent factors/columns, not an item list, and their own parse branches already defer a {{ }} entry to the executor's cartesian/zip resolvers. Reading their one-element array as "the whole source is secretly a list-producing template" instead ran them through the generic loop-template resolver, which took the wrong cartesian product for with_nested: and matched no resolver at all for with_together: - the task then ran ONCE with item unbound ("'item' is undefined"), where Ansible zips/iterates the resolved list.

LOOP_TEMPLATE_KEYS = ["loop", "with_items", "with_list", "with_dict", "with_nested", "with_together", "with_indexed_items"] of ::String

Loop source keywords that support a plain literal (array or hash) at parse time, in the same priority order used when picking a loop source in parse_task. Checked here for a scalar "{{ ... }}" template value once none of them matched literally.

MARIADB_FORK_ALIASES = Set {"mariadb_db", "mariadb_user", "ansible.mariadb.mariadb_db", "ansible.mariadb.mariadb_user"}

The MODULE_ALIASES spellings aliased onto community.mysql's mysql_db/mysql_user - the small set parse_task's controller- collection refusal additionally covers when CollectionIndex says the controller lacks ansible.mariadb (real refuses all four at playbook load there, issue #499): the alias alone would make resolved_module_name non-nil and exempt them from a refusal real still performs.

MODULE_ALIASES = {"systemd_service" => "ansible.builtin.systemd", "ansible.builtin.systemd_service" => "ansible.builtin.systemd", "ansible.legacy.systemd_service" => "ansible.builtin.systemd", "raw" => "ansible.builtin.shell", "ansible.builtin.raw" => "ansible.builtin.shell", "ansible.legacy.raw" => "ansible.builtin.shell", "mariadb_db" => "community.mysql.mysql_db", "mariadb_user" => "community.mysql.mysql_user", "ansible.mariadb.mariadb_db" => "community.mysql.mysql_db", "ansible.mariadb.mariadb_user" => "community.mysql.mysql_user", "openssl_certificate" => "community.crypto.x509_certificate", "ansible.builtin.openssl_certificate" => "community.crypto.x509_certificate", "ansible.legacy.openssl_certificate" => "community.crypto.x509_certificate", "community.crypto.openssl_certificate" => "community.crypto.x509_certificate", "community.general.openssl_certificate" => "community.crypto.x509_certificate", "openssl_certificate_info" => "community.crypto.x509_certificate_info", "ansible.builtin.openssl_certificate_info" => "community.crypto.x509_certificate_info", "ansible.legacy.openssl_certificate_info" => "community.crypto.x509_certificate_info", "community.crypto.openssl_certificate_info" => "community.crypto.x509_certificate_info", "community.general.openssl_certificate_info" => "community.crypto.x509_certificate_info", "ansible.builtin.snap" => "community.general.snap", "ansible.legacy.snap" => "community.general.snap", "ansible.builtin.make" => "community.general.make", "ansible.legacy.make" => "community.general.make", "community.libvirt.virt_net" => "virt_net"}

Ansible module aliases - a second FQCN (or bare name) that resolves to the exact same module, not merely a similarly-named one. systemd_service was added in ansible-core 2.12 as the "correct" name (systemd was ambiguous with systemd_service/ systemd_socket... at the time only one of each ever shipped); systemd is still kept as a working alias, and real-world roles use both spellings interchangeably (konstruktoid/ansible-role- hardening's own tasks write ansible.builtin.systemd_service 19 times across 14 files, never the bare ansible.builtin.systemd this codebase's plugin is actually named after). Checked before the AVAILABLE_PLUGINS/MODULE_SEARCH_COLLECTIONS lookups below, so both spellings resolve to the one real plugin binary.

MODULE_SEARCH_COLLECTIONS = ["ansible.builtin", "ansible.legacy", "ansible.posix", "community.general", "community.docker", "community.mysql", "community.postgresql", "community.rabbitmq", "community.crypto"]

The collections a bare (non-FQCN) module name resolves against, in Ansible's own default search order - getent: (no ansible. builtin. prefix) is extremely common in real-world playbooks/roles (dev-sec's own molecule test fixtures use it, unlike the role's own tasks, which are always fully qualified) and previously only ever matched AVAILABLE_PLUGINS verbatim, so any bare name failed outright ("Plugin not available: getent") even though the qualified form works fine. None of AVAILABLE_PLUGINS' short names collide across collections, so the search order only matters for documentation purposes here, not correctness.

OPENING_BLOCK_TAGS = {"if", "for", "block", "filter", "macro", "call", "with", "raw"}

Returns each COMPLETE top-level {% %} block construct (the opening tag through its matching {% end* %}, body included) in s. Used by the executor's _jinja_block_args handling (see parse_module_params's marker comment): Ansible's observed finalization type for such an args string tracks whether any {% %} block contributed output, which only a whole-construct render can answer - a {% if %} tag rendered on its own is a syntax error, and whitespace inside a block splits it across split_shell_like tokens. Only PAIRED constructs are collected: a self-contained tag ({% set x = 1 %}) can never contribute output, and rendering it standalone would only re-run its side effects. A construct nested inside another is consumed by the outer one (Jinja skips an inner block when the enclosing condition is false, so rendering the inner standalone would wrongly report output).

PARSE_KV_COMMAND_SPECIALS = ["creates", "removes", "chdir", "executable", "warn", "stdin", "stdin_add_newline", "strip_empty_ends"] of ::String

The same whitelist spelled out for the legacy k=v merge deprecation: parse_kv's check_raw=True branch tests the token's key text against this list BEFORE stripping (see warn_legacy_kv_args_deprecation).

RAW_COMMAND_MODULES = {"command", "shell", "script", "raw", "ansible.builtin.command", "ansible.builtin.shell", "ansible.legacy.command", "ansible.legacy.shell", "ansible.builtin.script", "ansible.legacy.script", "ansible.builtin.raw", "ansible.legacy.raw"}

Modules whose bare-string task arg is a raw command line, not free-form key=value params - see the yaml.as_s? branch of #parse_module_params. Bare "command"/"shell" is included defensively alongside the resolved FQCN forms, in case this is ever reached before module_name resolution.

REDIRECT_DEPRECATIONS = {"community.mysql.mysql_db" => {warning_text: "Use ansible.mysql.mysql_db instead.", collection: "community.mysql", version: "6.0.0"}, "community.mysql.mysql_info" => {warning_text: "Use ansible.mysql.mysql_info instead.", collection: "community.mysql", version: "6.0.0"}, "community.mysql.mysql_query" => {warning_text: "Use ansible.mysql.mysql_query instead.", collection: "community.mysql", version: "6.0.0"}, "community.mysql.mysql_replication" => {warning_text: "Use ansible.mysql.mysql_replication instead.", collection: "community.mysql", version: "6.0.0"}, "community.mysql.mysql_role" => {warning_text: "Use ansible.mysql.mysql_role instead.", collection: "community.mysql", version: "6.0.0"}, "community.mysql.mysql_user" => {warning_text: "Use ansible.mysql.mysql_user instead.", collection: "community.mysql", version: "6.0.0"}, "community.mysql.mysql_variables" => {warning_text: "Use ansible.mysql.mysql_variables instead.", collection: "community.mysql", version: "6.0.0"}, "community.general.proxmox" => {warning_text: "The proxmox content has been moved to community.proxmox.", collection: "community.general", version: "15.0.0"}, "community.general.proxmox_kvm" => {warning_text: "The proxmox content has been moved to community.proxmox.", collection: "community.general", version: "15.0.0"}, "community.windows.win_audit_policy_system" => {warning_text: "win_audit_policy_system will be removed in a future release of this collection. Use ansible.windows.win_audit_policy_system instead.", collection: "community.windows", version: "4.0.0"}, "amazon.aws.ec2_vpc_peer" => {warning_text: "ec2_vpc_peer has been renamed to ec2_vpc_peering. Please update your tasks.", collection: "amazon.aws", version: "13.0.0"}, "ansible.builtin.apt_repository" => {warning_text: "Use deb822_repository instead.", collection: "ansible.builtin", version: "2.25"}, "apt_repository" => {warning_text: "Use deb822_repository instead.", collection: "ansible.builtin", version: "2.25"}, "apt_key" => {warning_text: "Use deb822_repository instead.", collection: "ansible.builtin", version: "2.25"}, "ansible.builtin.apt_key" => {warning_text: "Use deb822_repository instead.", collection: "ansible.builtin", version: "2.25"}, "ansible.legacy.apt_key" => {warning_text: "Use deb822_repository instead.", collection: "ansible.builtin", version: "2.25"}}

Collection-level plugin deprecations that fire at task-load time, keyed by module FQCN: the collection's own meta/runtime.yml plugin_routing deprecation entries - a redirect's warning_text or a moved-content notice - plus the collection that carries the deprecation and the version it will be removed in, live-verified against ansible-core 2.19.11's console output and the installed collections' meta/runtime.yml data. Real resolves a task's module at task-load time and record_deprecation warns there - once per distinct message per run (the Display dedups), with the one-time "Deprecation warnings can be disabled" hint before the first - and every task result the module actually produced carries the deprecations entry (TaskExecutor's DeferredWarningContext). The bare (non-FQCN) spellings resolve through MODULE_SEARCH_COLLECTIONS and warn with the RESOLVED FQCN (amazon.aws's bare spellings excepted - that collection is not in MODULE_SEARCH_COLLECTIONS, so its bare names qualify through redirect_deprecation_lookup's own explicit prefix).

Only deprecations observable in krikri's own runs belong here, whatever the module's support state: the community.mysql entries redirect to their ansible.mysql.* homes (removal_version 6.0.0, warning_text "Use ansible.mysql.<module> instead." - installed community.mysql 5.0.2), and that includes mysql_replication and mysql_role even though neither is a krikri-supported module: real prints their load-time [DEPRECATION WARNING] alongside the supported modules' ones (the hoplacloud.wordpress round, which diverged on the missing mysql_replication line alone - the warning is observable even though the module never executes; the same reasoning keeps proxmox_kvm here). community.general.proxmox_kvm is not krikri-supported either - only its parse-time stderr deprecation is observable (the inoxio.proxmox_vms role round): community.general moved all proxmox content to community.proxmox and its meta/runtime.yml marks proxmox_kvm deprecated with removal_version 15.0.0, warning_text naming the new home.

REMOVED_MODULE_TOMBSTONES = {"ec2_remote_facts" => nil, "ansible.builtin.ec2_remote_facts" => nil, "ansible.legacy.ec2_remote_facts" => nil, "amazon.aws.ec2_remote_facts" => nil, "consul_acl" => nil, "docker_service" => nil, "bigip_asm_policy" => BIGIP_ASM_POLICY_REMOVAL_MESSAGE, "ansible.builtin.bigip_asm_policy" => BIGIP_ASM_POLICY_REMOVAL_MESSAGE, "ansible.legacy.bigip_asm_policy" => BIGIP_ASM_POLICY_REMOVAL_MESSAGE, "bigip_facts" => BIGIP_FACTS_REMOVAL_MESSAGE, "ansible.builtin.bigip_facts" => BIGIP_FACTS_REMOVAL_MESSAGE, "ansible.legacy.bigip_facts" => BIGIP_FACTS_REMOVAL_MESSAGE, "bigip_gtm_facts" => BIGIP_GTM_FACTS_REMOVAL_MESSAGE, "ansible.builtin.bigip_gtm_facts" => BIGIP_GTM_FACTS_REMOVAL_MESSAGE, "ansible.legacy.bigip_gtm_facts" => BIGIP_GTM_FACTS_REMOVAL_MESSAGE, "community.docker.docker_compose" => DOCKER_COMPOSE_REMOVAL_MESSAGE, "community.general.docker_compose" => DOCKER_COMPOSE_REMOVAL_MESSAGE, "docker_compose" => DOCKER_COMPOSE_REMOVAL_MESSAGE, "ansible.windows.win_domain" => "The 'ansible.windows.win_domain' module has been removed. Use microsoft.ad.domain instead. This feature was removed from collection 'ansible.windows' version 3.0.0.", "ansible.windows.win_domain_controller" => "The 'ansible.windows.win_domain_controller' module has been removed. Use microsoft.ad.domain_controller instead. This feature was removed from collection 'ansible.windows' version 3.0.0.", "ansible.windows.win_domain_membership" => "The 'ansible.windows.win_domain_membership' module has been removed. Use microsoft.ad.membership instead. This feature was removed from collection 'ansible.windows' version 3.0.0.", "community.crypto.acme_account_facts" => "The 'community.crypto.acme_account_facts' module has been removed. The 'community.crypto.acme_account_facts' module has been renamed to 'community.crypto.acme_account_info'. This feature was removed from collection 'community.crypto' version 2.0.0.", "community.crypto.ecs_certificate" => "The 'community.crypto.ecs_certificate' module has been removed. The 'community.crypto.ecs_certificate' module has been removed due to the upcoming sunsetting of the ECS service. Please use community.crypto 2.x.y to continue using this module. This feature was removed from collection 'community.crypto' version 3.0.0.", "community.crypto.ecs_domain" => "The 'community.crypto.ecs_domain' module has been removed. The 'community.crypto.ecs_domain' module has been removed due to the upcoming sunsetting of the ECS service. Please use community.crypto 2.x.y to continue using this module. This feature was removed from collection 'community.crypto' version 3.0.0.", "community.crypto.openssl_certificate" => "The 'community.crypto.openssl_certificate' module has been removed. The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'. This feature was removed from collection 'community.crypto' version 2.0.0.", "community.crypto.openssl_certificate_info" => "The 'community.crypto.openssl_certificate_info' module has been removed. The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'. This feature was removed from collection 'community.crypto' version 2.0.0.", "community.general.ali_instance_facts" => "The 'community.general.ali_instance_facts' module has been removed. Use community.general.ali_instance_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.bearychat" => "The 'community.general.bearychat' module has been removed. Chat service is no longer available. This feature was removed from collection 'community.general' version 12.0.0.", "community.general.clc_alert_policy" => "The 'community.general.clc_alert_policy' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_blueprint_package" => "The 'community.general.clc_blueprint_package' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_firewall_policy" => "The 'community.general.clc_firewall_policy' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_group" => "The 'community.general.clc_group' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_loadbalancer" => "The 'community.general.clc_loadbalancer' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_modify_server" => "The 'community.general.clc_modify_server' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_publicip" => "The 'community.general.clc_publicip' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_server" => "The 'community.general.clc_server' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.clc_server_snapshot" => "The 'community.general.clc_server_snapshot' module has been removed. CenturyLink Cloud services went EOL in September 2023. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.consul_acl" => "The 'community.general.consul_acl' module has been removed. Use community.general.consul_token and/or community.general.consul_policy instead. This feature was removed from collection 'community.general' version 10.0.0.", "community.general.docker_image_facts" => "The 'community.general.docker_image_facts' module has been removed. Use community.docker.docker_image_info instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.docker_service" => "The 'community.general.docker_service' module has been removed. Use community.docker.docker_compose instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.facter" => "The 'community.general.facter' module has been removed. Use community.general.facter_facts instead. This feature was removed from collection 'community.general' version 12.0.0.", "community.general.flowdock" => "The 'community.general.flowdock' module has been removed. This module relied on HTTPS APIs that do not exist anymore and there is no clear path to update. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.foreman" => "The 'community.general.foreman' module has been removed. Use the modules from the theforeman.foreman collection instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcdns_record" => "The 'community.general.gcdns_record' module has been removed. Use google.cloud.gcp_dns_resource_record_set instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcdns_zone" => "The 'community.general.gcdns_zone' module has been removed. Use google.cloud.gcp_dns_managed_zone instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gce" => "The 'community.general.gce' module has been removed. Use google.cloud.gcp_compute_instance instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcp_backend_service" => "The 'community.general.gcp_backend_service' module has been removed. Use google.cloud.gcp_compute_backend_service instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcp_forwarding_rule" => "The 'community.general.gcp_forwarding_rule' module has been removed. Use google.cloud.gcp_compute_forwarding_rule or google.cloud.gcp_compute_global_forwarding_rule instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcp_healthcheck" => "The 'community.general.gcp_healthcheck' module has been removed. Use google.cloud.gcp_compute_health_check, google.cloud.gcp_compute_http_health_check or google.cloud.gcp_compute_https_health_check instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcp_target_proxy" => "The 'community.general.gcp_target_proxy' module has been removed. Use google.cloud.gcp_compute_target_http_proxy instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcp_url_map" => "The 'community.general.gcp_url_map' module has been removed. Use google.cloud.gcp_compute_url_map instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.gcpubsub_facts" => "The 'community.general.gcpubsub_facts' module has been removed. Use community.google.gcpubsub_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.gcspanner" => "The 'community.general.gcspanner' module has been removed. Use google.cloud.gcp_spanner_database and/or google.cloud.gcp_spanner_instance instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.github_hooks" => "The 'community.general.github_hooks' module has been removed. Use community.general.github_webhook and community.general.github_webhook_info instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.hipchat" => "The 'community.general.hipchat' module has been removed. The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.hpilo_facts" => "The 'community.general.hpilo_facts' module has been removed. Use community.general.hpilo_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.idrac_redfish_facts" => "The 'community.general.idrac_redfish_facts' module has been removed. Use community.general.idrac_redfish_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.jenkins_job_facts" => "The 'community.general.jenkins_job_facts' module has been removed. Use community.general.jenkins_job_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.katello" => "The 'community.general.katello' module has been removed. Use the modules from the theforeman.foreman collection instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.ldap_attr" => "The 'community.general.ldap_attr' module has been removed. Use community.general.ldap_attrs instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.logicmonitor" => "The 'community.general.logicmonitor' module has been removed. The logicmonitor_facts module is no longer maintained and the API used has been disabled in 2017. This feature was removed from collection 'community.general' version 1.0.0.", "community.general.logicmonitor_facts" => "The 'community.general.logicmonitor_facts' module has been removed. The logicmonitor_facts module is no longer maintained and the API used has been disabled in 2017. This feature was removed from collection 'community.general' version 1.0.0.", "community.general.memset_memstore_facts" => "The 'community.general.memset_memstore_facts' module has been removed. Use community.general.memset_memstore_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.memset_server_facts" => "The 'community.general.memset_server_facts' module has been removed. Use community.general.memset_server_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.na_cdot_aggregate" => "The 'community.general.na_cdot_aggregate' module has been removed. Use netapp.ontap.na_ontap_aggregate instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_cdot_license" => "The 'community.general.na_cdot_license' module has been removed. Use netapp.ontap.na_ontap_license instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_cdot_lun" => "The 'community.general.na_cdot_lun' module has been removed. Use netapp.ontap.na_ontap_lun instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_cdot_qtree" => "The 'community.general.na_cdot_qtree' module has been removed. Use netapp.ontap.na_ontap_qtree instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_cdot_svm" => "The 'community.general.na_cdot_svm' module has been removed. Use netapp.ontap.na_ontap_svm instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_cdot_user" => "The 'community.general.na_cdot_user' module has been removed. Use netapp.ontap.na_ontap_user instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_cdot_user_role" => "The 'community.general.na_cdot_user_role' module has been removed. Use netapp.ontap.na_ontap_user_role instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_cdot_volume" => "The 'community.general.na_cdot_volume' module has been removed. Use netapp.ontap.na_ontap_volume instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.na_ontap_gather_facts" => "The 'community.general.na_ontap_gather_facts' module has been removed. Use netapp.ontap.na_ontap_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.nginx_status_facts" => "The 'community.general.nginx_status_facts' module has been removed. Use community.general.nginx_status_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.one_image_facts" => "The 'community.general.one_image_facts' module has been removed. Use community.general.one_image_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.onepassword_facts" => "The 'community.general.onepassword_facts' module has been removed. Use community.general.onepassword_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_datacenter_facts" => "The 'community.general.oneview_datacenter_facts' module has been removed. Use community.general.oneview_datacenter_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_enclosure_facts" => "The 'community.general.oneview_enclosure_facts' module has been removed. Use community.general.oneview_enclosure_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_ethernet_network_facts" => "The 'community.general.oneview_ethernet_network_facts' module has been removed. Use community.general.oneview_ethernet_network_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_fc_network_facts" => "The 'community.general.oneview_fc_network_facts' module has been removed. Use community.general.oneview_fc_network_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_fcoe_network_facts" => "The 'community.general.oneview_fcoe_network_facts' module has been removed. Use community.general.oneview_fcoe_network_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_logical_interconnect_group_facts" => "The 'community.general.oneview_logical_interconnect_group_facts' module has been removed. Use community.general.oneview_logical_interconnect_group_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_network_set_facts" => "The 'community.general.oneview_network_set_facts' module has been removed. Use community.general.oneview_network_set_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.oneview_san_manager_facts" => "The 'community.general.oneview_san_manager_facts' module has been removed. Use community.general.oneview_san_manager_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.online_server_facts" => "The 'community.general.online_server_facts' module has been removed. Use community.general.online_server_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.online_user_facts" => "The 'community.general.online_user_facts' module has been removed. Use community.general.online_user_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt" => "The 'community.general.ovirt' module has been removed. Use ovirt.ovirt.ovirt_vm instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_affinity_label_facts" => "The 'community.general.ovirt_affinity_label_facts' module has been removed. Use ovirt.ovirt.ovirt_affinity_label_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_api_facts" => "The 'community.general.ovirt_api_facts' module has been removed. Use ovirt.ovirt.ovirt_api_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_cluster_facts" => "The 'community.general.ovirt_cluster_facts' module has been removed. Use ovirt.ovirt.ovirt_cluster_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_datacenter_facts" => "The 'community.general.ovirt_datacenter_facts' module has been removed. Use ovirt.ovirt.ovirt_datacenter_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_disk_facts" => "The 'community.general.ovirt_disk_facts' module has been removed. Use ovirt.ovirt.ovirt_disk_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_event_facts" => "The 'community.general.ovirt_event_facts' module has been removed. Use ovirt.ovirt.ovirt_event_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_external_provider_facts" => "The 'community.general.ovirt_external_provider_facts' module has been removed. Use ovirt.ovirt.ovirt_external_provider_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_group_facts" => "The 'community.general.ovirt_group_facts' module has been removed. Use ovirt.ovirt.ovirt_group_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_host_facts" => "The 'community.general.ovirt_host_facts' module has been removed. Use ovirt.ovirt.ovirt_host_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_host_storage_facts" => "The 'community.general.ovirt_host_storage_facts' module has been removed. Use ovirt.ovirt.ovirt_host_storage_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_network_facts" => "The 'community.general.ovirt_network_facts' module has been removed. Use ovirt.ovirt.ovirt_network_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_nic_facts" => "The 'community.general.ovirt_nic_facts' module has been removed. Use ovirt.ovirt.ovirt_nic_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_permission_facts" => "The 'community.general.ovirt_permission_facts' module has been removed. Use ovirt.ovirt.ovirt_permission_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_quota_facts" => "The 'community.general.ovirt_quota_facts' module has been removed. Use ovirt.ovirt.ovirt_quota_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_scheduling_policy_facts" => "The 'community.general.ovirt_scheduling_policy_facts' module has been removed. Use ovirt.ovirt.ovirt_scheduling_policy_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_snapshot_facts" => "The 'community.general.ovirt_snapshot_facts' module has been removed. Use ovirt.ovirt.ovirt_snapshot_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_storage_domain_facts" => "The 'community.general.ovirt_storage_domain_facts' module has been removed. Use ovirt.ovirt.ovirt_storage_domain_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_storage_template_facts" => "The 'community.general.ovirt_storage_template_facts' module has been removed. Use ovirt.ovirt.ovirt_storage_template_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_storage_vm_facts" => "The 'community.general.ovirt_storage_vm_facts' module has been removed. Use ovirt.ovirt.ovirt_storage_vm_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_tag_facts" => "The 'community.general.ovirt_tag_facts' module has been removed. Use ovirt.ovirt.ovirt_tag_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_template_facts" => "The 'community.general.ovirt_template_facts' module has been removed. Use ovirt.ovirt.ovirt_template_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_user_facts" => "The 'community.general.ovirt_user_facts' module has been removed. Use ovirt.ovirt.ovirt_user_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_vm_facts" => "The 'community.general.ovirt_vm_facts' module has been removed. Use ovirt.ovirt.ovirt_vm_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.ovirt_vmpool_facts" => "The 'community.general.ovirt_vmpool_facts' module has been removed. Use ovirt.ovirt.ovirt_vmpool_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.profitbricks" => "The 'community.general.profitbricks' module has been removed. Supporting library is unsupported since 2021. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.profitbricks_datacenter" => "The 'community.general.profitbricks_datacenter' module has been removed. Supporting library is unsupported since 2021. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.profitbricks_nic" => "The 'community.general.profitbricks_nic' module has been removed. Supporting library is unsupported since 2021. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.profitbricks_volume" => "The 'community.general.profitbricks_volume' module has been removed. Supporting library is unsupported since 2021. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.profitbricks_volume_attachments" => "The 'community.general.profitbricks_volume_attachments' module has been removed. Supporting library is unsupported since 2021. This feature was removed from collection 'community.general' version 11.0.0.", "community.general.purefa_facts" => "The 'community.general.purefa_facts' module has been removed. Use purestorage.flasharray.purefa_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.purefb_facts" => "The 'community.general.purefb_facts' module has been removed. Use purestorage.flashblade.purefb_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.python_requirements_facts" => "The 'community.general.python_requirements_facts' module has been removed. Use community.general.python_requirements_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.rax" => "The 'community.general.rax' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_cbs" => "The 'community.general.rax_cbs' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_cbs_attachments" => "The 'community.general.rax_cbs_attachments' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_cdb" => "The 'community.general.rax_cdb' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_cdb_database" => "The 'community.general.rax_cdb_database' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_cdb_user" => "The 'community.general.rax_cdb_user' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_clb" => "The 'community.general.rax_clb' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_clb_nodes" => "The 'community.general.rax_clb_nodes' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_clb_ssl" => "The 'community.general.rax_clb_ssl' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_dns" => "The 'community.general.rax_dns' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_dns_record" => "The 'community.general.rax_dns_record' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_facts" => "The 'community.general.rax_facts' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_files" => "The 'community.general.rax_files' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_files_objects" => "The 'community.general.rax_files_objects' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_identity" => "The 'community.general.rax_identity' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_keypair" => "The 'community.general.rax_keypair' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_meta" => "The 'community.general.rax_meta' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_mon_alarm" => "The 'community.general.rax_mon_alarm' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_mon_check" => "The 'community.general.rax_mon_check' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_mon_entity" => "The 'community.general.rax_mon_entity' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_mon_notification" => "The 'community.general.rax_mon_notification' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_mon_notification_plan" => "The 'community.general.rax_mon_notification_plan' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_network" => "The 'community.general.rax_network' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_queue" => "The 'community.general.rax_queue' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_scaling_group" => "The 'community.general.rax_scaling_group' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.rax_scaling_policy" => "The 'community.general.rax_scaling_policy' module has been removed. This module relied on the deprecated package pyrax. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.redfish_facts" => "The 'community.general.redfish_facts' module has been removed. Use community.general.redfish_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.rhn_channel" => "The 'community.general.rhn_channel' module has been removed. RHN is EOL. This feature was removed from collection 'community.general' version 10.0.0.", "community.general.rhn_register" => "The 'community.general.rhn_register' module has been removed. RHN is EOL. This feature was removed from collection 'community.general' version 10.0.0.", "community.general.scaleway_image_facts" => "The 'community.general.scaleway_image_facts' module has been removed. Use community.general.scaleway_image_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.scaleway_ip_facts" => "The 'community.general.scaleway_ip_facts' module has been removed. Use community.general.scaleway_ip_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.scaleway_organization_facts" => "The 'community.general.scaleway_organization_facts' module has been removed. Use community.general.scaleway_organization_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.scaleway_security_group_facts" => "The 'community.general.scaleway_security_group_facts' module has been removed. Use community.general.scaleway_security_group_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.scaleway_server_facts" => "The 'community.general.scaleway_server_facts' module has been removed. Use community.general.scaleway_server_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.scaleway_snapshot_facts" => "The 'community.general.scaleway_snapshot_facts' module has been removed. Use community.general.scaleway_snapshot_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.scaleway_volume_facts" => "The 'community.general.scaleway_volume_facts' module has been removed. Use community.general.scaleway_volume_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.sf_account_manager" => "The 'community.general.sf_account_manager' module has been removed. Use netapp.elementsw.na_elementsw_account instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.sf_check_connections" => "The 'community.general.sf_check_connections' module has been removed. Use netapp.elementsw.na_elementsw_check_connections instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.sf_snapshot_schedule_manager" => "The 'community.general.sf_snapshot_schedule_manager' module has been removed. Use netapp.elementsw.na_elementsw_snapshot_schedule instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.sf_volume_access_group_manager" => "The 'community.general.sf_volume_access_group_manager' module has been removed. Use netapp.elementsw.na_elementsw_access_group instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.sf_volume_manager" => "The 'community.general.sf_volume_manager' module has been removed. Use netapp.elementsw.na_elementsw_volume instead. This feature was removed from collection 'community.general' version 2.0.0.", "community.general.smartos_image_facts" => "The 'community.general.smartos_image_facts' module has been removed. Use community.general.smartos_image_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.stackdriver" => "The 'community.general.stackdriver' module has been removed. This module relied on HTTPS APIs that do not exist anymore, and any new development in the direction of providing an alternative should happen in the context of the google.cloud collection. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.vertica_facts" => "The 'community.general.vertica_facts' module has been removed. Use community.general.vertica_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.general.webfaction_app" => "The 'community.general.webfaction_app' module has been removed. This module relied on HTTPS APIs that do not exist anymore and there is no clear path to update. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.webfaction_db" => "The 'community.general.webfaction_db' module has been removed. This module relied on HTTPS APIs that do not exist anymore and there is no clear path to update. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.webfaction_domain" => "The 'community.general.webfaction_domain' module has been removed. This module relied on HTTPS APIs that do not exist anymore and there is no clear path to update. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.webfaction_mailbox" => "The 'community.general.webfaction_mailbox' module has been removed. This module relied on HTTPS APIs that do not exist anymore and there is no clear path to update. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.webfaction_site" => "The 'community.general.webfaction_site' module has been removed. This module relied on HTTPS APIs that do not exist anymore and there is no clear path to update. This feature was removed from collection 'community.general' version 9.0.0.", "community.general.xenserver_guest_facts" => "The 'community.general.xenserver_guest_facts' module has been removed. Use community.general.xenserver_guest_info instead. This feature was removed from collection 'community.general' version 3.0.0.", "community.postgresql.postgresql_lang" => "The 'community.postgresql.postgresql_lang' module has been removed. Use community.postgresql.postgresql_ext instead. This feature was removed from collection 'community.postgresql' version 4.0.0.", "community.windows.win_domain_computer" => "The 'community.windows.win_domain_computer' module has been removed. Use microsoft.ad.computer instead. This feature was removed from collection 'community.windows' version 3.0.0.", "community.windows.win_domain_group" => "The 'community.windows.win_domain_group' module has been removed. Use microsoft.ad.group instead. This feature was removed from collection 'community.windows' version 3.0.0.", "community.windows.win_domain_group_membership" => "The 'community.windows.win_domain_group_membership' module has been removed. Use microsoft.ad.group instead. This feature was removed from collection 'community.windows' version 3.0.0.", "community.windows.win_domain_object_info" => "The 'community.windows.win_domain_object_info' module has been removed. Use microsoft.ad.object_info instead. This feature was removed from collection 'community.windows' version 3.0.0.", "community.windows.win_domain_ou" => "The 'community.windows.win_domain_ou' module has been removed. Use microsoft.ad.ou instead. This feature was removed from collection 'community.windows' version 3.0.0.", "community.windows.win_domain_user" => "The 'community.windows.win_domain_user' module has been removed. Use microsoft.ad.user instead. This feature was removed from collection 'community.windows' version 3.0.0.", "kubernetes.core.k8s_facts" => "The 'kubernetes.core.k8s_facts' module has been removed. Use kubernetes.core.k8s_info instead. This feature was removed from collection 'kubernetes.core' version 2.0.0."}

Bare module names ansible-core can no longer resolve in ANY collection (removed from ansible-core years ago and from the collections that absorbed them), so every ansible-playbook install hard-stops on them. Deliberately minimal: an entry here hard-stops the whole run at parse time, so a name belongs here only when it is unresolvable on EVERY real controller - never a module that a current collection still ships. Widening = adding entries here. Value is Ansible's own hard-stop error text for that name: nil means the generic couldn't-resolve wording (what ansible-core prints when nothing anywhere resolves the name), while some removed names have Ansible print its own specific removal message instead - verified live against ansible-core 2.19.11, including which names get which wording.

RESERVED_TOPLEVEL_WARN_MUTEX = Mutex.new
RESERVED_VAR_NAMES = ["action", "always", "any_errors_fatal", "args", "async", "async_val", "become", "become_exe", "become_flags", "become_method", "become_user", "block", "changed_when", "check_mode", "collections", "connection", "cycler", "debugger", "delay", "delegate_facts", "delegate_to", "dict", "diff", "environment", "fact_path", "failed_when", "force_handlers", "gather_facts", "gather_timeout", "handlers", "hosts", "ignore_errors", "ignore_unreachable", "joiner", "lipsum", "local_action", "lookup", "loop", "loop_control", "loop_with", "max_fail_percentage", "module_defaults", "name", "namespace", "no_log", "notify", "now", "omit", "order", "poll", "port", "post_tasks", "pre_tasks", "q", "query", "range", "register", "remote_user", "rescue", "retries", "roles", "run_once", "serial", "strategy", "tags", "tasks", "throttle", "timeout", "undef", "until", "vars_files", "vars_prompt", "when"] of ::String

include_vars: - a controller-side pseudo-module ("_include_vars"). It reads a YAML file from the controller and merges it into the variable context, so there is no plugin binary and nothing runs on the target. Accepts the bare-string form (include_vars: x.yml) and the dict form with file:/name:. The task-attribute subset EVERY task-parsing branch shares - used to be an identical ~10-line block copy-pasted into five separate parsers (parse_task, parse_include_vars_task, parse_block_task, parse_include_tasks, parse_include_role), and each branch's copy accumulated its own silent-failure bugs over the rounds: a fix landed in one branch's block had to be re-discovered in the others (several of those re-discoveries are still visible as "same gap <branch> had" comments on the branches' own module-specific parsing below). One shared implementation now; each caller adds only its own module-specific parsing on top. tags: accepts a bare string as well as a list (Ansible's own parser does; only include_vars's copy of this block used to honor that). ansible-core's reserved variable names (the Ansible module's get_reserved_names, live-verified from 2.19.11's own list - the with_ PREFIX entry excluded: a vars: entry named with_items does not warn, every exact name does). A play/task vars: entry with one of these names emits "[WARNING]: Found variable using reserved name 'X'." with an Origin block naming the var key's own position - live-verified byte-for-byte vs 2.19.11 (play vars and task vars both warn, at definition/compile time). vars itself is NOT in the list: ansible's warn_if_reserved does varnames.discard('vars') (it is added internally, so safe to ignore), so a vars: key never warns there - a top-level vars: in a role's vars/main.yml must stay silent too.

SOURCE_LINES_MUTEX = Mutex.new
SPECIAL_COMMAND_PARAM_RE = /\A(creates|removes|chdir|executable|warn|stdin|stdin_add_newline|strip_empty_ends)=(.*)\z/m

parse_kv's own whitelist: a free-form command:/shell: token whose key is one of these becomes a module option instead of command text (ansible.parsing.splitter.parse_kv with check_raw=True - the exact set mod_args.py's parsing hands to the module).

SPECIAL_KEYS = begin keys = Set {"name", "when", "register", "ignore_errors", "check_mode", "diff", "become", "become_user", "become_method", "become_flags", "become_pass", "become_exe", "tags", "args", "listen", "with_items", "loop", "timeout", "with_dict", "with_fileglob", "with_file", "with_first_found", "with_nested", "with_together", "with_sequence", "with_flattened", "with_community.general.flattened", "with_subelements", "with_indexed_items", "until", "retries", "delay", "with_community.general.filetree", "loop_control", "notify", "changed_when", "failed_when", "delegate_to", "delegate_facts", "run_once", "connection", "collections", "async", "poll", "vars", "environment", "no_log", "module_defaults", "ignore_unreachable", "throttle", "remote_user", "debugger", "block", "rescue", "always", "import_tasks", "include_tasks", "include_role", "import_role", "meta", "include_vars"} keys.to_a.each do |k| keys.add("ansible.builtin.#{k}") end keys.add("ansible.legacy.import_tasks") keys.add("ansible.legacy.include_tasks") keys.add("ansible.legacy.include_role") keys.add("ansible.legacy.include_vars") keys.add("ansible.legacy.meta") keys end

Task-level special (non-module) keywords parse_task must skip when hunting for the module key, plus the same names fully qualified (directive() accepts either spelling) and the ansible.legacy.* spellings of the structural directives. A Set constant built once at load: this used to be rebuilt as a ~110-element array with two map copies per parsed task, then linear-scanned per key.

SUPPORTED_META_ACTIONS = Set {"clear_facts", "flush_handlers", "end_host", "end_play", "clear_host_errors", "noop", "refresh_inventory", "end_batch", "end_role", "reset_connection"}

meta: - a pseudo-module ("_meta"), like block:/include_tasks:, that acts on the executor's own state rather than running a plugin on a target.

clear_facts/flush_handlers/end_host/end_play/ clear_host_errors/noop/refresh_inventory are supported, as of 0.9.789 also end_batch/end_role/reset_connection (see TaskExecutor#execute_meta). flush_handlers added in round 18 - found via robertdebock's own roles, several of which (mysql, selinux, zabbix_repository, zabbix_server, core_dependencies) use ansible.builtin.meta: flush_handlers deliberately mid-role (e.g. flushing a "Update cache" handler BEFORE a later task that needs the freshly-added repo's package list) - skipping the task entirely, the previous behavior, isn't just a display-order cosmetic gap here: it caused a genuine functional divergence from ansible-playbook (a package install failing "Unable to locate package" because the apt cache update handler ran at the very end of the play instead of mid-role). end_host/end_play/clear_host_errors/noop/ refresh_inventory added after that - see TaskExecutor#execute_ meta for the exact semantics (each verified against ansible-playbook, including the non-obvious ones: end_play affects every currently-active host even if only ONE host's own when: actually reaches it; clear_host_errors does NOT resume execution in the current play, only exempts the host from the next one; refresh_inventory does NOT add hosts to the CURRENT play's own host loop either, only to a LATER play's - Ansible's own documented caveat). end_batch behaves exactly like end_play here - its one distinguishing behavior, ending only the current serial: batch, is meaningless while this engine doesn't model serial batching (one batch per play). end_role skips every remaining task of the CALLING role for the host that executes it (Ansible consumes them silently - no banners, no recap counters). reset_connection drops the host's persistent connection state (daemons + ssh ControlMaster sockets).

TASK_INCLUDE_VALID_KEYWORDS = Set {"action", "args", "collections", "debugger", "ignore_errors", "loop", "loop_control", "loop_with", "name", "no_log", "register", "run_once", "tags", "timeout", "vars", "when", "ansible.builtin.include_tasks", "ansible.builtin.include_role", "ansible.legacy.include_tasks", "ansible.legacy.include_role", "with_first_found", "with_items", "with_dict", "with_nested", "with_together", "with_sequence", "with_indexed_items", "with_fileglob", "with_file", "with_subelements", "listen", "environment", "changed_when", "failed_when", "until", "retries", "delay", "check_mode", "diff", "delegate_to", "delegate_facts", "connection", "ignore_unreachable", "throttle", "remote_user", "module_defaults"}

ansible-core 2.19's TaskInclude/HandlerTaskInclude parser validates the task dict against a fixed allowlist (TaskInclude's own VALID_INCLUDE_KEYWORDS frozenset) and raises 'X' is not a valid attribute for a TaskInclude for any key not on the list. become/become_user/become_method/become_flags/ become_exe are the ones that bite in practice - the Task class has them as fields (so a normal task accepts them) but the include-only classes deliberately do not, since the include statement itself is a control-flow directive and per-task privilege escalation doesn't compose with that. The andrewrothstein.java-oracle role's alpine-glibc-shim dependency had become: yes / become_user: root on an include_tasks: line and crystal accepted it (then failed downstream with a different error, rc=2 vs Ansible's rc=4). Same fix for RoleInclude (the parser class behind include_role:), whose _validate_attributes rejects any key not in its own fattributes

  • become/ is not there either, same error class 'X' is not a valid attribute for a IncludeRole.

import_tasks:/import_role: are NOT validated this way (ansible's own ImportPlaybook/ImportRole inherit the full Task fattributes and accept become:/become_user:), so this set is consulted only by parse_include_tasks and parse_include_role below. Starts from Ansible's TaskInclude.VALID_INCLUDE_KEYWORDS verbatim, then extends with the keys crystal's existing parse_include_tasks and the broader task parser already read off a task_hash so the FQCN ansible.builtin.include_tasks: form (parse_task's directive() helper keys on the bare name, but the FQCN form lands in the same task_hash) and the legacy with_first_found: form (githubixx.ansible_role_wireguard's "Include tasks depending on OS" pattern, see playbook_parser_spec.cr:1614) keep working. Ansible would reject some of these (notably with_first_found and the block-level attrs) with the same error; that's a separate gap from this round-194 fix, not one any role in ROLES_TESTED.md currently depends on behaving the ansible way. notify WAS on this allowlist too, until juju4.ansible_role_mattermost's own include_tasks: selinux.yml carrying a notify: key on the include line itself (RHEL-family round 60113) hit exactly this predicted gap live - ansible-core's actual VALID_INCLUDE_KEYWORDS (verified via python3 -c "import ansible.playbook.task_include as ti; print(sorted(ti.TaskInclude.VALID_INCLUDE_KEYWORDS))", ansible-core 2.19.4) does not include it - a task's OWN notify: is always valid (handled entirely separately, by the regular Task parser, not this one); notifying anything from the include directive line itself is not Ansible's syntax at all.

Class methods

apply_module_defaults(tasks : Array(Task), inherited : Hash(String, Hash(String, String))) : Nil

Fills each task's missing arguments from the module_defaults in scope. Runs as a post-parse pass over the whole task tree so play, block and task scope are handled in one place, with the nearest scope winning and the task's OWN arguments always winning over any default.

Source
check_template_quoting(path : String, content : String) : Nil

ansible-core 2.19's loader refuses at LOAD time (rc=4, on STDERR, with the same Origin block shape as every other YAML error) any file where an unquoted template parses as a collection used AS a mapping key - owner: {{ x }} is a flow mapping whose KEY is a flow mapping. Every other YAML error here comes from libyaml; this one doesn't (libyaml accepts the file), so the position comes from the source map's collection-key detection instead of a ParseException. Callers run it wherever YAML text is about to be parsed (playbook, role files, static imports), the way Ansible's single DataLoader path covers all of them.

Source
collecting_reserved_warnings
Source
condition_to_list(yaml : YAML::Any) : Array(String) | Nil

The same when:-list, kept as its own per-item condition strings for the STRICT evaluation path (see Task#when_condition_list for why the joined string can't serve there). nil unless when: is a list with more than one non-empty item - single-item lists and scalars evaluate identically through either representation.

Source
condition_to_string(yaml : YAML::Any) : String

Helper: Safely convert any YAML value to string This handles cases where YAML values might be booleans, integers, etc. when:/changed_when:/failed_when: may each be given as a list, which Ansible ANDs together - it is the idiomatic way to write a multi-clause condition and is used throughout widely-deployed roles (dev-sec's os_hardening alone has 79 of them).

This used to fall through safe_yaml_to_string's else branch to YAML::Any#to_s, producing a Crystal array literal - ["a_var", "'x' in pkgs"] - as the condition string. That was never evaluable: at best it was truthy by accident (a non-empty string), and at worst it hung the run outright, because an element containing " and " inside its quotes made ConditionalEvaluator split on nothing and recurse on the identical string forever.

Each element is parenthesized before joining so an element that is itself a compound condition (a or b) cannot bind loosely against its neighbours - (a or b) and (c), not a or b and c. Not private: RoleLoader also needs this for a roles: entry's/meta dependency's own when: (a role-level when: is Ansible's own RoleRequirement field too, same list-or-scalar shape as a task's).

Source
drain_static_import_notices
Source
extract_command_special_params(raw : String) : Tuple(String, Hash(String, String))

Strips " key=value" tokens (key one of command:/shell:'s own recognized special params - SPECIAL_COMMAND_PARAM_RE, any position in the string, matching Ansible's parse_kv which examines EVERY whitespace-delimited token, not just the trailing ones) off raw, returning the remaining command text and the extracted params. The command body itself, including any "=" it legitimately contains (VAR=1 somecommand), is never re-tokenized or rewritten.

Tokenizes via #split_shell_like (the same brace-depth-aware scanner #parse_inline_kv_params already uses) rather than a single backtracking regex over the whole string - two independent regex-based attempts here each had a real bug, in OPPOSITE directions, because a bare \{\{.*?\}\} alternative can't be trusted to stop at the boundary of a single template block:

  1. Under-matching: a value with exactly one {{ }} block and no further "}}" anywhere later in the string couldn't complete the pattern's trailing \s*\z at all, so extraction silently never happened (geerlingguy.solr's creates={{ solr_install_path }}/bin/solr).
  2. Over-matching: with a SECOND "{{ }}" block later in the string, the lazy .*? could backtrack straight through an entire separate key=value param - including the space between them and that param's own braces - to reach that later "}}", silently absorbing it into the wrong param's value. geerlingguy.svn's own "Create a test repository." task, svnadmin create testrepo chdir={{ svn_repository_home }} creates={{ svn_repository_home }}/testrepo/README.txt, hit this: chdir's value swallowed the entire trailing creates={{ ... }}/testrepo/README.txt text as part of itself, so chdir= failed outright ("No such file or directory") on the resulting, never-a-real-path string.

A brace-depth-tracking tokenizer (rather than backtracking regex matching) can't make either mistake: it splits on whitespace outside any {{ }}/{% %} span, so each key=value token's boundary is exactly right regardless of how many template blocks appear anywhere else in the string. Public (not private): the task executor ALSO needs this, at RUNTIME - a key=value whose key only appears after templating (the value side of a {% if %} branch, found live via kamaln7.swapfile) is not a token at parse time at all, so the executor's post-render pass over the RENDERED command line has to keep running too - it is the same function, applied to the text the module will really be handed.

Source
flatten_tag_list(yaml : YAML::Any) : Array(String)

Ansible flattens nested tag lists (tags: [[a, b], c] -> a, b, c), so a tag value may be an arbitrarily nested array; mapping the top-level entries with .as_s raised on the nested form and the whole task was skipped ("Cast from Array(YAML::Any) to String failed"). Public so RoleLoader's role-tag parse shares it.

Source
jinja_block_constructs(s : String) : Array(String)
Source
normalize_module_key(name : String) : String
Source
origin_carrying_builtin_deprecation?(name : String) : Bool

Whether a module's load-time deprecation warning carries an Origin block: only the two builtin-core deprecations do (issues #917/#918 - their console warning prints once per task occurrence, positioned at the module key for the bare and FQCN spellings and position-less <unknown> for ansible.legacy.); every collection-redirect entry above is Origin-less and dedups per run. Matched on the bare trailing segment because the table is keyed by bare and FQCN spellings alike.

Source
origin_error_render_at(message : String, path : String | Nil, source_map : YamlSourceMap | Nil, pos : YamlSourceMap::Pos | Nil) : String

A parse-time task error with source context, in ansible-core's _error_utils.SourceContext layout: "[ERROR]: <msg>", "Origin: <abs path>:<line>:<col>", a blank line, then the two preceding source lines + the target line (right-aligned line-number labels, tabs echoed as spaces) and a caret under the column. Best-effort: without a source position only the [ERROR] line is rendered. Same render as origin_error_render, but pointed at an EXPLICIT source position instead of a task-prefix lookup - for errors whose Origin is a specific sibling KEY inside the task (the static: attribute refusal points at the static: line itself, not the task). Public (not private) because RoleLoader's meta/main.yml dependency not-found raise builds its Origin block from the declaring role's meta file, outside this class (issue #12).

Source
parse(path : String) : Playbook

Parse playbook from file

Source
parse_adhoc_params(module_name : String, raw_args : String) : Hash(String, String)

Parse module parameters into a hash Parses an ansible ad-hoc command's -a string into module params. Two paths, matching Ansible's own ad-hoc arg handling: a string that looks like a JSON object (starts with { after stripping whitespace) and actually parses as one is used as the module params directly, with nested types kept; everything else uses the exact same rules as a playbook's own bare-string task arg (the yaml.as_s? branch of #parse_module_params below - see that branch's own comment for the full rationale): command:/ shell:/script:/raw: get the whole string as a command line plus any trailing key=value specials (creates=/removes=/chdir=/ executable=) stripped off the end; every other module gets Ansible's free-form key=value key2="quoted value" inline syntax. (verified live against ansible-core 2.19.11: a -a string that starts with { but is not valid JSON, e.g. {bad json, is NOT specially errored - Ansible's ModuleArgsParser falls through to the ordinary k=v split, so the string lands in _raw_params and whatever the module does with raw params (debug rejects it, command tries to execute it) is the only "error" there is; the fallback below reproduces exactly that). A parsed-but-not-object value ([1,2], "str") falls through the same way - Ansible 2.19.11 also treated those as free-form raw params.

The JSON-object path is why genuinely dict/list-shaped module args (expect's responses, command's argv, xml's namespaces) have any ad-hoc-CLI path at all: the k=v encoding cannot express a dict value, and before this path the whole -a string was silently ignored (module ran on its own defaults).

Source
parse_free_form_params(s : String, module_name : String) : Hash(String, String)

Parses Ansible's free-form inline key=value key2="quoted value" key3='{{ a_template }}' task-arg syntax into individual params. Tokenizes on whitespace outside single/double quotes (so a quoted value may itself contain spaces - msg="hello world", or a {{ }} expression with its own internal spaces), splits each token on its first = (a value may legitimately contain further = characters, e.g. base64 padding - only the first one is the key/value separator), and strips one layer of matching quotes from the value. A token with no = at all (a malformed fragment, or the whole string is actually a bare free-form value with no key=value pairs anywhere) is skipped, not raised on - callers already fall back to _raw_params for that case. Public entry point for the executor's runtime re-parse of a templated action:/local_action: free-form string (see Task#templated_action / TaskExecutor#resolve_templated_action): the module name is only known after substitution, so the rest of the string can't be shaped into params at parse time.

Source
parse_inline_kv_params_public(s : String) : Tuple(Hash(String, String), String | Nil)

Returns the key=value params plus, as a second tuple element, the leftover free-form text (tokens with no "=" - Ansible's parse_kv raw_params list, joined back with single spaces) or nil when every token was a key=value pair.

Source
parse_module_defaults(yaml : YAML::Any | Nil) : Hash(String, Hash(String, String))

Parse a single play Parses a module_defaults: mapping into {module name => {arg => value}}. Keys are normalized to the bare module name, because Ansible matches a short key against an FQCN task and vice versa - verified against ansible-core 2.19.4: a debug: key supplies defaults to an ansible.builtin.debug: task, and an ansible.builtin.debug: key to a debug: task.

An action-group key (group/aws) is expanded to its member modules via ActionGroups, which reads the installed collections' meta/runtime.yml exactly as Ansible does.

Source
parse_string(content : String, path : String = "playbook.yml") : Playbook

Parse playbook from string

Source
parse_tasks(tasks_yaml : Array(YAML::Any), play : Play, context : String, file_dir : String, known_vars : Hash(String, JSON::Any) | Nil = nil, role_path : String | Nil = nil, playbook_dir : String | Nil = nil, source_file : String | Nil = nil, source_map : YamlSourceMap | Nil = nil, source_prefix : String = "", handler : Bool = false) : Array(Task)

Parse a list of task-shaped YAML nodes, skipping (with a warning) any individual entry that fails to parse rather than failing the whole list. Shared by play.tasks, play.handlers, block/rescue/always, and (via RoleLoader) a role's tasks/main.yml and handlers/main.yml - public for that last one. file_dir is the directory of whichever YAML file tasks_yaml came from - used to resolve import_tasks:/ include_tasks: paths relative to that file (not the top-level playbook), and passed down unchanged for block/rescue/always since those stay within the same file.

role_path/playbook_dir are the two library/ search roots PythonModuleRunner uses at execution time for a role-private module (task.role_path and the executor's own playbook dir). Threaded through here so the unconditional unimplemented-module hard-stop can run the SAME lookup at parse time and stay graceful for exactly the tasks the runner would later execute - nil means "not knowable at this call site", never a false "source exists". Buffered like the top-level loads: runtime include_tasks/handlers loads go through here too, and a load Ansible refuses (a removed bare include: aborts before its task vars are ever evaluated) must not emit the reserved-name warnings it already scanned.

Source
parse_timeout_value(yaml : YAML::Any | Nil) : Tuple(Int64 | Nil, String | Nil)

Companion to #resolve_become's family: parses a timeout: keyword value - an integer literal (seconds) or a raw {{ }} expression (resolved per host at execution time by the executor). Returns {int, expr}: exactly one non-nil when the keyword is present, {nil, nil} when absent. A non-numeric literal keeps {nil, nil} (no limit) - Ansible's own post_validate rejects those, but the executor's 0/absent no-limit path is the gentler degradation. Public so RoleLoader's roles:-entry parser can share it verbatim.

Source
raise_unresolvable_module_error(as_written : String, resolved : String | Nil = nil, source_file : String | Nil = nil, source_map : YamlSourceMap | Nil = nil, prefix : String = "") : Nil

Raises UnresolvedModuleError for the tombstoned-removed hard-stop shape - with Ansible's own exact wording for that name (the generic couldn't-resolve text, or the collection's own specific removal message where Ansible prints one instead) - returns normally for every other name. as_written is the module/action name exactly as the task wrote it - Ansible's message echoes the source spelling, not any resolved form (except a tombstone with its own fixed message, like docker_compose's).

resolved is the name #resolve_module_name landed on, when it resolved at all: the tombstone is consulted for the RESOLVED FQCN too, not just the as-written one - real's loader resolves a task's name first and then checks the resolved FQCN against the collection's meta/runtime.yml plugin_routing tombstones; a bare spelling that lands on a tombstoned FQCN is therefore refused without every bare spelling needing its own table entry (which would wrongly refuse spellings real resolves through redirects that are NOT tombstoned, like the builtin-runtime ones).

A name krikri cannot resolve natively (resolved nil) also gets the builtin-runtime redirect hop: a bare spelling whose ansible_builtin_runtime.yml redirect lands on a tombstoned collection FQCN refuses with THAT FQCN's own removal message when the target collection is installed on this controller (bare docker_service: -> community.general.docker_service -> community.general 2.0.0's tombstone: rc=1 and the community.general.docker_service message, live-verified vs 2.19.11 for the bare, ansible.builtin.- and ansible.legacy.- qualified spellings; issue #8). Without the collection real's redirect chain dies at the collection import and the name falls back to the generic couldn't-resolve refusal - which the bare spellings' nil entries in the table still provide. source_file/source_map/prefix build the RemovedModuleError render ([ERROR] line + task Origin) where the raise site has a source position.

Source
record_static_import(path : String) : Nil
Source
redirect_deprecation_fqcn(name : String) : String | Nil

The FQCN that one of the deprecations above echoes on the console, nil for every name that is not one of them. A caller compares it against the as-written spelling to tell whether real kept the YAML tag on the name: an as-written FQCN keeps it (a positioned Origin block), a bare or ansible.legacy spelling is rebuilt by joining the collection onto the name and prints the position-less Origin: <unknown> form instead (issue #973).

Source
redirect_deprecation_result_entry(name : String) : JSON::Any | Nil

The deprecations result entry real attaches to every executed task result for a deprecated module (live-verified via register: r.deprecations == [{"msg": "community.mysql.mysql_info has been deprecated. Use ansible.mysql.mysql_info instead.", "collection_name": "community.mysql", "version": "6.0.0", "deprecator": {"resolved_name": "community.mysql", "type": null}}]

  • note the msg carries NO removal tail; only the console line does). nil for every name that is not one of the deprecations.
Source
redirect_deprecation_text(name : String) : String | Nil

The exact console [DEPRECATION WARNING] text ansible-core 2.19.11 prints for one of the deprecations above (live-verified: the loader's warning_text joined onto the "has been deprecated." stem, followed by Display's removal-version tail naming the collection and version). nil for every name that is not one of the deprecations.

Source
resolve_include_path(file_rel : String, file_dir : String, missing_dir : String | Nil = nil) : String

import_tasks: is resolved at PARSE time - the imported file's tasks are spliced directly into the caller's task list (returning Array(Task) rather than a single wrapping Task, unlike block:). Per ansible-doc: "Most keywords, including loops and conditionals, only apply to the imported tasks, not to this statement itself" - so the import's own when:/tags: are applied to EACH imported task individually. loop: is not supported on import_tasks (use include_tasks instead) and is simply ignored here. Returns nil when the YAML node isn't an import_tasks: entry at all. Resolves an include_tasks:/import_tasks: file path, trying the direct interpretation first (relative to file_dir, the including file's own directory) and falling back to stripping a leading tasks/ from file_rel and retrying against the same directory if that doesn't exist. Ansible's own include-path search considers multiple roots (including the role root itself, not just the including file's directory), so a role convention like include_tasks: tasks/foo.yml written inside a file that's already directly in <role>/tasks/ resolves there correctly - our single-root resolution doubled it into <role>/tasks/tasks/foo.yml instead. Found via linux-system-roles' journald role, whose tasks/main.yml does exactly this (include_tasks: tasks/set_vars. yml) - a common enough convention (explicit tasks/ prefix even from within the tasks dir) that this isn't specific to one role.

Source
resolve_module_name(raw : String) : String | Nil

Resolves a task's module key (as written) to the AVAILABLE_PLUGINS entry it refers to - itself unchanged if already fully qualified (or a pseudo-module like "_block"), otherwise the first MODULE_SEARCH_COLLECTIONS prefix that matches. nil if nothing matches at all (a genuinely unimplemented/unknown module).

Source
stats(playbook : Playbook) : Hash(String, Int32)

Get statistics about playbook

Source
strip_line_continuation_tokens(raw : String) : String

Strips standalone line-continuation backslashes from a free-form command:/shell: task string, exactly the way Ansible's own controller does at task-parse time: the free-form string goes through parse_kv → split_args BEFORE any Jinja templating, and split_args silently DROPS every whitespace-delimited token that is exactly \ (outside quotes), also suppressing the newline it would otherwise rejoin at the end of such a line. This is why the common role idiom of a folded YAML scalar with trailing backslash continuations works at all under Ansible:

shell: > set -o errexit;
set -o pipefail;
cfssl gencert ...

PyYAML folds that to "...errexit; \ set -o pipefail..." - backslash, folded space and all - which bash itself REJECTS: the \ starts a command word " set" (escaped leading space) that is no builtin, so bash dies with exit 127 and "line 1: set: command not found" (the doubled space in the message is the word's own leading space). Ansible never hands bash that string - split_args has already removed every standalone backslash, so bash receives "set -o errexit; set -o pipefail; cfssl gencert ..." and runs it. Found live via githubixx.kubernetes_ca's "Generate the etcd certificate authority (CA) and private key" task (round900207): this engine passed the folded text through verbatim and the task died with exactly that bash 127, while a minimal local repro (identical folded shape, executable: /bin/bash) succeeds under ansible-playbook with the backslash-free command above.

The transformation here is a matches split_args' per-line token walk (dropped lone-\ tokens, the quote state that suppresses the drop inside quotes, the {{ }}/{% %}/{# #} depth tracking, the empty-token run that preserves internal space runs, and the newline-restoration suppression for lines containing a lone \), followed by join_args' reassembly - for any string without a standalone \ token that round-trip is byte-for-byte lossless (verified against real split_args on the round900207 repro shape, including its preserved double space and trailing newline), so the early includes?('\\') bail-out below keeps every existing command:/shell: string untouched. A backslash glued to other characters (\;, path\to) or inside quotes is NOT a standalone token and survives, same as real split_args. Only the free-form string form gets this treatment - a cmd:/_raw_params dict key or post-render variable content never goes through Ansible's split_args, so callers apply this to parse-time raw text only.

Source
validate(playbook : Playbook) : Array(String)

Validate playbook structure

Source
validate_register_name(value : String, source_file : String | Nil = nil, source_map : YamlSourceMap | Nil = nil, prefix : String = "") : Nil

Ansible validates register:'s value as a variable-name identifier at task-load time and refuses the whole run for anything else - see InvalidRegisterError's own comment. The value is checked RAW: Ansible never templates a register: value, so register: '{{ var }}' is rejected whatever var resolves to (webbylab.sources, round 900914).

Source
warn_duplicate_map_keys(path : String, source_map : YamlSourceMap, io : IO = STDERR) : Nil
Source
warn_empty_import(path : String, io : IO = STDERR) : Nil

ansible-core 2.19 (constructor.py construct_mapping) prints, at LOAD time and before any PLAY output, one warning per duplicate mapping-key occurrence, with the Origin at the DUPLICATE (second) key's own position and the help_text line after the excerpt: [WARNING]: Found duplicate mapping key 'bookworm'. Origin: /abs/path/pb.yml:6:7 <up to two preceding source lines + the duplicate key's line> ^ column 7 Using last defined value only. The warning is cosmetic - the last value still wins. Public because role_loader warns the same way for the vars/task files it loads through the same YamlSourceMap scan. io exists for tests. ansible-core's load_list_of_tasks warns (plain, uncolored) when a statically imported file parses to nothing, naming the resolved path.

Source
warn_reserved_toplevel_vars(path : String, keys : Array(String), role_warned : Set(String) | Nil = nil) : Nil
Source
warn_reserved_toplevel_vars_file(path : String) : Nil

Same warning for one play vars_files file, keyed off a parse of the file itself (the caller only has a candidate path). Tolerant like the runtime vars_files loader: a missing or unparseable file stays silent - ansible-playbook does not abort for a vars_files entry it cannot use either.

Source

Nested types