Loading environment variables from YAML file (using include_vars) in Ansible - ansible

I am trying to load variables from a YAML file to be used as environment variables in a later task of the same playbook. This is what I am doing:
Template host vars in YAML format:
- name: builtin | template | template host variables file
ansible.builtin.template:
src: templates/django/hostvars.j2
dest: "host_vars/{{ inventory_hostname_short }}.yml"
mode: 0640
output_encoding: "utf-8"
delegate_to: localhost
This is producing the following file:
--
# Host variables to be set as environment variables in tasks that need it
POSTGRES_PASSWORD: "<password>"
POSTGRES_USER: "dbuser"
POSTGRES_DB: "dbname"
POSTGRES_HOST: "dbhost"
POSTGRES_PORT: 5432
POSTGRES_SSLMODE: "verify-full"
POSTGRES_SSLCA: "/etc/ssl/certs/ISRG_Root_X1.pem"
POSTGRES_APPNAME: "myproject"
DJANGO_SITE_NAME: "mysite"
DJANGO_SITE_PASSWORD: "mypassword"
DJANGO_SITE_USER: "myuser"
DJANGO_SITE_ID: 2
DJANGO_SECRET_KEY: "<very-long-and-random-secret>"
[..]
Use include_vars to load the vars into the playbook:
- name: builtin | include_vars | load host vars
ansible.builtin.include_vars:
file: "host_vars/{{ inventory_hostname_short }}.yml"
name: envvars
Later on the playbook, check that the variables are there:
- name: builtin | debug | print variable 'envvars'
ansible.builtin.debug:
var: envvars
This is working as intended (apparently) and a list of KEY: value variables is being printed, such as:
TASK [builtin | debug | print variable 'envvars'] ***************************************************************
ok: [django1.mydomain.com] => {
"envvars": {
"DJANGO_DEBUG": 0,
"DJANGO_LOGS_DIR": "/opt/django/logs",
"DJANGO_MEDIA_BASE": "/opt/django/media",
"DJANGO_SECRET_KEY": "<very-long-and-random-secret>",
[..]
Use django_manage to update the database schema:
- name: community.general | django_manage | update database schema
community.general.django_manage:
command: migrate
settings: myproject.settings
project_path: "/opt/django/src"
virtualenv: "/opt/django/venv"
become: true
become_user: django
become_method: su
environment: "{{ envvars }}"
This, unfortunately, is failing. Django is complaining that it cannot find the SECRET_KEY environment variable, which it should build based on one of the variables in the abovementioned list (especifically, DJANGO_SECRET_KEY).
Incidentally, if I run the following task, nothing is printed out:
- name: print environment variables
ansible.builtin.command: env
become: true
become_user: django
become_method: su
environment: "{{ envvars }}"
And I don't understand why. I've tried to debug using -vvv and they are being sent by Ansible though the SSH connection (at least so it seems).
Any hints on what bit I am doing wrong?
EDIT 1
I've changed the tasks list file where I use django_manage into the following code:
- name: builtin | shell | capture DJANGO_ environment variables
debugger: on_failed
ansible.builtin.shell:
cmd: "env | grep DJANGO_"
register: out
environment: "{{ envvars }}"
become: true
become_user: django
become_method: su
- name: builtin | debug | pinrt content of out.stdout
ansible.builtin.debug:
var: out.stdout
- name: builtin | debug | print variable 'envvars'
ansible.builtin.debug:
var: envvars
- name: community.general | django_manage | populate the static subdirectory
community.general.django_manage:
command: collectstatic
clear: yes
project_path: "/opt/django/src"
virtualenv: "/opt/django/venv"
become: true
become_user: django
become_method: su
environment: "{{ envvars }}"
The second and third tasks both print the values of the variables (the first one from the env | grep DJANGO_ command sent via shell and the second is the value of the envvars variable which is being sent via the environment: directive.
This is the error of the last task:
TASK [builtin | shell | capture DJANGO_ environment variables] *************************************************************************************************************
changed: [django1.donmain.com]
TASK [builtin | debug | pinrt content of out.stdout] ***********************************************************************************************************************
ok: [django1.django.com] => {
"out.stdout": "DJANGO_SITE_USER=mysite\nDJANGO_MEDIA_BASE=/opt/django/media\nDJANGO_SITE_NAME=mysite\nDJANGO_SITE_ID=2\nDJANGO_SECRET_KEY=<very-secret-key>\nDJANGO_LOGS_DIR=/opt/django/logs\nDJANGO_SETTINGS_MODULE=myproject.settings.production\nDJANGO_DEBUG=0\nDJANGO_STATIC_BASE=/opt/django/static\nDJANGO_SITE_PASSWORD=mypassword\nDJANGO_SITE_VERSION=57a2f3c168d86243f03809e5d02a0f50a8fa892e"
}
TASK [builtin | debug | print variable 'envvars'] **************************************************************************************************************************
ok: [django1.domain.com] => {
"envvars": {
"DJANGO_DEBUG": 0,
"DJANGO_LOGS_DIR": "/opt/django/logs",
"DJANGO_MEDIA_BASE": "/opt/django/media",
"DJANGO_SECRET_KEY": "<very-secret-key>",
"DJANGO_SETTINGS_MODULE": "myproject.settings.production",
"DJANGO_SITE_ID": 2,
"DJANGO_SITE_NAME": "mysite",
"DJANGO_SITE_PASSWORD": "mypassword",
"DJANGO_SITE_USER": "myuser",
[..]
}
}
TASK [community.general | django_manage | populate the static subdirectory] ************************************************************************************************
fatal: [django1.domain.com]: FAILED! => {"changed": false, "cmd": ["./manage.py", "collectstatic", "--noinput", "--clear"], "msg": "\n:stderr: Traceback (most recent call last):\n File \"/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py\", line 204, in fetch_command\n app_name = commands[subcommand]\nKeyError: 'collectstatic'\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/opt/django/src/./manage.py\", line 22, in <module>\n execute_from_command_line(sys.argv)\n File \"/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py\", line 381, in execute_from_command_line\n utility.execute()\n File \"/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py\", line 375, in execute\n self.fetch_command(subcommand).run_from_argv(self.argv)\n File \"/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py\", line 211, in fetch_command\n settings.INSTALLED_APPS\n File \"/opt/django/venv/lib/python3.9/site-packages/django/conf/__init__.py\", line 57, in __getattr__\n self._setup(name)\n File \"/opt/django/venv/lib/python3.9/site-packages/django/conf/__init__.py\", line 44, in _setup\n self._wrapped = Settings(settings_module)\n File \"/opt/django/venv/lib/python3.9/site-packages/django/conf/__init__.py\", line 107, in __init__\n mod = importlib.import_module(self.SETTINGS_MODULE)\n File \"/usr/lib/python3.9/importlib/__init__.py\", line 127, in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n File \"<frozen importlib._bootstrap>\", line 1030, in _gcd_import\n File \"<frozen importlib._bootstrap>\", line 1007, in _find_and_load\n File \"<frozen importlib._bootstrap>\", line 986, in _find_and_load_unlocked\n File \"<frozen importlib._bootstrap>\", line 680, in _load_unlocked\n File \"<frozen importlib._bootstrap_external>\", line 790, in exec_module\n File \"<frozen importlib._bootstrap>\", line 228, in _call_with_frames_removed\n File \"/opt/django/src/black_pearl/settings/production.py\", line 3, in <module>\n from black_pearl.settings.common import *\n File \"/opt/django/src/black_pearl/settings/common.py\", line 301, in <module>\n path_app = import_module(app).__path__\n File \"/usr/lib/python3.9/importlib/__init__.py\", line 127, in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\nModuleNotFoundError: No module named 'None'\n", "path": "/opt/django/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "syspath": ["/tmp/ansible_community.general.django_manage_payload_l53eeo4g/ansible_community.general.django_manage_payload.zip", "/usr/lib/python39.zip", "/usr/lib/python3.9", "/usr/lib/python3.9/lib-dynload", "/usr/local/lib/python3.9/dist-packages", "/usr/lib/python3/dist-packages"]}
PLAY RECAP *****************************************************************************************************************************************************************
django1.domain.com : ok=14 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Thanks in advance.

Q: "Nothing is printed out."
A: Register the output if you want to see it. For example
- hosts: localhost
tasks:
- command: echo $DJANGO_DEBUG
register: out
environment:
DJANGO_DEBUG: 0
- debug:
var: out.stdout
gives (abridged)
out.stdout: '0'
This should work for you. Step by step add the complexity to your code and isolate the problem. For example, the playbook below should display the environment at the remote host
- hosts: test_11
vars:
env:
DJANGO_DEBUG: 0
DJANGO_SITE_NAME: mysite
DJANGO_SITE_PASSWORD: mypassword
DJANGO_SITE_USER: myuser
DJANGO_SITE_ID: 2
tasks:
- shell: env | grep DJANGO_
register: out
environment: "{{ env }}"
- debug:
var: out.stdout

Related

Lookup secrets from AWS secret manager | Ansible

Using Terraform code I have created Other type of secrets in AWS Secrets Manager.
I need to use these AWS secrets in Ansible code. I found this below link but I am unable to proceed it.
https://docs.ansible.com/ansible/2.8/plugins/lookup/aws_secret.html
I have below Ansible code:-
database.yml
- name: Airflow | DB | Create MySQL DB
mysql_db:
login_user: "{{ mysql_user }}"
# login_password: "{{ mysql_root_password }}"
login_password: "{{ lookup('ca_dev', 'mysql_root_password') }}"
# config_file: /etc/my.cnf
# login_unix_socket: /var/lib/mysql/mysql.sock
# encrypted: yes
name: "airflow"
state: "present"
How can I incorporate AWS secret Manager in my ansible code?
Error message:-
TASK [../../roles/airflow : Airflow | DB | Create MySQL DB] **************************************************************************************************************************************************************************
task path: /home/ec2-user/cng-ansible/roles/airflow/tasks/database.yml:25
The full traceback is:
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 140, in run
res = self._execute()
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 539, in _execute
self._task.post_validate(templar=templar)
File "/usr/lib/python2.7/site-packages/ansible/playbook/task.py", line 267, in post_validate
super(Task, self).post_validate(templar)
File "/usr/lib/python2.7/site-packages/ansible/playbook/base.py", line 364, in post_validate
value = templar.template(getattr(self, name))
File "/usr/lib/python2.7/site-packages/ansible/template/__init__.py", line 540, in template
disable_lookups=disable_lookups,
File "/usr/lib/python2.7/site-packages/ansible/template/__init__.py", line 495, in template
disable_lookups=disable_lookups,
File "/usr/lib/python2.7/site-packages/ansible/template/__init__.py", line 746, in do_template
res = j2_concat(rf)
File "<template>", line 8, in root
File "/usr/lib/python2.7/site-packages/jinja2/runtime.py", line 193, in call
return __obj(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/ansible/template/__init__.py", line 631, in _lookup
instance = self._lookup_loader.get(name.lower(), loader=self._loader, templar=self)
File "/usr/lib/python2.7/site-packages/ansible/plugins/loader.py", line 381, in get
obj = getattr(self._module_cache[path], self.class_name)
AttributeError: 'module' object has no attribute 'LookupModule'
fatal: [127.0.0.1]: FAILED! => {
"msg": "Unexpected failure during module execution.",
"stdout": ""
}
RUNNING HANDLER [../../roles/airflow : restart rabbitmq-server]
task path: /home/ec2-user/cng-ansible/roles/airflow/handlers/main.yml:28
to retry, use: --limit #/home/ec2-user/cng-ansible/plays/airflow/installAirflow.retry
PLAY RECAP
127.0.0.1 : ok=39 changed=7 unreachable=0 failed=1
ansible-doc -t lookup -l output
The error {"msg": "lookup plugin (ca_dev) not found"} suggests your issue is the misuse of the lookup command.
The following line:
login_password: "{{ lookup('ca_dev', 'mysql_root_password') }}"
Should look something like
login_password: "{{ lookup('aws_secret', 'mysql_root_password') }}"
ca_dev is not a valid lookup type, whereas aws_secret is.
You can see a list of supported lookup plugins for Ansible 2.8 in the Lookup Plugins section of the official documentation.
If you are using a custom lookup plugin, or backporting a plugin from a future version of ansible to an older version, you must make sure that it is in a directory visible to ansible.
You can either place the custom file in the default location ansible looks in ~/.ansible/plugins/lookup:/usr/share/ansible/plugins/lookup or configure your ansible.cfg to look in a different place using the following lookup_plugins ini key under the defaults section.
DEFAULT_LOOKUP_PLUGIN_PATH
Description: Colon separated paths in which Ansible will search for Lookup Plugins.
Type: pathspec
Default: ~/.ansible/plugins/lookup:/usr/share/ansible/plugins/lookup
Ini Section: defaults
Ini Key: lookup_plugins
Environment: ANSIBLE_LOOKUP_PLUGINS
Documentation for this can be found in the Ansible Configuration section of the official documentation

How can I merge lists with ansible 2.6.3

In the past I used ansible 2.3.1.0 and now want to use ansible 2.6.3.
I now want to install some packages with apt. therefore I have to source lists and I want to merge them in one. In ansible 2.3.1.0 I just used the following:
apt_packages:
- "{{ packages_list1 + packages_list2 }}"
And I am getting the following error:
Traceback (most recent call last):\r\n File \"/tmp/ansible_k0tmag/ansible_module_apt.py\", line 1128, in <module>\r\n main()\r\n File \"/tmp/ansible_k0tmag/ansible_module_apt.py\", line 1106, in main\r\n
allow_unauthenticated=allow_unauthenticated\r\n File \"/tmp/ansible_k0tmag/ansible_module_apt.py\", line 521, in install\r\n pkgspec = expand_pkgspec_from_fnmatches(m, pkgspec, cache)\r\n File \"/tmp/ansible_k0tmag/ansible_module_apt.py\", line 439, in expand_pkgspec_from_fnmatches\r\n
pkgname_pattern, version = package_split(pkgspec_pattern)\r\n File \"/tmp/ansible_k0tmag/ansible_module_apt.py\", line 312, in package_split\r\n
parts = pkgspec.split('=', 1)\r\nAttributeError: 'list' object has no attribute 'split'\r\n", "msg": "MODULE FAILURE", "rc": 1}
Content of the role:
apt:
state: present
name: "{{ apt_packages }}"
force: yes
when: apt_packages is defined```
With apt_packages: "{{ packages_list1 + packages_list2 }}"(without the -), it will work.
Working sample:
- hosts: localhost
gather_facts: no
vars:
pkgs1:
- perl
- python
pkgs2:
- vim
- tar
packages: "{{ pkgs1 + pkgs2 }}"
tasks:
- apt:
name: "{{ packages }}"
state: present

Ansible vaulted variables with quotes in it

I am using Ansible 2.4.
I can't get following ansible-playbook to run:
test.yml
---
- hosts: "localhost"
become: no
vars:
foo_withsinglequote: !vault |
$ANSIBLE_VAULT;1.1;AES256
39313737636336313832376165636465346162366333663137373165363662316263336166393666
3566643732663063386333303638633962363863306463610a643931396636613361353165653265
38376630313939626637623538613432373336646663636563623062636238313731326263336263
3138643931323662620a336534383964663562353162393930613965386465616630363335326138
3431
foo_withdoublequote: !vault |
$ANSIBLE_VAULT;1.1;AES256
64633863363838326664323238313866616161313937323563636430326432393638336334303336
3533653339663438356238613937336466623834666537630a646139643033653237353262616662
30643732313861373130633036346361663130326332303932616433643761633739306137333237
6263653365386132620a633738663336313532366637613533313361646339623137393461383363
3332
tasks:
- name: Echo foo_withsinglequote
command: echo "{{ foo_withsinglequote }}"
- name: Echo foo_withdoublequote
command: echo "{{ foo_withdoublequote }}"
To generate the vault variables I used following:
$ echo 123 > vlt.txt
$ ansible-vault --vault-password-file=vlt.txt encrypt_string "abc\"def"
$ ansible-vault --vault-password-file=vlt.txt encrypt_string "abc\'def"
To run the playbook:
$ ansible-playbook --vault-password-file=vlt.txt test.yml
This gives following error:
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ValueError: No closing quotation
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "module_stderr": "Traceback (most recent call last):\n File \"/tmp/ansible_8uz23O/ansible_module_command.py\", line 213, in \n main()\n File \"/tmp/ansible_8uz23O/ansible_module_command.py\", line 182, in main\n args = shlex.split(args)\n File \"/usr/lib/python2.7/shlex.py\", line 279, in split\n return list(lex)\n File \"/usr/lib/python2.7/shlex.py\", line 269, in next\n token = self.get_token()\n File \"/usr/lib/python2.7/shlex.py\", line 96, in get_token\n raw = self.read_token()\n File \"/usr/lib/python2.7/shlex.py\", line 172, in read_token\n raise ValueError, \"No closing quotation\"\nValueError: No closing quotation\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 0}
How can I quote the vaulted variables correctly? Because I don't know in advance, if the vaulted variables will contain single or double quotes.
Your problem description, despite being well written, unfortunately wrongly attributes the problem to Ansible Vault.
In fact, the problem you reported, comes simply from trying to execute the task which effectively becomes:
- command: echo abc"def
Ansible Vault plays no role in causing this problem -- if you defined the variable directly with foo: abc\"def you'd get the same error message.
The solution is simply to quote the string in the echo command:
- command: echo '{{ foo }}'
Other than that you can use quote filter, but for Vault-protected variable you need to first set a static fact:
- set_fact:
bar: "{{ foo }}"
- command: echo {{ bar|quote }}
Finally, the simplest solution to the underlying problem is: do not use special characters in passwords. Increase the length instead.

Ansible Script Module Not Interpreting Variable

I am having an issue with the Ansible script module interpreting a with_items variable.
vsa_deploy_config/tasks/main.yml:
- name: Create VSA scripts for center
template:
src: vsa_deploy.ps1.j2
dest: "/opt/ansible/roles/vsa_deploy_config/files/{{ item.vsa_hostname }}.ps1"
when: target == "local"
with_items:
- "{{ vsa_center }}"
- name: Deploy VSAs on Center
script: "files/{{ item.vsa_hostname }}.ps1"
register: out
when: target == "win_center"
- debug: var=out
with_items:
- "{{ vsa_center }}"
vsa_deploy_config/vars/main.yml:
---
vsa_center:
- vcsa_hostname: 10.10.10.74
vcsa_username: administrator#vsphere.local
vcsa_password: password
vcsa_datacenter: DataCenter1
vsa_rdm_lun: 02000000006006bf1d58d25a1020d292f8fcfb22b3554353432d4d
vsa_hostname: sm01-ct01
vsa_mgmt_ip: 10.10.10.75
vsa_mgmt_netmask: 255.255.255.192
vsa_mgmt_gw: 10.10.10.65
vsa_mgmt_ns: 10.10.10.92
vsa_mgmt_pg: SC-MGMT
vsa_mgmt_moref: Network:network-13
vsa_iscsi_ip: 192.168.2.1
vsa_iscsi_netmask: 255.255.255.0
vsa_iscsi_pg: ISCSI
vsa_iscsi_moref: Network:network-22
vsa_mirror_ip: 192.168.5.1
vsa_mirror_netmask: 255.255.255.0
vsa_mirror_pg: Mirror
vsa_mirror_moref: Network:network-23
esxi_hostname: 10.10.10.72
esxi_datastore: DS-01
- vcsa_hostname: 10.10.10.74
vcsa_username: administrator#vsphere.local
vcsa_password: password
vcsa_datacenter: DataCenter1
vsa_rdm_lun: 02000000006006bf1d58d25dd0210bb356a78344e5554353432d4d
vsa_hostname: sm02-ct01
vsa_mgmt_ip: 10.10.10.76
vsa_mgmt_netmask: 255.255.255.192
vsa_mgmt_gw: 10.10.10.65
vsa_mgmt_ns: 10.10.10.92
vsa_mgmt_pg: SC-MGMT
vsa_mgmt_moref: Network:network-13
vsa_iscsi_ip: 192.168.2.2
vsa_iscsi_netmask: 255.255.255.0
vsa_iscsi_pg: ISCSI
vsa_iscsi_moref: Network:network-22
vsa_mirror_ip: 192.168.5.2
vsa_mirror_netmask: 255.255.255.0
vsa_mirror_pg: Mirror
vsa_mirror_moref: Network:network-23
esxi_hostname: 10.2.120.73
esxi_datastore: DS-02
When I run the playbook I get the following error:
TASK [vsa_deploy_config : Deploy VSAs on Center] *******************************************************************************
fatal: [auto-win1.lab.com]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'item' is undefined\n\nThe error appears to have been in '/opt/ansible/roles/vsa_deploy_config/tasks/main.yml': line 10, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Deploy VSAs on Center\n ^ here\n"}
to retry, use: --limit #/opt/ansible/powershell.retry
The first task using the template module interprets the item.vsa_hostname variable correctly, but the script module does not. Is the script module not capable of using with_items?
There is no with_items for your script task:
- name: Deploy VSAs on Center # -\
script: "files/{{ item.vsa_hostname }}.ps1" # \
register: out # / This is task1
when: target == "win_center" # -/
- debug: var=out # -\
with_items: # > This is task2
- "{{ vsa_center }}" # -/
I guess you'd want to move debug to the very bottom:
- name: Deploy VSAs on Center
script: "files/{{ item.vsa_hostname }}.ps1"
register: out
when: target == "win_center"
with_items: "{{ vsa_center }}"
- debug: var=out
P.S. also there is no need to feed unnecessary nested list into with_items.
just move the line - debug: var=out to the end of the file and it will work

Ansible: `RequirementParseError` when feeding a variable to `pip: name=pkg version="{{ v }}"`

When I have this role:
# playbooks/roles/ansible/tasks/main.yml
- name: Install Ansible
pip:
state: present
name: ansible
version: "{{ ansible_version }}"
# playbooks/roles/ansible/defaults/main.yml
ansible_version: 1.9.4
I get this error while running ansible-playbook version 1.9.4 or 2.0.0.2:
TASK: [ansible | Install Ansible] *********************************************
failed: [localhost] => {"cmd": "/usr/local/bin/pip install ansible=={'major': 1, 'full': '1.9.4', 'string': '1.9.4\\n configured module search path = None', 'minor': 9, 'revision': 4}", "failed": true}
msg:
:stderr: Invalid requirement: 'ansible=={major:'
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/pip/req/req_install.py", line 73, in __init__
req = pkg_resources.Requirement.parse(req)
File "/usr/local/lib/python2.7/dist-packages/pip/_vendor/pkg_resources/__init__.py", line 3036, in parse
req, = parse_requirements(s)
File "/usr/local/lib/python2.7/dist-packages/pip/_vendor/pkg_resources/__init__.py", line 2980, in parse_requirements
"version spec")
File "/usr/local/lib/python2.7/dist-packages/pip/_vendor/pkg_resources/__init__.py", line 2945, in scan_list
raise RequirementParseError(msg, line, "at", line[p:])
RequirementParseError: Expected version spec in ansible=={major: at =={major:
This is the playbook:
- name: Install Sensu
serial: "100%"
hosts: all
sudo: yes
roles:
- role: "ansible-pull"
server_type: "sensu"
ansible_version: "2"
Where the ansible-pull role depends on the ansible role in meta/main.yml.
Am I injecting the variable incorrectly in this case? Is there some problem with setting the variable in the dependent ansible-pull roll rather than directly in the ansible role?
It turns out that ansible_version is a magic variable set by Ansible.
Who knew?
Using an arbitrarily different but unused variable name does the trick.

Resources