Search code examples
ansibleansible-facts

Can't set Ansible fact as integer


I am trying to extract the major distro version (which ansible_facts holds as a string) and store it as an integer for later < or > comparison to an integer. When I do this:

- set_fact:
    distromajor: "{{ ansible_facts['distribution_major_version'] | int }}"

I find distromajor holds "7" instead of 7.
So later comparisons fail. In fact, the only way I can get it to work is to compare like this:

(distromajor|int >=6) and (distromajor|int <= 8)

Is this expected behaviour?
Why can I not save the distro major version as an int?

The closest SO question does not explain why a later integer comparison fails without reconverting the distromajor variable to integer at time of comparison.


Solution

  • Q: "Is this expected behavior?"

    A: Yes. This is the expected behavior, in Ansible.


    Q: "Why can I not save the distro major version as an int?"

    A: Ansible decided you can't (todo: reference to source code needed). In YAML, there are three basic primitives:

    • mappings (hashes/dictionaries)
    • sequences (arrays/lists)
    • scalars (strings/numbers)

    As you can see, the scalars are both strings and numbers. But, for some reason unknown to me, Ansible decided that any "{{ scalar }}" expression can return only string or boolean. For example,

        - set_fact:
            distromajor: "{{ ansible_facts['distribution_major_version']|int }}"
        - debug:
            var: distromajor
        - debug:
            msg: "{{ distromajor|type_debug }}"
    

    gives a string despite the explicit conversion to integer, as you've already found out

      distromajor: '20'
      msg: AnsibleUnsafeText
    

    Note: In the above example, scalar represents a scalar result of the Jinja expression. If there is no expression Ansible doesn't call Jinja. As a result, this will preserve the evaluated variable's type. For example,

        - set_fact:
            v2: "{{ v1 }}"
          vars:
            v1: 123
        - debug:
            msg: |
              v2: {{ v2 }}
              v2 is {{ v2 | type_debug }}
    

    gives

      msg: |-
        v2: 123
        v2 is int
    

    However, if there is any expression, even an arithmetic one, Ansible calls Jinja to evaluate the expression and the result will be a string

        - set_fact:
            v3: "{{ v1 + 1 }}"
          vars:
            v1: 123
        - debug:
            msg: |
              v3: {{ v3 }}
              v3 is {{ v3 | type_debug }}
    

    gives

      msg: |-
        v3: 124
        v3 is str
    

    Update.

    If you want to keep a variable as integer put it into a dictionary. For example,

      my_dict_yaml: |
        distromajor: {{ ansible_distribution_major_version }}
      my_dict: "{{ my_dict_yaml|from_yaml }}"
    

    gives

      my_dict:
        distromajor: 20
      my_dict.distromajor|type_debug: int
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        my_dict_yaml: |
          distromajor: {{ ansible_distribution_major_version }}
        my_dict: "{{ my_dict_yaml|from_yaml }}"
    
      tasks:
    
        - setup:
            gather_subset: distribution_major_version
        - debug:
            var: ansible_distribution_major_version
        - debug:
            var: ansible_distribution_major_version|type_debug
    
        - debug:
            var: my_dict
        - debug:
            var: my_dict.distromajor|type_debug