Search code examples
puppetansiblehiera

Puppet hiera equivalent in Ansible


hiera.yaml

---
:hierarchy:
- node/%{host_fqdn}
  - site_config/%{host_site_name}
  - site_config/perf_%{host_performance_class}
  - site_config/%{host_type}_v%{host_type_version}
  - site/%{host_site_name}
  - environments/%{site_environment}
  - types/%{host_type}_v%{host_type_version}
  - hosts
  - sites
  - users
  - common
# options are native, deep, deeper
:merge_behavior: deeper

We currently have this hiera config. So the config gets merged in the following sequence common.yaml > users.yaml > sites.yaml > hosts.yaml > types/xxx_vxxx.yaml > etc. For the variable top hierarchies, it gets overwritten only if that file exists.

eg: common.yaml

server:
  instance_type: m3.medium

site_config/mysite.yaml

server:
  instance_type: m4.large

So for all other sites, the instance type will be m3.medium, but only for mysite it will be m4.large.

How can I achieve the same in Ansible?


Solution

  • I think that @Xiong is right that you should go the variables way in Ansible.
    You can set up flexible inventory with vars precedence from general to specific.

    But you can try this snippet if it helps:

    ---
    - hosts: loc-test
      tasks:
        - include_vars: hiera/{{ item }}
          with_items:
            - common.yml
            - "node/{{ ansible_fqdn }}/users.yml"
            - "node/{{ ansible_fqdn }}/sites.yml"
            - "node/{{ ansible_fqdn }}/types/{{ host_type }}_v{{ host_type_version }}.yml"
          failed_when: false
    
        - debug: var=server
    

    This will try to load variables from files with structure similar to your question.
    Nonexistent files are ignored (because of failed_when: false).
    Files are loaded in order of this list (from top to bottom), overwriting previous values.

    Gotchas:

    • all variables that you use in the list must be defined (e.g. host_type in this example can't be defined in common.yml), because list of items to iterate is templated before the whole loop is executed (see update for workaround).

    • Ansible overwrite(replace) dicts by default, I guess your use case expects merging behavior. This can be achieved with hash_behavior setting – but this is unusual for Ansible playbooks.

    P.S. You may alter top-to-bottom-merge behavior by changing with_items to with_first_found and reverse the list (from specific to general). In this case Ansible will load variables from first file found.

    Update: use variables from previous includes in file path.

    You can split the loop into multiple tasks, so Ansible will evaluate each task's result before templating next file's include path.
    Make hiera_inc.yml:

    - include_vars: hiera/common.yml
      failed_when: false
    - include_vars: hiera/node/{{ ansible_fqdn }}/users.yml
      failed_when: false
    - include_vars: hiera/node/{{ ansible_fqdn }}/sites.yml
      failed_when: false
    - include_vars: hiera/node/{{ ansible_fqdn }}/types/{{ host_type | default('none') }}_v{{ host_type_version | default('none') }}.yml
      failed_when: false
    

    And in your main playbook:

    - include: hiera_inc.yml
    

    This looks a bit clumsy, but this way you can define host_type in common.yaml and it will be honored in the path templating for next tasks.

    With Ansible 2.2 it will be possible to include_vars into named variable (not global host space), so you can include_vars into hiera_facts and use combine filter to merge them without altering global hash behavior.