--- - name: entrypoint hosts: all user: ansible become: yes gather_facts: true vars: test_run_roles: # - network # - repos # - yum # - ssh # - users # - systemd # - rsyslog # - audittrail # - nhc # - prometheus # - sysctl # - limits # START # - ntp # - os_packages - hypervisor_prep # - vxlan # - firewalld tasks: ######## load core group_vars # # load the following core environment files under vars['steel'] # - inventory/group_vars/cluster.yml # - inventory/group_vars/roles.yml # - inventory/group_vars/networks.yml - name: load core environment configuration block: - name: set env import variables ansible.builtin.set_fact: env_files: - 'cluster.yml' - 'roles.yml' - 'networks.yml' env_dir: "{{ ansible_inventory_sources[0] | dirname }}/group_vars" config_namespace: "steel" - name: include vars from core config files ansible.builtin.include_vars: file: "{{ env_path }}" name: "env_import_{{ env_namespace }}" loop: "{{ env_files }}" loop_control: loop_var: entry vars: env_path: "{{ env_dir }}/{{ entry }}" env_namespace: "{{ entry.split('.yml')[0] }}" - name: append env vars to temp dict ansible.builtin.set_fact: env_dict: "{{ env_dict | default({}) | combine (env_import, recursive=True) }}" loop: "{{ lookup('ansible.builtin.varnames', 'env_import_').split(',') }}" loop_control: loop_var: entry vars: env_import: "{{ vars[entry] }}" - name: copy dict of env vars under top level namespace set_fact: { "{{ config_namespace }}": "{{ env_dict }}" } ######## generate list of roles to run against host - name: list all hostvars groups where the host is a member ansible.builtin.set_fact: _hostvars_groups: "{{ hostvars[ansible_hostname]['group_names'] | default([]) + (['all']) }}" - name: intersect hostvars groups with role groups, uses role groups to dictate role runtime order ansible.builtin.set_fact: active_role_groups: "{{ role_groups | intersect(group) }}" loop: - "{{ _hostvars_groups }}" loop_control: loop_var: entry vars: group: "{{ entry }}" role_groups: "{{ vars[config_namespace]['roles'] | list }}" - name: list roles to run against "{{ ansible_hostname }}" ansible.builtin.set_fact: _run_roles: "{{ _run_roles | default([]) + vars[config_namespace]['roles'][entry] }}" loop: "{{ active_role_groups }}" loop_control: loop_var: entry # this is required for roles to be dual purpose (client/server) # - roles.yml may have role 'ntp' under groups 'all' and again under 'ntpd' # - the 'ntp' role will be run only once for the 'all' group but will select a client/server setup based on ansible inventory membership in the 'ntpd' group - name: select only unique roles, preserve runtime order ansible.builtin.set_fact: _run_roles: "{{ _run_roles | unique }}" - name: roles to run on "{{ ansible_hostname }}" ansible.builtin.debug: msg: - run roles "{{ _run_roles }}" # global variables we can use for evaluations, we use these to determine role/task execution: # - "{{ active_role_groups }}" # role groups that host is a member of, whitelisted/intersected from vars[config_namespace]['roles'] / group_vars/roles.yml # - "{{ _run_roles }}" # all roles to run on host, role runtime execution is ordered from vars[config_namespace]['role'] / group_vars/roles.yml # # example usage of active_role_groups: # # - a task can check if host is in the monitoring group # when: "'monitoring' in active_role_groups" # this method includes all groups host is a member and also the 'all' group, crucially it only includes groups whitelisted in vars[config_namespace]['role'] # when: "'monitoring' in hostvars[ansible_hostname]['group_names']" # this is a ansible native test but excludes the 'all' group # when: inventory_hostname in groups['monitoring'] # this is a ansible native method, probably the easiest ansible native method, notice inventory_hostname usage where external dns or hosts file entries are not available # # - ansible.builtin.debug: # msg: # - "{{ groups['all'] }}" # - "{{ hostvars[ansible_hostname]['group_names'] }}" # - "{{ active_role_groups }}" # - "{{ _run_roles }}" ######## load vars from each target role # try to code around this method to make code more modular/portable: # - strategies such as pull other host facts with delegate_facts where possible, chain roles where applicable - run roles on all hosts with skip tasks where unavoidable # # if this task is truely necessary extend its use further to fit future design patterns: # # info.yml should load variables into config_namespace and also contain merge OR overwrite functions to source from group_vars/.yml # each role main.yml would run the info.yml as the first task also, to be a catch_all function for any future portability (it could always check if vars[config_namespace] was already populated) # role tasks should all source variables from vars[config_namespace] # - name: "Load variables {{ ansible_hostname }}" # ansible.builtin.include_role: # name: "{{ role_var }}" # public: true # tasks_from: info.yml # loop: "{{ _run_roles }}" # loop_control: # loop_var: role_var # label: Setup {{ role_var }} variables on {{ ansible_hostname }} # - name: Output vars structure # ansible.builtin.debug: # msg: "{{ vars[role_var] }}" # when: vars[role_var] is defined # loop: "{{ _run_roles }}" # loop_control: # loop_var: role_var # tags: never ######## run roles against hosts - name: override run_roles for testing set_fact: _run_roles: "{{ test_run_roles }}" when: test_run_roles is defined - name: run roles on "{{ ansible_hostname }}" ansible.builtin.include_role: name: "{{ entry }}" loop: "{{ _run_roles }}" loop_control: loop_var: entry label: run {{ entry }} role on {{ ansible_hostname }}