Strange behaviour in Ansible 2.19: String converted to an int

I have the following template:

---
- name: Test MAC address validity
  hosts: localhost
  gather_facts: False

  tasks:
    - name: Debug info
      debug:
        var: macadres

I created a job in AWX that calls this playbook, and have turned on ‘Prompt on launch’ for variables.

Case 1:
I edit the AWX job template and add macadres: '11:33:22:55:44:11' in the variables, run the playbook, click next on the ‘Variables’ prompt, and launch.
Result:

ok: [localhost] => {
    "macadres": "11:33:22:55:44:11"
}

Case 2:
I edit the AWX job template and clean the Variables section. I run the template, fill in macadres: '11:33:22:55:44:11 in the Variables prompt, click next, and launch.
Result:

ok: [localhost] => {
    "macadres": 8986232651
}

So when running as regular variable, everything works fine, when running as extra variable the string is converted to an int.

Ansible: 2.19.7
AWX: 24.6.1

I also did the same tests with Ansible 2.18.14 and here everything works fine.

I’m not sure if this is a bug, or expected behaviour. If it’s a bug I’ll submit an issue on Github.

The issue you are running into is a quoting problem. You can produce the same 8986232651 result with other versions of Ansible. The thing is, when the non-quoted value 11:33:22:55:44:11 is ingested, it’s treated as a set of base60 decisextets (sexagesimals as decimal numbers). In other words, it’s being interpreted as a time designation. Observe:

((((11 * 60 + 33) * 60 + 22) * 60 + 55) * 60 + 44) * 60 + 11 = 8986232651
    --        --         --         --         --         --

Get your quoting right, and the “problem” will go away.

BTW, sexagisimal ints were introduced in YAML 1.1.
The YAML 1.2 spec doesn’t mention them directly, but it appears applications (such as Ansible) can implement them (and any other scalar presentation) through TAGS.

4 Likes

Job 1 variables:

Job 2 variables:

One of these jobs converts macadres to an integer, the other one treats it as a string.

Care to guess which one does what?

The playbook that’s run is the exact one that I posted in the first post: 1 task with debug: var: macadres in it, so no quotes there.

No, I can not. the EE with Ansible 2.18 will show the string in both cases.

Well, I can. With your playbook, unchanged, from the command line by selectively quoting the --extra-vars value in question. Behold:

utoddl@tango:~/ansible$ ansible-playbook macadres.yml -e '{"macadres": "11:33:22:55:44:11"}'

PLAY [Test MAC address validity] *********************************************************************************

TASK [Debug info] *********************************************************************************
ok: [localhost] => 
  macadres: '11:33:22:55:44:11'

PLAY RECAP *********************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

utoddl@tango:~/ansible$ ansible-playbook macadres.yml -e '{"macadres": 11:33:22:55:44:11}'

PLAY [Test MAC address validity] *********************************************************************************

TASK [Debug info] *********************************************************************************
ok: [localhost] => 
  macadres: 8986232651

PLAY RECAP *********************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The only difference is the quoting of 11:33:22:55:44:11 in the --extra-vars. This is with Ansible core 2.18.12, python version = 3.14.3, libyaml = True.

I’m not denying there’s a difference between 2.18 and 2.19 wrt this behavior, but I/you/we can get the same set of values (integer 8986232651 or string ‘11:33:22:55:44:11’) by playing with the quoting of extra-vars in 2.18.

I don’t have a 2.19 handy at the moment, and it could be complicated by having AWX in the mix. Regardless, 8986232651 is the expected decimal integer representation of the sexagesimal scalar 11:33:22:55:44:11.

2 Likes

The behavior of extra vars behaving differently between 2.18 and 2.19 with AWX in the mix seems similar to an issue I had in 2025. The gist is that I had an AWX job template that would take a datetime input string passed as an extravar; when my execution environment used Ansible 2.18 it took the datetime as a literal string and worked for my purposes. When I switched this same job to use a 2.19 EE, the string would not be preserved literally, but interpreted into a different, incompatible with my use case date time string. The thread was here AWX extra_var for datetimes exhibiting different behavior between Ansible 2.18 and 2.19

Sorry if this isn’t terribly useful, but there was a reply that did elaborate a bit on why the behavior might be happening. At any rate, I’m glad I got to learn about sexagesimals.

1 Like

I think this is very useful information. Particularly that “…AWX writes out the extra vars file in YAML format for consumption by ansible-playbook”.

So the trick is to find a way to express 11:33:22:55:44:11 in a way that will get written to the extra-vars file such that when it gets read back in it ends up as a string instead of a decimal representation of a sexagesimal.

@ildjarn , maybe you can try

macadres: !!STR 11:33:55:44:11

in your AWX extra-vars?

If you’re willing to try a hacky but reliable method, write it in a way that’s always a string, like

macadres: S11:33:55:44:11    # Note the leading 'S' to force it to be a string

and strip off the leading non-sexagesimal character in the consuming playbook. It’s not pretty, but I bet it will work.

1 Like

The data is provided by an external system.
Fortunately, most, if not all, MAC addresses seem to have at least one letter in them (e.g. 00:1c:4a:3b:55:ff’, which seems to be enough to not get converted to an int. But changing the source data format is not going to work, I’m afraid.
I’ll guess I’ll add a check in the module to see if the string passed is an integer, if it is, it should be converted back to a sexagesimal number.

FWIW: The same behaviour is shown with variables that are added through an AWX survey, even if the survey type is set to ‘Text’.

1 Like

Something interesting:

from Sexagesimal - Wikipedia

YAML 1.2 was released in 2009, but pyyaml doesn’t support it (yet).

1 Like

I believe the difference in outcome here is the relationship of a few different moving parts. First I’ll show the outcome, and then try to explain why I think it is happening:

(ansible-2.18) $ ansible localhost -m debug -a var=macadres -e '{macadres: !unsafe 11:33:22:55:44:11}'
localhost | SUCCESS => {
    "macadres": "11:33:22:55:44:11"
}

(ansible-2.19) $ ansible localhost -m debug -a var=macadres -e '{macadres: !unsafe 11:33:22:55:44:11}'
localhost | SUCCESS => {
    "macadres": 8986232651
}

As you can see with YAML fed to ansible-core that looks like the following, the outcome is that the value is treated as a string on 2.18, but on 2.19 it is not.

{macadres: !unsafe 11:33:22:55:44:11}

Prior to 2.19 !unsafe implied the value was a string. This is no longer the case with 2.19 and newer.

Now, if you notice, there is a lack of quotes around the mac address in the YAML.

Now, I don’t have researched evindence, so this is just a hypothesis…

While pyyaml has support for sexagesimals, it appears as though whatever serializer/deserializer may be in use in AWX, potentially in the AWX UI, doesn’t support them, and as such doesn’t believe it needs to quote them. As a result, the value that is being sent as extra vars doesn’t contain the quotes.

1 Like

Here’s a filter plugin that will convert these integers back into their MAC addresses. If you pass it a string (i.e. it’s already a MAC) that string will be returned unchanged. Put it in a filter_plugins directory relative to your playbook.

#!/usr/bin/python
# filter_plugins/int2sexagesimal.py

def int2sexagesimal(value, minpairs=6):
    """
    Converts an integer to a sexagesimal string
    dd:dd:dd... where each 'd' is a decimal digit,
    each pair of digits is a zero-padded decimal number
    between 00 and 59.

    A non-integer value is returned unchanged.

    This is useful to recover a MAC address which happens to
    contain all decimal digit pairs below 60 and YAML 1.1 interpreted it as
    a sexagesimal integer instead of a string. For example the following
    two YAML 1.1 lines are equivalent:
        mac: 11:33:22:55:44:11
        mac: 8986232651
    They both assign to 'mac' the integer 8986232651 by this conversion:
        ((((11*60 + 33)*60 + 22)*60 + 55)*60 + 44)*60 + 11 = 8986232651
            --      --       --       --       --       --
    int2sexagesimal(8986232651, 6) will return "11:33:22:55:44:11".

    It can also convert seconds into hours, minutes, and seconds
    for values below 216000 - the number of seconds in 60 hours.
    int2sexagesimal(36125, 3) returns "10:02:05"
        i.e 10 hours, 2 minutes, 5 seconds
    """
    if not isinstance(value, int):
        return value

    if value < 0:
        sign = "-"
        value = abs(value)
    else:
        sign = ""

    acc = []
    while True:
      acc.insert(0, f"{value % 60:02d}")
      value //= 60
      if value == 0:
          break

    while len(acc) < minpairs:
      acc.insert(0, '00')

    return f"{sign}{':'.join(acc)}"

class FilterModule(object):
    ''' Ansible core jinja2 filters '''

    def filters(self):
        return {'int2sexagesimal': int2sexagesimal}

if __name__ == "__main__":
    print(f"     86399: {int2sexagesimal(     86399, 3)}")
    print(f"      3600: {int2sexagesimal(      3600, 1)}")
    print(f"      -500: {int2sexagesimal(      -500, 1)}")
    print(f"     36125: {int2sexagesimal(     36125, 3)}")
    print(f"8986232651: {int2sexagesimal(8986232651, 3)}")
    print(f"    216000: {int2sexagesimal(    216000, 3)}")
    print(f"'a string': {int2sexagesimal('a string', 8)}")

Running it as a stand-alone Python program it produces these results:

$ filter_plugins/int2sexagesimal.py
     86399: 23:59:59
      3600: 01:00:00
      -500: -08:20
     36125: 10:02:05
8986232651: 11:33:22:55:44:11
    216000: 01:00:00:00
'a string': a string

Here’s a playbook that uses it:

---
- name: Test MAC address recovery with int2sexagesimal filter
  hosts: localhost
  gather_facts: False
  vars:
    macs:
      - 8986232651         # an int; should have been a MAC address
      - 11:33:22:55:44:11  # sexagesimal! contains 2-digit decimals all <60
      - 11:33:22:55:44:1A  # contains 'A'
      - 11:63:22:55:44:11  # contains '6' (>5) in the tens place
      - 11:73:22:55:44:11  # contains '7' (>5) in the tens place
      - 'a string'         # contains a string
  tasks:
    - name: Display the values and types
      debug:
        msg: "{{ macs | map('int2sexagesimal')
                 | zip(macs | map('int2sexagesimal') | map('type_debug'))
                 | map('join', ', ') }}"

and the results of running that playbook:

$ ansible-playbook macadres.yml

PLAY [Test MAC address recovery with int2sexagesimal filter] ******

TASK [Display the values and types] *******************************
ok: [localhost] => 
  msg:
  - 11:33:22:55:44:11, str
  - 11:33:22:55:44:11, str
  - 11:33:22:55:44:1A, AnsibleUnicode
  - 11:63:22:55:44:11, AnsibleUnicode
  - 11:73:22:55:44:11, AnsibleUnicode
  - a string, AnsibleUnicode

I briefly made the returned str an AnsibleUnicode string but the extra code in the filter wasn’t worth it to me. It wasn’t hard, it was just … too “magical” for my taste.

I hope this is useful to you, @ildjarn .

2 Likes

Here’s what I hope is my final cut at a int2sexagesimal filter. It occurred to me that, besides “fixing” MAC addresses mangled by YAML 1.1, base 60 grouping is only useful for <hours>:<minutes>:<seconds>, or maybe <degrees>:<arc-minutes>:<arc-seconds>. For other conversions you need to be able to specify a list of bases. This version allows that. The EXAMPLES section includes converting seconds to ['years', 'days', 'hours', 'minutes', 'seconds'] and teaspoons to ['gallons', 'quarts', 'pints', 'cups', 'tablespoons', 'teaspoons']. I’m sure the lack of that capability has been slowing down the uptake of Ansible, so there’s that excuse gone. :grinning_cat: Provide whatever list of bases fits your data.

Not all solutions have a pressing problem. But if you happen to bump into a problem for which this is a solution, you are welcome to it! Happy Filtering!

#!/usr/bin/python
# filter_plugins/int2sexagesimal.py
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import annotations

DOCUMENTATION = '''
name: int2sexagesimal
author: Todd Lewis (https://forum.ansible.com/u/utoddl/summary)
type: filter
short_description: convert integer to sexagesimal string
version_added: "2.18"
description:
  - |
    This filter plugin converts an integer to the equivalent sexagesimal
    string. Sexagesimals consist of decimal integers joined by colons. All but
    the first such integer must be less than the corresponding base (60 by
    default) and left-padded with zeros to a length equal to the length of
    string representing O(base - 1). The first joined integer may be shorter,
    longer, or that same length, possibly left-padded with zeros if both
    O(minparts) and O(maxparts) are specified and equal, but it may be longer
    and larger than the O(base) if necessary to satisfy the O(maxparts) constraint.

  - |
    Commonly used for converting C(seconds) to C(<hours>:<minutes>:<seconds>),
    C(int2sexagesimal) also can be useful to recover a MAC address which was
    inadvertently interpreted as a sexagesimal integer constant by YAML 1.1.
    For example, V(mac: 11:33:22:55:44:11) is equivalent to V(mac: 8986232651) in YAML 1.1.

  - |
    If the input integer is negative, a C(-) will be prefixed to the resulting
    string.

  - |
    A non-integer input value is returned unchanged. This simplifies filtering
    MAC addresses which may have been mangled by YAML 1.1 as described
    above: V({{ macs | map('int2sexagesimal', minparts=6, maxparts=6\\) }})
    Setting minparts equal to maxparts ensures the first part is zero-padded.

options:
  maxparts:
    description:
      - The maximum number of colon-delimited parts in the resulting string
        representation. For example, the result C(143:14:30) has three parts.
    type: integer
    required: false

  minparts:
    description:
      - The minimum number of colon-delimited parts in the resulting
        string representation.
    type: integer
    required: false

  base:
    description:
      - The value at which a corresponding part's value carries over to the
        next leftward part.

      - Does not apply to the left-most part if O(maxparts) is specified and
        reached. For example, the result C(60:59:59) represents 60 hours, 59
        minutes, 59 seconds with the default O(base) of 60 and O(maxparts=3).
        Without setting O(maxparts) the result would be C(01:00:59:59).

      - O(base) can also be a list of integers. For example O(base=[100, 365, 24, 60, 60])
        with O(maxparts=5) would convert seconds to
        C(<years>:<days>:<hours>:<minutes>:<seconds>) and the left-most
        base value would not be used. But with O(maxparts=6)
        and a large enough input value the result would include a leading
        C(<millenium>:) part.

      - If more parts are generated than specified in the O(base) list, the
        left-most (or only) O(base) value is used repeatedly.
    type: integer or list of integers
    required: false
    default: 60

  _value:
    description:
      - The integer to convert to a sexagesimal string, or any other non-integer.
        A non-integer O(_value) is returned unchanged.
    type: raw
    require: true
'''

EXAMPLES = '''
- name: Restore mangled MAC address YAML 1.1 constant
  ansible.builtin.debug:
    msg: '{{ 8986232651 | int2sexagesimal() }}'
# TASK [Restore mangled MAC address YAML 1.1 constant] ******************************
# ok: [localhost] => 
#   msg: '11:33:22:55:44:11'

- name: Display converted integer 3753 with various parameters
  ansible.builtin.debug:
    msg:
     - "# 1 hour, 2 minutes, 33 seconds"
     - "3753 | int2sexagesimal()                       => {{ '%11s' | format(3753 | int2sexagesimal()) }}"
     - "3753 | int2sexagesimal(minparts=2)             => {{ '%11s' | format(3753 | int2sexagesimal(minparts=2)) }}"
     - "3753 | int2sexagesimal(maxparts=2)             => {{ '%11s' | format(3753 | int2sexagesimal(maxparts=2)) }}"
     - "3753 | int2sexagesimal(maxparts=2, minparts=2) => {{ '%11s' | format(3753 | int2sexagesimal(maxparts=2, minparts=2)) }}"
     - ""
     - "3753 | int2sexagesimal(minparts=3)             => {{ '%11s' | format(3753 | int2sexagesimal(minparts=3)) }}"
     - "3753 | int2sexagesimal(maxparts=3)             => {{ '%11s' | format(3753 | int2sexagesimal(maxparts=3)) }}"
     - "3753 | int2sexagesimal(maxparts=3, minparts=3) => {{ '%11s' | format(3753 | int2sexagesimal(maxparts=3, minparts=3)) }}"
     - ""
     - "3753 | int2sexagesimal(minparts=4)             => {{ '%11s' | format(3753 | int2sexagesimal(minparts=4)) }}"
     - "3753 | int2sexagesimal(maxparts=4)             => {{ '%11s' | format(3753 | int2sexagesimal(maxparts=4)) }}"
     - "3753 | int2sexagesimal(maxparts=4, minparts=4) => {{ '%11s' | format(3753 | int2sexagesimal(maxparts=4, minparts=4)) }}"
# TASK [Display converted integer 3753 with various parameters] *********************
# ok: [localhost] => 
#   msg:
#   - '# 1 hour, 2 minutes, 33 seconds'
#   - 3753 | int2sexagesimal()                       =>    01:02:33
#   - 3753 | int2sexagesimal(minparts=2)             =>    01:02:33
#   - 3753 | int2sexagesimal(maxparts=2)             =>       62:33
#   - 3753 | int2sexagesimal(maxparts=2, minparts=2) =>       62:33
#   - ''
#   - 3753 | int2sexagesimal(minparts=3)             =>    01:02:33
#   - 3753 | int2sexagesimal(maxparts=3)             =>     1:02:33
#   - 3753 | int2sexagesimal(maxparts=3, minparts=3) =>    01:02:33
#   - ''
#   - 3753 | int2sexagesimal(minparts=4)             => 00:01:02:33
#   - 3753 | int2sexagesimal(maxparts=4)             =>    01:02:33
#   - 3753 | int2sexagesimal(maxparts=4, minparts=4) => 00:01:02:33

- name: Convert large number of seconds with different bases
  ansible.builtin.debug:
    msg:
      - "# 10749 years, 66 days, 18 hour, 24 minutes, 11 seconds"
      - "{{ 338986232651 | int2sexagesimal(maxparts=5, base=bases) }}"
  vars:
    bases:
      - 100  # years / century
      - 365  # days / year
      - 24   # hours / day
      - 60   # minutes / hour
      - 60   # seconds / minute
# TASK [Convert large number of seconds with different bases] ***********************
# ok: [localhost] => 
#   msg:
#   - '# 10749 years, 66 days, 18 hour, 24 minutes, 11 seconds'
#   - 10749:066:18:24:11

- name: Same conversion as above, but create a dict to label the parts
  ansible.builtin.debug:
    msg:
      - "{{ dict(['years', 'days', 'hours', 'minutes', 'seconds']
                 | zip(338986232651
                       | int2sexagesimal(maxparts=5, minparts=5, base=[100, 365, 24, 60, 60])
                       | split(':')
                       | map('regex_replace', '^0+([0-9]+)', '\\1')
                      )
                ) }}"
# TASK [Same conversion as above, but create a dict to label the parts] *************
# ok: [localhost] => 
#   msg:
#   - days: '66'
#     hours: '18'
#     minutes: '24'
#     seconds: '11'
#     years: '10749'

- name: Convert 3041 teaspoons to gallons, quarts, pints, cups, tablespoons, teaspoons
  ansible.builtin.debug:
    msg:
      - - 3041 teaspoons as a colon-delimited string
        - "{{ 3041 | int2sexagesimal(maxparts=6, minparts=6, base=[4, 2, 2, 16, 3]) }}"
      - - 3041 teaspoons as a dict
        - "{{ dict(['gallons', 'quarts', 'pints', 'cups', 'tablespoons', 'teaspoons']
                 | zip(3041
                       | int2sexagesimal(maxparts=6, minparts=6, base=[4, 2, 2, 16, 3])
                       | split(':')
                       | map('regex_replace', '^0+([0-9]+)', '\\1')
                      )
                ) }}"
#TASK [Convert 3041 teaspoons to gallons, quarts, pints, cups, tablespoons, teaspoons] ****
#ok: [localhost] => 
#  msg:
#  - - 3041 teaspoons as a colon-delimited string
#    - '3:3:1:1:05:2'
#  - - 3041 teaspoons as a dict
#    - cups: '1'
#      gallons: '3'
#      pints: '1'
#      quarts: '3'
#      tablespoons: '5'
#      teaspoons: '2'

'''

from ansible.module_utils.common.text.converters import to_text
from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleFilterTypeError

def _flatten(terms):
    ret = []
    for term in terms:
        if isinstance(term, (list, tuple)):
            ret.extend(term)
        else:
            ret.append(term)
    return ret

def int2sexagesimal(value, **kwargs):
    if not isinstance(value, int):
        return value

    minparts = kwargs.get('minparts', None)
    maxparts = kwargs.get('maxparts', None)
    base = kwargs.get('base', 60)

    if minparts and (not isinstance(minparts, int) or minparts < 2):
        raise AnsibleFilterError(f'if specified, minparts must be an int > 1')
    if maxparts and (not isinstance(maxparts, int) or maxparts < 2):
        raise AnsibleFilterError(f'if specified, maxparts must be an int > 1')
    if minparts and maxparts and (minparts > maxparts):
        raise AnsibleFilterError(f'if both are specified, minparts must be less than maxparts')
    if not base:
        base = 60
    bases = _flatten([base])
    for b in bases:
        if not isinstance(b, int) or b < 2:
            raise AnsibleFilterError(f'base must consist of integers > 1')

    if value < 0:
        sign = "-"
        value = abs(value)
    else:
        sign = ""

    acc = []
    while not maxparts or len(acc) < maxparts:
        base = bases[-1]
        if len(bases) > 1:
            bases.pop()
        partfmt = "0" + f"0{len(f"{base-1}")}d"
        if maxparts and len(acc) == maxparts - 1:
            if minparts and minparts == maxparts:
                # zero-pad if minparts == maxparts
                acc.insert(0, f"{value:{partfmt}}")
            else:
                acc.insert(0, f"{value}")
            break
        acc.insert(0, f"{value % base:{partfmt}}")
        value //= base
        if value == 0:
            break

    if minparts:
        while len(acc) < minparts:
            base = bases[-1]
            if len(bases) > 1:
                bases.pop()
            partfmt = "0" + f"0{len(f"{base-1}")}d"
            acc.insert(0, f'{0:{partfmt}}')
    result_string = to_text(f"{sign}{':'.join(acc)}")
    return result_string

class FilterModule(object):
    ''' Ansible core jinja2 filters '''

    def filters(self):
        return {'int2sexagesimal': int2sexagesimal}

if __name__ == "__main__":
    for val in [ 86399, 3600, -500, 36125, 32000000, 8986232651, 338986232651, 216000, 'a string']:
        for ma in [None, 3, 5, 6]:
            for mi in [None, 4, 5, 6]:
                for ba in [None, [100, 365, 24, 60, 60]]:
                    if ma and mi and mi > ma:
                        break
                    print(f"{str(val):8s}: min:{str(mi):5s}, max:{str(ma):5s}, base:{str(ba):24s}) => {int2sexagesimal(val, minparts=mi, maxparts=ma, base=ba):17s}")
    # print(f"     86399: {int2sexagesimal(     86399            )}")
    # print(f"     86399: {int2sexagesimal(     86399, minparts=2)}")
    # print(f"     86399: {int2sexagesimal(     86399, maxparts=3)}")
    # print(f"      3600: {int2sexagesimal(      3600            )}")
    # print(f"      -500: {int2sexagesimal(      -500            )}")
    # print(f"     36125: {int2sexagesimal(     36125, minparts=3)}")
    # print(f"8986232651: {int2sexagesimal(8986232651, minparts=3)}")
    # print(f"    216000: {int2sexagesimal(    216000, minparts=3)}")
    # print(f"'a string': {int2sexagesimal('a string', minparts=8)}")
1 Like

This is classic YAML 1.1 sexagesimal behaviour. Any unquoted value matching the nn:nn:nn:... pattern where all components are decimal and under 60 gets parsed as a base-60 integer before Ansible ever sees it.

The root fix is quoting at the point of entry. In the AWX extra vars prompt, wrap the value in double quotes:

macadres: "11:33:22:55:44:11"

That tells the YAML parser to treat it as a string unconditionally. The reason Case 1 worked is that the job template variable was stored with quotes intact, whereas the prompt input in Case 2 strips outer quotes, leaving the bare value exposed to YAML 1.1 parsing.

For MAC addresses you’re already ingesting as integers, @utoddl’s int2sexagesimal filter from post #10 is the right recovery path. The minparts=6, maxparts=6 combination is what you want for MAC addresses specifically, as it guarantees zero-padded 6-part output.

@RianKellyIT

See the screenshots. AWX does quote the extra_vars, but it looks like variables that are injected with the ‘prompt on launch’ are treated differently.

Since this provides an opportunity to discuss a concrete example of a trip-hazard that’s been around, well, basically since YAML was created, I’m curious what y’all would think if we deprecated and eventually killed off some of those more problematic YAML 1.1 implicit resolvers in Ansible?

I suspect that back when YAML was originally integrated into Ansible, they didn’t know how to easily configure it not to use some of the more problematic default resolvers like sexagesimals, octals, and non true/false booleans, so ever since we’ve been stuck in this (too common) limbo where those are huge trip-hazards to most users, but a handful actually rely on it. It’s relatively easy to do though, and we could stage it in such a way that it could fire explicit warnings/errors when those values are encountered or used. I know I’ve personally gotten nailed countless times by weird behavior from values like 0800 and no.

That particular set of changes would also be forward-compatible with the most common standard tag set in YAML 1.2 if/when we ever get there- the tiny parser-level differences between 1.1 and 1.2 are generally of little consequence to typical Ansible content anyway.