Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Role argument validation appears not to enforce booleans? #78889

Closed
1 task done
chriscroome opened this issue Sep 27, 2022 · 3 comments
Closed
1 task done

Role argument validation appears not to enforce booleans? #78889

chriscroome opened this issue Sep 27, 2022 · 3 comments
Labels
affects_2.13 bug This issue/PR relates to a bug. support:core This issue/PR relates to code supported by the Ansible Engineering Team.

Comments

@chriscroome
Copy link

chriscroome commented Sep 27, 2022

Summary

When a variable is defined as a string Ansible role validation appears not to pick up that it is required it to be a boolean?

Issue Type

Bug Report

Component Name

role argument validation

Ansible Version

$ ansible --version
ansible [core 2.13.4]
  config file = /builds/webarch/gitlab-ci/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.7 (main, Sep  8 2022, 14:34:29) [GCC 12.2.0]
  jinja version = 3.0.3
  libyaml = True

Configuration

$ ansible-config dump --only-changed -t all

ANSIBLE_FORCE_COLOR(/builds/webarch/gitlab-ci/ansible.cfg) = True
ANSIBLE_NOCOWS(/builds/webarch/gitlab-ci/ansible.cfg) = True
ANSIBLE_PIPELINING(/builds/webarch/gitlab-ci/ansible.cfg) = True
DEFAULT_ROLES_PATH(/builds/webarch/gitlab-ci/ansible.cfg) = ['/builds/webarch/gitlab-ci/galaxy/roles']
RETRY_FILES_ENABLED(/builds/webarch/gitlab-ci/ansible.cfg) = False
        
CONNECTION:
==========

local:
pipelining(/builds/webarch/gitlab-ci/ansible.cfg) = True
psrp:
pipelining(/builds/webarch/gitlab-ci/ansible.cfg) = True
ssh:
pipelining(/builds/webarch/gitlab-ci/ansible.cfg) = True
winrm:
pipelining(/builds/webarch/gitlab-ci/ansible.cfg) = True

SHELL:
=====
sh:
world_readable_temp(/builds/webarch/gitlab-ci/ansible.cfg) = True

OS / Environment

This has been tested on Debian Bookworm runing in a Docker container triggered via GitLab CI.

Steps to Reproduce

Create a role with the following meta/argument_specs.yml:

---
argument_specs:
  main:
    short_description: The main entry point for the validation role.
    options:
      boolean1:
        type: bool
        required: true
        description: A boolean
      boolean2:
        type: bool
        required: true
        description: Another boolean
...

The following defaults/main.yml:

---
boolean1: false
boolean2: "no"
...

And the following tasks/main.yml:

---
- name: Debug boolean1 variable
  ansible.builtin.debug:
    var: boolean1

- name: Debug boolean1 type
  ansible.builtin.debug:
    msg:
      - "The boolean1 variable is a {{ boolean1 | type_debug }}"

- name: Debug boolean2 variable
  ansible.builtin.debug:
    var: boolean2

- name: Debug boolean2 type
  ansible.builtin.debug:
    msg:
      - "The boolean1 variable is a {{ boolean2 | type_debug }}"
...

Expected Results

I would expect the role to fail validation as "no" is string not a boolean.

Actual Results

ansible-playbook --extra-vars "hostname=localhost" -i "localhost," -c local validation.yml -vv
ansible-playbook [core 2.13.4]
  config file = /builds/webarch/gitlab-ci/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible-playbook
  python version = 3.10.7 (main, Sep  8 2022, 14:34:29) [GCC 12.2.0]
  jinja version = 3.0.3
  libyaml = True
Using /builds/webarch/gitlab-ci/ansible.cfg as config file
Skipping callback 'default', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.
PLAYBOOK: validation.yml *******************************************************
1 plays in validation.yml
PLAY [Role argument validation] ************************************************
TASK [Gathering Facts] *********************************************************
task path: /builds/webarch/gitlab-ci/validation.yml:2
ok: [localhost]
META: ran handlers
TASK [validation : Validating arguments against arg spec 'main' - The main entry point for the validation role.] ***
task path: /builds/webarch/gitlab-ci/validation.yml:2
ok: [localhost] => {"changed": false, "msg": "The arg spec validation passed", "validate_args_context": {"argument_spec_name": "main", "name": "validation", "path": "/builds/webarch/gitlab-ci/roles/validation", "type": "role"}}
TASK [validation : Debug boolean1 variable] ************************************
task path: /builds/webarch/gitlab-ci/roles/validation/tasks/main.yml:2
ok: [localhost] => {
    "boolean1": false
}
TASK [validation : Debug boolean1 type] ****************************************
task path: /builds/webarch/gitlab-ci/roles/validation/tasks/main.yml:6
ok: [localhost] => {
    "msg": [
        "The boolean1 variable is a bool"
    ]
}
TASK [validation : Debug boolean2 variable] ************************************
task path: /builds/webarch/gitlab-ci/roles/validation/tasks/main.yml:11
ok: [localhost] => {
    "boolean2": "no"
}
TASK [validation : Debug boolean2 type] ****************************************
task path: /builds/webarch/gitlab-ci/roles/validation/tasks/main.yml:15
ok: [localhost] => {
    "msg": [
        "The boolean1 variable is a AnsibleUnicode"
    ]
}

Code of Conduct

  • I agree to follow the Ansible Code of Conduct
@ansibot
Copy link
Contributor

ansibot commented Sep 27, 2022

Files identified in the description:
None

If these files are incorrect, please update the component name section of the description or use the !component bot command.

click here for bot help

@ansibot ansibot added affects_2.13 bug This issue/PR relates to a bug. needs_triage Needs a first human triage before being processed. support:core This issue/PR relates to code supported by the Ansible Engineering Team. labels Sep 27, 2022
@s-hertel s-hertel removed the needs_triage Needs a first human triage before being processed. label Sep 27, 2022
@sivel
Copy link
Member

sivel commented Sep 27, 2022

There are a few key pieces of information about role validation:

  1. The validator is a coercing validator, which means that if the value can be coerced to the specified type, through a set of rules we have defined, it will succeed. In this case, our bool type, handles common string representations of booleans (normalized in lowercase):

    BOOLEANS_TRUE = frozenset(('y', 'yes', 'on', '1', 'true', 't', 1, 1.0, True))
    BOOLEANS_FALSE = frozenset(('n', 'no', 'off', '0', 'false', 'f', 0, 0.0, False))
    
  2. Role validation does not inject the validated and coerced variables back into the variable list

Due to the above, role validation ensures that the types can be coerced to their respective types, but does not make use of those coerced types. As such, the role then becomes responsible for handling the coercion of the variables at run time.

We have another issue open, where there is an ask for strict type checking, that does not perform coercion:

#77159

As for (2) we have no mechanism for achieving this, because of complexities with variable precedence. At the moment, we could return the validated and coerced variables in an ansible_facts key from the validate_argument_spec action plugin, but this would break the assumptions of variable precedence. At this time we do not have plans on implementing a feature that would allow re-injecting the validated and coerced variables.

So in short, as of now, "no" is a valid boolean based on our coercion rules, but we do not inject the coerced value back into the role or play, so it remains the string "no".

You would be responsible for using {{ boolean2 | bool }} or {{ boolean2 is truthy(convert_bool=True) }} as needed, or manually checking that they are strict booleans using the boolean jinja2 test.

If you have further questions please stop by IRC or the mailing list:

@sivel sivel closed this as completed Sep 27, 2022
@chriscroome
Copy link
Author

Thanks for taking the time to explain this @sivel :-)

@ansible ansible locked and limited conversation to collaborators Oct 4, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affects_2.13 bug This issue/PR relates to a bug. support:core This issue/PR relates to code supported by the Ansible Engineering Team.
Projects
None yet
Development

No branches or pull requests

4 participants