ansible / ansible-lint

ansible-lint checks playbooks for practices and behavior that could potentially be improved and can fix some of the most common ones for you
https://ansible.readthedocs.io/projects/lint/
GNU General Public License v3.0
3.49k stars 660 forks source link

False positive in `jinja[invalid]` rule when using `changed` filter for registered var in `vars` #4173

Open ajalab opened 5 months ago

ajalab commented 5 months ago
Summary

False jinja[invalid] rule violation with AnsibleFilterError occurs, when we use changed filter for a registered variable in other variables defined in vars of a subsequent task.

Issue Type
OS / ENVIRONMENT
ansible-lint --version
ansible-lint 24.5.0 using ansible-core:2.16.6 ansible-compat:24.5.1 ruamel-yaml:0.18.6 ruamel-yaml-clib:0.2.8
STEPS TO REPRODUCE

Prepare an Ansible playbook file playbook.yml with the below content.

---
- name: Playbook
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Echo
      ansible.builtin.command: echo foo
      register: echo_result
      changed_when: true

    - name: Print echo result
      ansible.builtin.debug:
        msg: "Result: {% if echo_result is changed %}changed{% else %}ok{% endif %}"

    - name: Print echo result with vars (violates jinja[invalid])
      ansible.builtin.debug:
        msg: "Result: {{ result }}"
      vars:
        result: "{% if echo_result is changed %}changed{% else %}ok{% endif %}"

Then run ansible-lint.

$ ls
playbook.yml

$ ansible-lint
Desired Behavior

There should be no ansible-lint violations detected, since this playbook.yml should be a valid playbook file.

$ ansible-playbook -i localhost, --syntax-check playbook.yml

playbook: playbook.yml
Actual Behavior

It warns a jinja[invalid] violation only for Print echo result with vars (violates jinja[invalid]) task.

$ ansible-lint
WARNING  Listing 1 violation(s) that are fatal
jinja[invalid]: An unhandled exception occurred while templating '{% if echo_result is changed %}changed{% else %}ok{% endif %}'. Error was a <class 'ansible.errors.AnsibleFilterError'>, original message: The 'changed' test expects a dictionary
playbook.yml:17 Task/Handler: Print echo result with vars (violates jinja[invalid])

Read documentation for instructions on how to ignore specific rule violations.

              Rule Violation Summary
 count tag            profile rule associated tags
     1 jinja[invalid] basic   formatting

Failed: 1 failure(s), 0 warning(s) on 2 files. Last profile that met the validation criteria was 'min'.

It's strange that the violation is reported for only the latter task. The two tasks

are equivalent except for the use of changed filter in vars section.

Appendix
Playbook execution result ```console $ ansible-playbook playbook.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [Playbook] **************************************************************************************************************************************************************************************************************************** TASK [Echo] ******************************************************************************************************************************************************************************************************************************** changed: [localhost] TASK [Print echo result] ******************************************************************************************************************************************************************************************************************* ok: [localhost] => { "msg": "Result: changed" } TASK [Print echo result with vars (violates jinja[invalid])] ****************************************************************************************************************************************************************************** ok: [localhost] => { "msg": "Result: changed" } PLAY RECAP ********************************************************************************************************************************************************************************************************************************* localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ```
Result of ansible-lint with verbose flags ```console $ ansible-lint -vvvv DEBUG Logging initialized to level 10 INFO Identified / as project root due file system root. DEBUG Options: Options(_skip_ansible_syntax_check=False, cache_dir=PosixPath('/Users/jp25308/.cache/ansible-compat/e3b0c4'), colored=True, configured=True, cwd=PosixPath('/Users/jp25308/tmp/ansible/lint-copy-result'), display_relative_path=True, exclude_paths=['.cache', '.git', '.hg', '.svn', '.tox'], format=None, lintables=[], list_rules=False, list_tags=False, write_list=[], parseable=False, quiet=0, rulesdirs=[PosixPath('/Users/jp25308/tmp/ansible/.env/lib/python3.12/site-packages/ansiblelint/rules')], skip_list=[], tags=[], verbosity=4, warn_list=['experimental', 'jinja', 'fqcn'], mock_filters=[], mock_modules=[], mock_roles=[], loop_var_prefix=None, only_builtins_allow_collections=[], only_builtins_allow_modules=[], var_naming_pattern=None, offline=None, project_dir='/', extra_vars=None, enable_list=[], skip_action_validation=True, strict=False, rules={}, profile=None, task_name_prefix='{stem} | ', sarif_file=None, config_file=None, generate_ignore=False, rulesdir=[], use_default_rules=False, version=False, list_profiles=False, ignore_file=None, max_tasks=100, max_block_depth=20) DEBUG CWD: /Users/jp25308/tmp/ansible/lint-copy-result DEBUG Logging initialized to level 10 DEBUG Effective yamllint rules used: {'anchors': {'level': 'error', 'forbid-undeclared-aliases': True, 'forbid-duplicated-anchors': False, 'forbid-unused-anchors': False}, 'braces': {'level': 'error', 'forbid': False, 'min-spaces-inside': 0, 'max-spaces-inside': 1, 'min-spaces-inside-empty': -1, 'max-spaces-inside-empty': -1}, 'brackets': {'level': 'error', 'forbid': False, 'min-spaces-inside': 0, 'max-spaces-inside': 0, 'min-spaces-inside-empty': -1, 'max-spaces-inside-empty': -1}, 'colons': {'level': 'error', 'max-spaces-before': 0, 'max-spaces-after': 1}, 'commas': {'level': 'error', 'max-spaces-before': 0, 'min-spaces-after': 1, 'max-spaces-after': 1}, 'comments': {'level': 'warning', 'require-starting-space': True, 'ignore-shebangs': True, 'min-spaces-from-content': 1}, 'comments-indentation': False, 'document-end': False, 'document-start': False, 'empty-lines': {'level': 'error', 'max': 2, 'max-start': 0, 'max-end': 0}, 'empty-values': False, 'float-values': False, 'hyphens': {'level': 'error', 'max-spaces-after': 1}, 'indentation': {'level': 'error', 'spaces': 'consistent', 'indent-sequences': True, 'check-multi-line-strings': False}, 'key-duplicates': {'level': 'error', 'forbid-duplicated-merge-keys': False}, 'key-ordering': False, 'line-length': {'level': 'error', 'max': 160, 'allow-non-breakable-words': True, 'allow-non-breakable-inline-mappings': False}, 'new-line-at-end-of-file': {'level': 'error'}, 'new-lines': {'level': 'error', 'type': 'unix'}, 'octal-values': {'forbid-implicit-octal': True, 'forbid-explicit-octal': True, 'level': 'error'}, 'quoted-strings': False, 'trailing-spaces': {'level': 'error'}, 'truthy': {'level': 'warning', 'allowed-values': ['true', 'false'], 'check-keys': True}} INFO Set ANSIBLE_LIBRARY=/Users/jp25308/.cache/ansible-compat/bc51c2/modules:/Users/jp25308/.ansible/plugins/modules:/usr/share/ansible/plugins/modules INFO Set ANSIBLE_COLLECTIONS_PATH=/Users/jp25308/.cache/ansible-compat/bc51c2/collections:/Users/jp25308/.ansible/collections:/usr/share/ansible/collections:/Users/jp25308/tmp/ansible/.env/lib/python3.12/site-packages INFO Set ANSIBLE_ROLES_PATH=/Users/jp25308/.cache/ansible-compat/bc51c2/roles:/Users/jp25308/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles DEBUG Effective yamllint rules used: {'anchors': {'level': 'error', 'forbid-undeclared-aliases': True, 'forbid-duplicated-anchors': False, 'forbid-unused-anchors': False}, 'braces': {'level': 'error', 'forbid': False, 'min-spaces-inside': 0, 'max-spaces-inside': 1, 'min-spaces-inside-empty': -1, 'max-spaces-inside-empty': -1}, 'brackets': {'level': 'error', 'forbid': False, 'min-spaces-inside': 0, 'max-spaces-inside': 0, 'min-spaces-inside-empty': -1, 'max-spaces-inside-empty': -1}, 'colons': {'level': 'error', 'max-spaces-before': 0, 'max-spaces-after': 1}, 'commas': {'level': 'error', 'max-spaces-before': 0, 'min-spaces-after': 1, 'max-spaces-after': 1}, 'comments': {'level': 'warning', 'require-starting-space': True, 'ignore-shebangs': True, 'min-spaces-from-content': 1}, 'comments-indentation': False, 'document-end': False, 'document-start': False, 'empty-lines': {'level': 'error', 'max': 2, 'max-start': 0, 'max-end': 0}, 'empty-values': False, 'float-values': False, 'hyphens': {'level': 'error', 'max-spaces-after': 1}, 'indentation': {'level': 'error', 'spaces': 'consistent', 'indent-sequences': True, 'check-multi-line-strings': False}, 'key-duplicates': {'level': 'error', 'forbid-duplicated-merge-keys': False}, 'key-ordering': False, 'line-length': {'level': 'error', 'max': 160, 'allow-non-breakable-words': True, 'allow-non-breakable-inline-mappings': False}, 'new-line-at-end-of-file': {'level': 'error'}, 'new-lines': {'level': 'error', 'type': 'unix'}, 'octal-values': {'forbid-implicit-octal': True, 'forbid-explicit-octal': True, 'level': 'error'}, 'quoted-strings': False, 'trailing-spaces': {'level': 'error'}, 'truthy': {'level': 'warning', 'allowed-values': ['true', 'false'], 'check-keys': True}} DEBUG Logging initialized to level 10 DEBUG Effective yamllint rules used: {'anchors': {'level': 'error', 'forbid-undeclared-aliases': True, 'forbid-duplicated-anchors': False, 'forbid-unused-anchors': False}, 'braces': {'level': 'error', 'forbid': False, 'min-spaces-inside': 0, 'max-spaces-inside': 1, 'min-spaces-inside-empty': -1, 'max-spaces-inside-empty': -1}, 'brackets': {'level': 'error', 'forbid': False, 'min-spaces-inside': 0, 'max-spaces-inside': 0, 'min-spaces-inside-empty': -1, 'max-spaces-inside-empty': -1}, 'colons': {'level': 'error', 'max-spaces-before': 0, 'max-spaces-after': 1}, 'commas': {'level': 'error', 'max-spaces-before': 0, 'min-spaces-after': 1, 'max-spaces-after': 1}, 'comments': {'level': 'warning', 'require-starting-space': True, 'ignore-shebangs': True, 'min-spaces-from-content': 1}, 'comments-indentation': False, 'document-end': False, 'document-start': False, 'empty-lines': {'level': 'error', 'max': 2, 'max-start': 0, 'max-end': 0}, 'empty-values': False, 'float-values': False, 'hyphens': {'level': 'error', 'max-spaces-after': 1}, 'indentation': {'level': 'error', 'spaces': 'consistent', 'indent-sequences': True, 'check-multi-line-strings': False}, 'key-duplicates': {'level': 'error', 'forbid-duplicated-merge-keys': False}, 'key-ordering': False, 'line-length': {'level': 'error', 'max': 160, 'allow-non-breakable-words': True, 'allow-non-breakable-inline-mappings': False}, 'new-line-at-end-of-file': {'level': 'error'}, 'new-lines': {'level': 'error', 'type': 'unix'}, 'octal-values': {'forbid-implicit-octal': True, 'forbid-explicit-octal': True, 'level': 'error'}, 'quoted-strings': False, 'trailing-spaces': {'level': 'error'}, 'truthy': {'level': 'warning', 'allowed-values': ['true', 'false'], 'check-keys': True}} INFO Set ANSIBLE_LIBRARY=/Users/jp25308/.cache/ansible-compat/bc51c2/modules:/Users/jp25308/.ansible/plugins/modules:/usr/share/ansible/plugins/modules INFO Set ANSIBLE_COLLECTIONS_PATH=/Users/jp25308/.cache/ansible-compat/bc51c2/collections:/Users/jp25308/.ansible/collections:/usr/share/ansible/collections:/Users/jp25308/tmp/ansible/.env/lib/python3.12/site-packages INFO Set ANSIBLE_ROLES_PATH=/Users/jp25308/.cache/ansible-compat/bc51c2/roles:/Users/jp25308/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles DEBUG data set to None for .ansible-lint-bak due to being '' (unknown) kind. INFO Executing syntax check on playbook playbook.yml (0.35s) DEBUG Examining playbook.yml of type playbook DEBUG Running rule internal-error DEBUG Running rule load-failure DEBUG Running rule parser-error DEBUG Running rule warning DEBUG Running rule yaml DEBUG Running rule args DEBUG Running rule avoid-implicit DEBUG Running rule command-instead-of-module DEBUG Running rule command-instead-of-shell DEBUG Running rule complexity DEBUG Running rule deprecated-bare-vars DEBUG Running rule deprecated-local-action DEBUG Running rule deprecated-module DEBUG Running rule fqcn DEBUG Running rule galaxy DEBUG Running rule ignore-errors DEBUG Running rule inline-env-var DEBUG Running rule jinja DEBUG NAME 'echo_result' (prefix='') DEBUG NAME 'is' (prefix=' ') DEBUG NAME 'changed' (prefix=' ') DEBUG NEWLINE '\n' (prefix=' ') DEBUG ENDMARKER '' (prefix='') DEBUG Stop. DEBUG NAME 'echo_result' (prefix='') DEBUG NAME 'is' (prefix=' ') DEBUG NAME 'changed' (prefix=' ') DEBUG NEWLINE '\n' (prefix='') DEBUG ENDMARKER '' (prefix='') DEBUG Stop. DEBUG ENDMARKER '' (prefix='\n') DEBUG Stop. DEBUG ENDMARKER '' (prefix='\n') DEBUG Stop. DEBUG NAME 'echo_result' (prefix='') DEBUG NAME 'is' (prefix=' ') DEBUG NAME 'changed' (prefix=' ') DEBUG NEWLINE '\n' (prefix=' ') DEBUG ENDMARKER '' (prefix='') DEBUG Stop. DEBUG NAME 'echo_result' (prefix='') DEBUG NAME 'is' (prefix=' ') DEBUG NAME 'changed' (prefix=' ') DEBUG NEWLINE '\n' (prefix='') DEBUG ENDMARKER '' (prefix='') DEBUG Stop. DEBUG ENDMARKER '' (prefix='\n') DEBUG Stop. DEBUG ENDMARKER '' (prefix='\n') DEBUG Stop. DEBUG Running rule key-order DEBUG Running rule latest DEBUG Running rule literal-compare DEBUG Running rule loop-var-prefix DEBUG Running rule meta-incorrect DEBUG Running rule meta-no-tags DEBUG Running rule meta-runtime DEBUG Running rule meta-video-links DEBUG Running rule name DEBUG Running rule no-changed-when DEBUG Running rule no-free-form DEBUG Running rule no-handler DEBUG Running rule no-jinja-when DEBUG Running rule no-relative-paths DEBUG Running rule no-tabs DEBUG Running rule package-latest DEBUG Running rule partial-become DEBUG Running rule playbook-extension DEBUG Running rule risky-file-permissions DEBUG Running rule risky-octal DEBUG Running rule risky-shell-pipe DEBUG Running rule role-name DEBUG Running rule run-once DEBUG Running rule sanity DEBUG Running rule schema DEBUG Running rule var-naming WARNING Listing 1 violation(s) that are fatal jinja[invalid]: An unhandled exception occurred while templating '{% if echo_result is changed %}changed{% else %}ok{% endif %}'. Error was a , original message: The 'changed' test expects a dictionary playbook.yml:17 Task/Handler: Print echo result with vars (violates jinja[invalid]) DEBUG Attempting to release lock 4366696960 on /Users/jp25308/.cache/ansible-compat/e3b0c4/.lock DEBUG Lock 4366696960 released on /Users/jp25308/.cache/ansible-compat/e3b0c4/.lock Read documentation for instructions on how to ignore specific rule violations. DEBUG Determined rule-profile order: {'internal-error': (0, 'min'), 'load-failure': (1, 'min'), 'parser-error': (2, 'min'), 'syntax-check': (3, 'min'), 'command-instead-of-module': (4, 'basic'), 'command-instead-of-shell': (5, 'basic'), 'deprecated-bare-vars': (6, 'basic'), 'deprecated-local-action': (7, 'basic'), 'deprecated-module': (8, 'basic'), 'inline-env-var': (9, 'basic'), 'key-order': (10, 'basic'), 'literal-compare': (11, 'basic'), 'jinja': (12, 'basic'), 'no-free-form': (13, 'basic'), 'no-jinja-when': (14, 'basic'), 'no-tabs': (15, 'basic'), 'partial-become': (16, 'basic'), 'playbook-extension': (17, 'basic'), 'role-name': (18, 'basic'), 'schema': (19, 'basic'), 'name': (20, 'basic'), 'var-naming': (21, 'basic'), 'yaml': (22, 'basic'), 'name': (23, 'moderate'), 'name': (24, 'moderate'), 'name': (25, 'moderate'), 'spell-var-name': (26, 'moderate'), 'avoid-implicit': (27, 'safety'), 'latest': (28, 'safety'), 'package-latest': (29, 'safety'), 'risky-file-permissions': (30, 'safety'), 'risky-octal': (31, 'safety'), 'risky-shell-pipe': (32, 'safety'), 'galaxy': (33, 'shared'), 'ignore-errors': (34, 'shared'), 'layout': (35, 'shared'), 'meta-incorrect': (36, 'shared'), 'meta-no-tags': (37, 'shared'), 'meta-video-links': (38, 'shared'), 'meta-version': (39, 'shared'), 'meta-runtime': (40, 'shared'), 'no-changed-when': (41, 'shared'), 'no-changelog': (42, 'shared'), 'no-handler': (43, 'shared'), 'no-relative-paths': (44, 'shared'), 'max-block-depth': (45, 'shared'), 'max-tasks': (46, 'shared'), 'unsafe-loop': (47, 'shared'), 'avoid-dot-notation': (48, 'production'), 'sanity': (49, 'production'), 'fqcn': (50, 'production'), 'import-task-no-when': (51, 'production'), 'meta-no-dependencies': (52, 'production'), 'single-entry-point': (53, 'production'), 'use-loop': (54, 'production')} Rule Violation Summary count tag profile rule associated tags 1 jinja[invalid] basic formatting Failed: 1 failure(s), 0 warning(s) on 2 files. Last profile that met the validation criteria was 'min'. ```
sibebleuze commented 1 month ago

Hi, I'm experiencing the same issue. I can add that I see it with registered_var is skipped and registered_var is failed as well. It appears the issue only occurs when the following conditions are true:

Only for task 2 do I receive the linting error like the original report: An unhandled exception occurred while templating '{{ task_result is skipped }}'. Error was a <class 'ansible.errors.AnsibleFilterError'>, original message: The 'skipped' test expects a dictionary

- name: Task 1
  ansible.builtin.debug:
    msg: "Placeholder message"
  register: task_result

- name: Task 2
  ansible.builtin.debug:
    msg: "Task 1 was skipped: {{ task_skipped }}"
  vars:
    task_skipped: "{{ task_result is skipped }}"

- name: Task 3
  ansible.builtin.debug:
    msg: "Task 1 was skipped: {{ task_result is skipped }}"

- name: Task 4
  ansible.builtin.debug:
    msg: "Task 1 was skipped: false"
  vars:
    task_skipped: "{{ task_result is skipped }}"