# AnsibleUnsafeText, use, and linting

**URL:** <https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259>\
**Category:** Get Help\
**Tags:** ansible-lint, ansible-core\
**Created:** [July 12, 2024, 9:14pm UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259 "2024-07-12T21:14:37Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [July 12, 2024, 9:14pm UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259/1 "2024-07-12T21:14:37Z")

</div>

We have a particular cron job that we run once a day on each host during business hours. Any particular host uses the same time every day, but across all the hosts those jobs are scheduled fairly evenly throughout the day. We accomplish this by picking the hour and minute for each host with the `random()` function, seeding each call to `random()` with a value specific and unchanging for each host.

```yaml
   hour: "{{ 19 | random(seed=ansible_machine_id, start=7) }}" # hours between 7 and 18
   minute: '{{ 59 | random(seed=ansible_hostname) }}' # minutes between 0 and 59

```

This has worked for years. (And the jobs are much more evenly spread than expected!)

Recently, `ansible-lint` has raised objections to this practice, complaining specifically that both `ansible_machine_id` and `ansible_hostname` are of type `AnsibleUnsafeText`, whereas the he only supported seed types are: `None`, `int`, `float`, `str`, `bytes`, and `bytearray`.

It’s trivially easy to work around. For example, this:

```yaml
- name: Test some AnsibleUnsafeText re-typing methods
  ansible.builtin.debug:
    msg:
      - "(ansible_machine_id) is a {{ ansible_machine_id | type_debug }} : {{ ansible_machine_id }}"
      - "(ansible_machine_id | string) is a {{ ansible_machine_id | string | type_debug }} : {{ ansible_machine_id | string }}"
      - "(ansible_machine_id ~ '') is a {{ (ansible_machine_id ~ '') | type_debug }} : {{ (ansible_machine_id ~ '') }}"
      - "(ansible_machine_id | regex_replace('a', 'a')) is a {{ ansible_machine_id | regex_replace('a', 'a') | type_debug }} : {{ ansible_machine_id | regex_replace('a', 'a') }}"

```

produces

```auto
- '(ansible_machine_id) is a AnsibleUnsafeText : 245ebab998344152848322cf4c07ba6e'
- '(ansible_machine_id | string) is a NativeJinjaText : 245ebab998344152848322cf4c07ba6e'
- '(ansible_machine_id ~ '''') is a str : 245ebab998344152848322cf4c07ba6e'
- '(ansible_machine_id | regex_replace(''a'', ''a'')) is a str : 245ebab998344152848322cf4c07ba6e'

```

and `ansible-lint` seems happy with these techniques.

My question is: Are any of these methods the Right Way™ to deal with `AnsibleUnsafeText`? This feels highly reminiscent of Perl’s “tainted” values and the similar workarounds.

Is the point of `AnsibleUnsafeText` simply to make the user aware of possible issues? If so, it seems like it should take an explicit “`unsafe_ok`” filter to use such values. As it is, you can “`unsafe_var_a ~ unsafe_var_b`” and get a `str`. Lots of normal expressions can accidentally employ `AnsibleUnsafeText` with the user being none the wiser. Which leaves one wondering: What’s really the point?

---

<div class="post-metadata">

**Author:** ![flowerysong](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/flowerysong/32/2305_2.png) [@flowerysong](https://forum.ansible.com/u/flowerysong)\
**Post date:** [July 12, 2024, 10:40pm UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259/2 "2024-07-12T22:40:06Z")

</div>

None of those are necessary. `AnsibleUnsafeText` is a subclass of `str`, and that’s an acceptable type for the seed.

More information on your environment and what exact output you got would be helpful, because I cannot reproduce your result (I tried with a couple of different versions of Python, Ansible, and ansible-lint.)

I _can_ get this error from Python if I call `random.Random` with an unacceptable type, but `AnsibleUnsafeText` is fine.

```python
#!/usr/bin/env python3

from random import Random
from ansible.utils.unsafe_proxy import AnsibleUnsafeText

us = AnsibleUnsafeText('myseed')
r = Random(us)

r = Random(['notaseed'])

```

```plaintext
Traceback (most recent call last):
  File "/home/ezekielh/testing/./test.py", line 9, in <module>
    r = Random(['notaseed'])
        ^^^^^^^^^^^^^^^^^^^^
  File "/home/ezekielh/.pyenv/versions/3.11.2/lib/python3.11/random.py", line 125, in __init__
    self.seed(x)
  File "/home/ezekielh/.pyenv/versions/3.11.2/lib/python3.11/random.py", line 160, in seed
    raise TypeError('The only supported seed types are: None,\n'
TypeError: The only supported seed types are: None,
int, float, str, bytes, and bytearray.

```

`AnsibleUnsafeText` is an internal implementation detail of the feature that prevents Ansible from templating values that should not be templated; these can include strings that the user specifically marked as unsafe, the values returned by code that runs on the target (like facts), values from lookups, and the results of templating things that included unsafe strings. Users mostly do not have to care about this (and the implementation will change Real Soon Now™ anyway.)

```yaml
- hosts: localhost
  vars:
    foo: !unsafe this is an {{ unsafe }} value
  tasks:
    - debug:
        msg:
          - "{{ foo | type_debug }}"
          - "{{ (foo ~ ' thing') | type_debug }}"

    - set_fact:
        bar: "{{ foo ~ ' thing' }}"

    - debug:
        msg:
          - "{{ bar }}"
          - "{{ bar | type_debug }}"
          - "{{ [0, 1, 2, 3] | random(seed=bar) }}"

```

```plaintext
TASK [debug] *******************************************************************
ok: [localhost] =>
    msg:
    - AnsibleUnsafeText
    - str

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] =>
    msg:
    - this is an {{ unsafe }} value thing
    - AnsibleUnsafeText
    - '1'

```

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [July 13, 2024, 2:54am UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259/3 "2024-07-13T02:54:10Z")

</div>

Thanks for putting in the effort, @flowerysong. I always value your insights.

> [@flowerysong](#):
>
> the implementation will change Real Soon Now™ anyway

I swing between “change nothing ever again” and “change everything.” You’ve piqued my curiosity.  
“_I’m intrigued by your ideas and would like to subscript to your newsletter._” — Very early Internet meme, from USENET days.

* * *

To clarify: the code in question has run for years, and still does. It’s the linting where the problem shows up. Here’s a complete but minimal, trivial example. The random seed variable is a host fact, and as such is of type `AnsibleUnsafeText`. Behold:

```yaml
---
# random-seed.yml
- name: Testing random seeds
  hosts: localhost
  gather_facts: true
  tasks:
    - name: Test some random seeds
      ansible.builtin.debug:
        msg:
          - "{{ runs_and_lints }}"
          - "{{ runs_but_does_not_lint }}"
      vars:
        runs_and_lints: "{{ 59 | random(seed=ansible_machine_id ~ '') }}"
        runs_but_does_not_lint: "{{ 59 | random(seed=ansible_machine_id) }}"

```

I get identical results from the two versions of `ansible-lint` most handy for me:

```txt
$ ansible-lint --version
ansible-lint 24.2.0 using ansible-core:2.16.8 ansible-compat:4.1.11 ruamel-yaml:0.18.5 ruamel-yaml-clib:0.2.7
---
$ ansible-lint --version
ansible-lint 24.7.0 using ansible-core:2.16.6 ansible-compat:24.5.1 ruamel-yaml:0.18.6 ruamel-yaml-clib:0.2.8

```

And that `ansible-lint` output is:

```txt
WARNING Skipped installing old role dependencies due to running in offline mode.
WARNING Listing 1 violation(s) that are fatal
jinja[invalid]: An unhandled exception occurred while templating '{{ 59 | random(seed=ansible_machine_id) }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: Unexpected templating type error occurred on ({{ 59 | random(seed=ansible_machine_id) }}): The only supported seed types are: None,
int, float, str, bytes, and bytearray.. The only supported seed types are: None,
int, float, str, bytes, and bytearray.
random-seed.yml:9 Task/Handler: Test some random seeds

```

(Note: that message-within-message is real; that’s not a copy-n-paste error. Nor is it of concern for this discussion.)

---

<div class="post-metadata">

**Author:** ![flowerysong](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/flowerysong/32/2305_2.png) [@flowerysong](https://forum.ansible.com/u/flowerysong)\
**Post date:** [July 13, 2024, 5:24am UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259/4 "2024-07-13T05:24:31Z")

</div>

Yeah, that’s pretty clearly an ansible-lint bug. This isn’t actually related to `AnsibleUnsafeText`, it’s that the variable isn’t defined when ansible-lint attempts to evaluate the template. That means that the filter gets… something (probably an instance of `AnsibleUndefined`) which is not an allowed type.

```plaintext
jinja[invalid]: An unhandled exception occurred while templating '{{ 59 | random(seed=sssssssssssss) }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: Unexpected templating type error occurred on ({{ 59 | random(seed=sssssssssssss) }}): The only supported seed types are: None,
int, float, str, bytes, and bytearray.. The only supported seed types are: None,
int, float, str, bytes, and bytearray.

```

---

<div class="post-metadata">

**Author:** ![flowerysong](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/flowerysong/32/2305_2.png) [@flowerysong](https://forum.ansible.com/u/flowerysong)\
**Post date:** [July 13, 2024, 6:38am UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259/5 "2024-07-13T06:38:53Z")

</div>

> [@utoddl](#):
>
> I swing between “change nothing ever again” and “change everything.” You’ve piqued my curiosity.  
> “_I’m intrigued by your ideas and would like to subscript to your newsletter._” — Very early Internet meme, from USENET days.

To my understanding (with the caveat that I’m just a curious bystander) one of the things that will happen as part of the highly anticipated release of [data tagging](https://github.com/ansible/ansible/issues/80747) is that the model around “unsafe” data will be inverted, so that only data which is tagged as safe will be eligible for templating. Even before that decision, though, `AnsibleUnsafeText` was going to be replaced with the generic object proxies for tagged data. Users mostly shouldn’t have to care about this because it will once again be an internal implementation detail, but it should address some long-standing [issues](https://github.com/ansible/ansible/issues?q=is%3Aissue+is%3Aopen+label%3Adata_tagging) and edge cases.

Personally I think this feature is going to slip again and be pushed back to 2.19, but it is closer to becoming a reality than it’s been at any point in the past.

---

<div class="post-metadata">

**Author:** ![Denney-tech](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/denney-tech/32/1556_2.png) [@Denney-tech](https://forum.ansible.com/u/Denney-tech)\
**Post date:** [July 15, 2024, 2:50am UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259/6 "2024-07-15T02:50:27Z")

</div>

> [@utoddl](#):
>
> Thanks for putting in the effort, @flowerysong. I always value your insights.

I just want to say I’ve been following this thread, and want to second this notion.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/ansible/original/1X/1882d9a01da5d8996a862359b0913cc568a39b84.svg) [@system](https://forum.ansible.com/u/system)\
**Post date:** [August 14, 2024, 2:50am UTC](https://forum.ansible.com/t/ansibleunsafetext-use-and-linting/7259/7 "2024-08-14T02:50:56Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
