Nested templating with folders and files - ansible

There is a variable which contains a name with value foo. Only for this name 'special' templating must be done. The folder name, the filename and content must be templated 2 times, and once without any additions. The remaining files must also be templated, but without the special treatment. Let me describe my issue:
This is my variable:
bar:
- name: something
- name: foo
This is my template directory in Ansible:
templates/foo/another_folder/foo.spec
templates/foo/folder_folder/folder2/some_file/foo.service
templates/foo/some_file.txt
templates/something/ignore.txt
The contents of foo.spec:
name: foo
The goal to have the directory structure on the target machine:
foo/another_folder/foo.spec
foo/folder_folder/folder2/some_file/foo.service
foo/some_file.txt
foo-1/another_folder/foo-1.spec
foo-1/folder_folder/folder2/some_file/foo-1.service
foo-1/some_file.txt
foo-2/another_folder/foo-2.spec
foo-2/folder_folder/folder2/some_file/foo-2.service
foo-2/some_file.txt
something/ignore.txt
The content for each foo.spec should be templated to:
# for foo:
name: foo
--
# for foo-1
name: foo-1
--
# for foo-2
name: foo-2
To solve my problem, I took a look at a similar question, which is with_filetree and loops. However, I simply can't figure out where to start. Is this possible to configure in Ansible?

This is quite an awkward requirement but was kind of fun crafting a solution so here we go.
The below solution takes for granted your templates have the exact same file names as the target files where you deploy them. If you ever want to add the j2 extension to them, see the examples in the filetree documentation to remove it while templating to target.
It is not possible in ansible to imbricate loops in the same task. The solution to this is to loop over an include_tasks and add more loops in the included file.
The basic operation is to template an entire file tree to a target dir so this will be our final included file. In between we just have to detect if we are in the foo situation where we want to loop X time over a range of integers, or in the default one where we only process the directory once.
I used the exact same file tree you introduced in your question for my tests. The only change is the content of the foo/another_folder/foo.spec which is now:
name: {{ spec_name }}
Let's start with the base playbook deploy.yml
---
- name: my bizare templating pattern
hosts: localhost
gather_facts: false
vars:
bar:
- name: something
- name: foo
target_base_dir: /tmp/example/
tasks:
- name: make sure target base dir exists
file:
path: "{{ target_base_dir }}"
state: directory
- name: load template pattern chooser file
include_tasks: "template_pattern_chooser.yml"
loop: "{{ bar }}"
loop_control:
loop_var: template_pattern
This is the included template_pattern_chooser.yml
---
- name: Select the corresponding templating pattern
vars:
find_me:
- "pattern_{{ template_pattern.name }}.yml"
- "pattern_default.yml"
include_tasks: "{{ lookup('first_found', find_me) }}"
As you can see, this will look for either a specific file named after the template pattern name or fallback to a default one.
Thi is the specific pattern_foo.yml. Note that this is where set the var spec_name which is used in the above template.
---
- name: "loop over our {{ template_pattern.name }} pattern"
vars:
target_dir_name: "{{ template_pattern.name }}-{{ pattern_iteration }}"
spec_name: "{{ target_dir_name }}"
include_tasks: "template_tree.yml"
loop: "{{ range(1,4) | list }}"
loop_control:
loop_var: pattern_iteration
pattern_default.yml:
---
- name: default templating pattern
vars:
target_dir_name: "{{ template_pattern.name }}"
include_tasks: template_tree.yml
Note that both files include the same template_tree.yml file. The only change is that we loop over it when we are dealing with the foo pattern. This is where the real job takes place:
---
- name: Get list of templates only once
set_fact:
template_tree: "{{ query('filetree', 'templates/' ~ template_pattern.name ~ '/') }}"
- name: Create needed target dir
file:
path: "{{ target_base_dir }}/{{ target_dir_name }}"
state: "directory"
- name: Create needed directories inside target
file:
path: "{{ target_base_dir }}/{{ target_dir_name }}/{{ item.path }}"
state: "{{ item.state }}"
loop: "{{ template_tree }}"
when: item.state == 'directory'
- name: Deploy templates
template:
src: "{{ item.src }}"
dest: "{{ target_base_dir }}/{{ target_dir_name }}/{{ item.path }}"
loop: "{{ template_tree }}"
when: item.state == 'file'
Running this as a test on my machine gives:
$ ansible-playbook deploy.yml
PLAY [my bizare templating pattern] *****************************************************************************
TASK [make sure target base dir exists] *****************************************************************************
changed: [localhost]
TASK [load template pattern chooser file] *****************************************************************************
included: /home/user/test/template_pattern_chooser.yml for localhost => (item={'name': 'something'})
included: /home/user/test/template_pattern_chooser.yml for localhost => (item={'name': 'foo'})
TASK [Select the corresponding templating pattern] *****************************************************************************
included: /home/user/test/pattern_default.yml for localhost
TASK [default templating pattern] *****************************************************************************
included: /home/user/test/template_tree.yml for localhost
TASK [Get list of templates only once] *****************************************************************************
ok: [localhost]
TASK [Create needed target dir] *****************************************************************************
changed: [localhost]
TASK [Create needed directories inside target] *****************************************************************************
skipping: [localhost] => (item={'root': '/home/user/test/templates/something/', 'path': 'ignore.txt', 'state': 'file', 'src': '/home/user/test/templates/something/ignore.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 6, 'mtime': 1640280116.3296282, 'ctime': 1640280116.3296282})
TASK [Deploy templates] *****************************************************************************
changed: [localhost] => (item={'root': '/home/user/test/templates/something/', 'path': 'ignore.txt', 'state': 'file', 'src': '/home/user/test/templates/something/ignore.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 6, 'mtime': 1640280116.3296282, 'ctime': 1640280116.3296282})
TASK [Select the corresponding templating pattern] *****************************************************************************
included: /home/user/test/pattern_foo.yml for localhost
TASK [loop over our foo pattern] *****************************************************************************
included: /home/user/test/template_tree.yml for localhost => (item=1)
included: /home/user/test/template_tree.yml for localhost => (item=2)
included: /home/user/test/template_tree.yml for localhost => (item=3)
TASK [Get list of templates only once] *****************************************************************************
ok: [localhost]
TASK [Create needed target dir] *****************************************************************************
changed: [localhost]
TASK [Create needed directories inside target] *****************************************************************************
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280024.0969715, 'ctime': 1640280024.0969715})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640279254.875486, 'ctime': 1640279254.875486})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'some_file.txt', 'state': 'file', 'src': '/home/user/test/templates/foo/some_file.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280116.3376281, 'ctime': 1640280116.3376281})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280051.5691671, 'ctime': 1640280051.5691671})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file/foo.service', 'state': 'file', 'src': '/home/user/test/templates/foo/folder_folder/some_file/foo.service', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280051.561167, 'ctime': 1640280051.561167})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder/foo.spec', 'state': 'file', 'src': '/home/user/test/templates/foo/another_folder/foo.spec', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 21, 'mtime': 1640279254.871486, 'ctime': 1640279254.871486})
TASK [Deploy templates] *****************************************************************************
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280024.0969715, 'ctime': 1640280024.0969715})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640279254.875486, 'ctime': 1640279254.875486})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'some_file.txt', 'state': 'file', 'src': '/home/user/test/templates/foo/some_file.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280116.3376281, 'ctime': 1640280116.3376281})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280051.5691671, 'ctime': 1640280051.5691671})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file/foo.service', 'state': 'file', 'src': '/home/user/test/templates/foo/folder_folder/some_file/foo.service', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280051.561167, 'ctime': 1640280051.561167})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder/foo.spec', 'state': 'file', 'src': '/home/user/test/templates/foo/another_folder/foo.spec', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 21, 'mtime': 1640279254.871486, 'ctime': 1640279254.871486})
TASK [Get list of templates only once] *****************************************************************************
ok: [localhost]
TASK [Create needed target dir] *****************************************************************************
changed: [localhost]
TASK [Create needed directories inside target] *****************************************************************************
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280024.0969715, 'ctime': 1640280024.0969715})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640279254.875486, 'ctime': 1640279254.875486})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'some_file.txt', 'state': 'file', 'src': '/home/user/test/templates/foo/some_file.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280116.3376281, 'ctime': 1640280116.3376281})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280051.5691671, 'ctime': 1640280051.5691671})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file/foo.service', 'state': 'file', 'src': '/home/user/test/templates/foo/folder_folder/some_file/foo.service', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280051.561167, 'ctime': 1640280051.561167})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder/foo.spec', 'state': 'file', 'src': '/home/user/test/templates/foo/another_folder/foo.spec', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 21, 'mtime': 1640279254.871486, 'ctime': 1640279254.871486})
TASK [Deploy templates] *****************************************************************************
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280024.0969715, 'ctime': 1640280024.0969715})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640279254.875486, 'ctime': 1640279254.875486})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'some_file.txt', 'state': 'file', 'src': '/home/user/test/templates/foo/some_file.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280116.3376281, 'ctime': 1640280116.3376281})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280051.5691671, 'ctime': 1640280051.5691671})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file/foo.service', 'state': 'file', 'src': '/home/user/test/templates/foo/folder_folder/some_file/foo.service', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280051.561167, 'ctime': 1640280051.561167})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder/foo.spec', 'state': 'file', 'src': '/home/user/test/templates/foo/another_folder/foo.spec', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 21, 'mtime': 1640279254.871486, 'ctime': 1640279254.871486})
TASK [Get list of templates only once] *****************************************************************************
ok: [localhost]
TASK [Create needed target dir] *****************************************************************************
changed: [localhost]
TASK [Create needed directories inside target] *****************************************************************************
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280024.0969715, 'ctime': 1640280024.0969715})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640279254.875486, 'ctime': 1640279254.875486})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'some_file.txt', 'state': 'file', 'src': '/home/user/test/templates/foo/some_file.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280116.3376281, 'ctime': 1640280116.3376281})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280051.5691671, 'ctime': 1640280051.5691671})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file/foo.service', 'state': 'file', 'src': '/home/user/test/templates/foo/folder_folder/some_file/foo.service', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280051.561167, 'ctime': 1640280051.561167})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder/foo.spec', 'state': 'file', 'src': '/home/user/test/templates/foo/another_folder/foo.spec', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 21, 'mtime': 1640279254.871486, 'ctime': 1640279254.871486})
TASK [Deploy templates] *****************************************************************************
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280024.0969715, 'ctime': 1640280024.0969715})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640279254.875486, 'ctime': 1640279254.875486})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'some_file.txt', 'state': 'file', 'src': '/home/user/test/templates/foo/some_file.txt', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280116.3376281, 'ctime': 1640280116.3376281})
skipping: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file', 'state': 'directory', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0755', 'size': 3, 'mtime': 1640280051.5691671, 'ctime': 1640280051.5691671})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'folder_folder/some_file/foo.service', 'state': 'file', 'src': '/home/user/test/templates/foo/folder_folder/some_file/foo.service', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 4, 'mtime': 1640280051.561167, 'ctime': 1640280051.561167})
changed: [localhost] => (item={'root': '/home/user/test/templates/foo/', 'path': 'another_folder/foo.spec', 'state': 'file', 'src': '/home/user/test/templates/foo/another_folder/foo.spec', 'uid': 1000, 'gid': 100, 'owner': 'user', 'group': 'users', 'mode': '0644', 'size': 21, 'mtime': 1640279254.871486, 'ctime': 1640279254.871486})
PLAY RECAP *****************************************************************************
localhost : ok=24 changed=12 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
This is the result in the target dir:
$ tree /tmp/example/
/tmp/example/
├── foo-1
│   ├── another_folder
│   │   └── foo.spec
│   ├── folder_folder
│   │   └── some_file
│   │   └── foo.service
│   └── some_file.txt
├── foo-2
│   ├── another_folder
│   │   └── foo.spec
│   ├── folder_folder
│   │   └── some_file
│   │   └── foo.service
│   └── some_file.txt
├── foo-3
│   ├── another_folder
│   │   └── foo.spec
│   ├── folder_folder
│   │   └── some_file
│   │   └── foo.service
│   └── some_file.txt
└── something
└── ignore.txt
13 directories, 10 files
And as an example the content of one the foo.spec files:
$ cat /tmp/example/foo-2/another_folder/foo.spec
name: foo-2

Related

AWX fails to retrieve collections from Ansible Galaxy

I have a project with roles/requirements.yml and collections/requirements.yml. This - I think - per AWX / Tower guidelines.
On project update the roles/requirements.yml is picked up successfully but the collections/requirements.yml fails with a typical Ansible error message unfortunately that doesn't help me understand what is going wrong.
PLAY [Update source tree if necessary] *****************************************
TASK [delete project directory before update] **********************************
changed: [localhost]
TASK [update project using git] ************************************************
changed: [localhost]
TASK [Set the git repository version] ******************************************
ok: [localhost]
TASK [Repository Version] ******************************************************
ok: [localhost] => {
"msg": "Repository Version 812a488b0a09f7fda6a784a8247abaf6892e1f13"
}
PLAY [Install content with ansible-galaxy command if necessary] ****************
TASK [detect roles/requirements.(yml/yaml)] ************************************
ok: [localhost] => (item={'ext': '.yml'})
ok: [localhost] => (item={'ext': '.yaml'})
TASK [fetch galaxy roles from requirements.(yml/yaml)] *************************
changed: [localhost] => (item={'changed': False, 'stat': {'exists': True, 'path': '/var/lib/awx/projects/_6__c2/roles/requirements.yml', 'mode': '0644', 'isdir': False, 'ischr': False, 'isblk': False, 'isreg': True, 'isfifo': False, 'islnk': False, 'issock': False, 'uid': 0, 'gid': 0, 'size': 390, 'inode': 131356, 'dev': 64771, 'nlink': 1, 'atime': 1613038457.4215767, 'mtime': 1613038456.678576, 'ctime': 1613038456.678576, 'wusr': True, 'rusr': True, 'xusr': False, 'wgrp': False, 'rgrp': True, 'xgrp': False, 'woth': False, 'roth': True, 'xoth': False, 'isuid': False, 'isgid': False, 'blocks': 8, 'block_size': 4096, 'device_type': 0, 'readable': True, 'writeable': True, 'executable': False, 'pw_name': 'root', 'gr_name': 'root', 'checksum': '29ae537db56b12b54f2eecb8668f4185b7b1e91d', 'mimetype': 'text/plain', 'charset': 'us-ascii', 'version': None, 'attributes': [], 'attr_flags': ''}, 'invocation': {'module_args': {'path': '/var/lib/awx/projects/_6__c2/roles/requirements.yml', 'follow': False, 'get_md5'…
skipping: [localhost] => (item={'changed': False, 'stat': {'exists': False}, 'invocation': {'module_args': {'path': '/var/lib/awx/projects/_6__c2/roles/requirements.yaml', 'follow': False, 'get_md5': False, 'get_checksum': True, 'get_mime': True, 'get_attributes': True, 'checksum_algorithm': 'sha1'}}, 'failed': False, 'item': {'ext': '.yaml'}, 'ansible_loop_var': 'item'})
TASK [detect collections/requirements.(yml/yaml)] ******************************
ok: [localhost] => (item={'ext': '.yml'})
ok: [localhost] => (item={'ext': '.yaml'})
TASK [fetch galaxy collections from collections/requirements.(yml/yaml)] *******
failed: [localhost] (item={'changed': False, 'stat': {'exists': True, 'path': '/var/lib/awx/projects/_6__c2/collections/requirements.yml', 'mode': '0644', 'isdir': False, 'ischr': False, 'isblk': False, 'isreg': True, 'isfifo': False, 'islnk': False, 'issock': False, 'uid': 0, 'gid': 0, 'size': 183, 'inode': 131286, 'dev': 64771, 'nlink': 1, 'atime': 1613038457.4195766, 'mtime': 1613038456.673576, 'ctime': 1613038456.673576, 'wusr': True, 'rusr': True, 'xusr': False, 'wgrp': False, 'rgrp': True, 'xgrp': False, 'woth': False, 'roth': True, 'xoth': False, 'isuid': False, 'isgid': False, 'blocks': 8, 'block_size': 4096, 'device_type': 0, 'readable': True, 'writeable': True, 'executable': False, 'pw_name': 'root', 'gr_name': 'root', 'checksum': '7b8e2a2b64c7d5e64917b548dc40347158f8bb1b', 'mimetype': 'text/plain', 'charset': 'us-ascii', 'version': None, 'attributes': [], 'attr_flags': ''}, 'invocation': {'module_args': {'path': '/var/lib/awx/projects/_6__c2/collections/requirements.yml', 'follow': False, '…
skipping: [localhost] => (item={'changed': False, 'stat': {'exists': False}, 'invocation': {'module_args': {'path': '/var/lib/awx/projects/_6__c2/collections/requirements.yaml', 'follow': False, 'get_md5': False, 'get_checksum': True, 'get_mime': True, 'get_attributes': True, 'checksum_algorithm': 'sha1'}}, 'failed': False, 'item': {'ext': '.yaml'}, 'ansible_loop_var': 'item'})
Of course I tested the requirements locally. It works. Just not in AWX.
With the exception of one task having status failed I really don't see any difference between the error message on failure and the success logging when fetches roles.
What is wrong here?
I just came across this question since I encountered the same situation. I was able to resolve my issue and would like to share my approach. My AWX version is 17.1.0 (Docker image).
How to identify the root cause
enable the -vvv option as sadok-f suggested (see detailed instructions below)
open the job execution logs
click on the logline of the failing task (a small pop-up appears)
click the JSON tab (or Standard Error - Most details are provided in the JSON tab)
read the significantly more descriptive error message
My issue was, that I had a typo in the Ansible Galaxy credentials. Therefore my dependencies within the requirements.yml were not installable.
How to increase the verbosity for project jobs
As mentioned by sadok-f, enable the -vvv option for more verbosity. That option is a bit hidden. You can see the current configuration at Settings > Jobs settings. Now look for "Run Project Updates With Higher Verbosity" (the project update triggers the installation of requirements.yml files).
At least for AWX 17.1.0, editing the settings via the GUI is not implemented. You need to patch the settings via AWX-API to enable the project update-job verbosity. To do so, visit the AWX-API:
<your-awx-fqdn>/api/v2/settings/jobs/
At the bottom of the page, you have a small window that allows you to modify the content. Within the content box look for:
"PROJECT_UPDATE_VVV": false,
and change it to:
"PROJECT_UPDATE_VVV": true,
Click the PATCH button. That enables the -vvv option for project refresh jobs. As written, those refresh jobs install the requirements.
Synch your project and have a look at the significantly more verbose job logs (Projects > Sync Project (the tiny cycle-arrow button)).

Ansible extract sub-string from a matched element of a List variable

I have a variable, which is a list as below:
"test_list": [
"some text here",
"server1",
"command1 parameter1",
"path/to/command2 parameter2 server1_202012",
"some more text",
"New backup file is server1_202019"
]
I am trying to extract the substring server1_202019 which is at the end of the line that starts with New backup file.... There is only one line like this and I've tried below option to get the substring.
- set_fact:
test_name: "{{ test_list | select('^New backup file is (.+)$','\\1') }}"
and the output I've received is:
"test_name": "<generator object _select_or_reject at 0x3fe5b40>"
I've tried below code also, but failed.
- set_fact:
test_name: "{{ test_list | regex_search('^New backup file is (.+)$','\\1') }}"
The error is:
"msg": "Unexpected templating type error occurred on ({{ test_list | regex_search('^New backup file is (.+)$','\\\\1') }}): ..... expected string or buffer"
Could somebody please suggest how I can get the output as below:
"test_name": "server1_202019"
The ansible version I'm using is 2.9
Thanks
Select the item from the list and then use regex_replace. For example
- set_fact:
test_name: "{{ test_list|
select('regex', my_regex)|
first|
regex_replace(my_regex, my_replace) }}"
vars:
my_regex: '^New backup file is (.*)$'
my_replace: '\1'
- debug:
var: test_name
give
"test_name": "server1_202019"
Since test_list is a list, you need to loop through the items to be matched using regex. Something like below can be done.
- set_fact:
matched: "{{ item | regex_search('^New backup file is (.+)$','\\1') }}"
loop: "{{ test_list }}"
register: matching_items
- debug:
var: item.ansible_facts.matched
with_items: "{{ matching_items.results }}"
when: item.ansible_facts.matched != ""
Should output like below for debug:
TASK [debug] *******************************************************************************************************************************
skipping: [localhost] => (item={'changed': False, 'ansible_facts': {'matched': ''}, 'failed': False, 'item': 'some text here', 'ansible_loop_var': 'item'})
skipping: [localhost] => (item={'changed': False, 'ansible_facts': {'matched': ''}, 'failed': False, 'item': 'server1', 'ansible_loop_var': 'item'})
skipping: [localhost] => (item={'changed': False, 'ansible_facts': {'matched': ''}, 'failed': False, 'item': 'command1 parameter1', 'ansible_loop_var': 'item'})
skipping: [localhost] => (item={'changed': False, 'ansible_facts': {'matched': ''}, 'failed': False, 'item': 'path/to/command2 parameter2 server1_202012', 'ansible_loop_var': 'item'})
ok: [localhost] => (item={'changed': False, 'ansible_facts': {'matched': ['server1_202019']}, 'failed': False, 'item': 'New backup file is server1_202019', 'ansible_loop_var': 'item'}) => {
"ansible_loop_var": "item",
"item": {
"ansible_facts": {
"matched": [
"server1_202019"
]
},
"ansible_loop_var": "item",
"changed": false,
"failed": false,
"item": "New backup file is server1_202019"
},
"item.ansible_facts.matched": [
"server1_202019"
]
}
skipping: [localhost] => (item={'changed': False, 'ansible_facts': {'matched': ''}, 'failed': False, 'item': 'some more text', 'ansible_loop_var': 'item'})

ansible output multiple variables to same line

I'm collecting the SQL version and edition found in registry using win_reg_stat, registering these into a variable, and writing them to a file.
Is it possible to write the values to the same line instead of separate lines?
- name: get sql version
win_reg_stat:
path: HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\{{ item }}\Setup
name: Version
with_items: "{{ sql_versions }}"
register: sql_version
- name: get sql edition
win_reg_stat:
path: HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\{{ item }}\Setup
name: Edition
with_items: "{{ sql_versions }}"
register: sql_edition
- lineinfile:
dest: /tmp/sql
line: "{{ inventory_hostname }};{{ item.value }}"
when: item.exists
with_items:
- "{{ sql_version.results }}"
delegate_to: localhost
- lineinfile:
dest: /tmp/sql
line: "{{ inventory_hostname }};{{ item.value }}"
when: item.exists
with_items:
- "{{ sql_edition.results }}"
delegate_to: localhost
The sql_versions variable contains multiple items:
sql_versions:
- MSSQL15.MSSQLSERVER
- MSSQL14.MSSQLSERVER
- MSSQL13.MSSQLSERVER
- MSSQL12.MSSQLSERVER
- MSSQL11.MSSQLSERVER
- MSSQL10_50.MSSQLSERVER
- MSSQL10.MSSQLSERVER
- MSSQL.1
Output example:
TASK [licensing : register sql version] *****************************************************************************************************************************************************************************************************
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL15.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL14.MSSQLSERVER', 'ansible_loop_var': 'item'})
ok: [win2019] => (item={'changed': False, 'type': 'REG_SZ', 'raw_value': '13.2.5026.0', 'value': '13.2.5026.0', 'exists': True, 'failed': False, 'item': 'MSSQL13.MSSQLSERVER', 'ansible_loop_var': 'item'}) => {
"msg": "13.2.5026.0"
}
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL12.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL11.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL10_50.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL10.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL.1', 'ansible_loop_var': 'item'})
TASK [licensing : register sql edition] *****************************************************************************************************************************************************************************************************
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL15.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL14.MSSQLSERVER', 'ansible_loop_var': 'item'})
ok: [win2019] => (item={'changed': False, 'type': 'REG_SZ', 'raw_value': 'Web Edition', 'value': 'Web Edition', 'exists': True, 'failed': False, 'item': 'MSSQL13.MSSQLSERVER', 'ansible_loop_var': 'item'}) => {
"msg": "Web Edition"
}
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL12.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL11.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL10_50.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL10.MSSQLSERVER', 'ansible_loop_var': 'item'})
skipping: [win2019] => (item={'changed': False, 'exists': False, 'failed': False, 'item': 'MSSQL.1', 'ansible_loop_var': 'item'})
did you tried this..
- lineinfile:
dest: /tmp/sql
line: "{{ inventory_hostname }};{{ item.value }}"
when: item.exists
with_items:
- "{{ sql_version.results }}"
- "{{ sql_edition.results }}"
delegate_to: localhost
Please try as below (not tested)
- lineinfile:
dest: /tmp/sql
line: "{{ inventory_hostname }};{{ item.version.value }};{{ item.edition.value }}"
when: item.exists
with_items:
- "{ version: {{sql_version.results}}, edition: {{sql_edition.results}} }"
delegate_to: localhost

Ansibe find module extra output, why and how

ansible-playbook find module shows extra output, how to quiet it.
Here is the playbook
---
- hosts: all
gather_facts: no
tasks:
- name: "Testing if I can list the home folder"
find:
paths: $HOME
register: files_matched
- name: "Output data"
debug:
msg: "Testing {{ item.path }}"
with_items: "{{ files_matched.files }}"
Here is the output
.....
......
ok: [cdl-z4-01.es.ad.adp.com] => (item={'uid': 1000, 'woth': False, 'mtime': 1450139013.0,
'inode': 1351746, 'isgid': False, 'size': 11814683, 'roth': False, 'isuid': False, 'isreg': True, 'pw_name': 'virtual', 'gid': 10, 'ischr': False, 'wusr': True, 'xoth': False, 'rusr': True, 'nlink': 1,\
'issock': False, 'rgrp': True, 'gr_name': 'wheel', 'path': '/home/virtual/buzz.war', 'xusr': False, 'atime': 1542161791.0,
'isdir': False, 'ctime': 1450139013.0, 'isblk': False, 'xgrp': False, 'dev': 2049, 'wgrp': False, 'isfifo': False, 'mode': '0640',
'islnk': False}) => {
"msg": "Testing /home/virtual/buzz.war"
}
......
....
===============
The output above shows the msg correctly.
However I see the ok line with hostname and extra information.
Am I doing something differently?
The output is controlled by the option ANSIBLE_STDOUT_CALLBACK. There are a lot of plugins to choose from. For example minimal, actionable or null. It is possible to write custom plugin.

Ansible - Register variable to item property in loop

I'm trying to register the output of a shell command to an item property inside a list of items.
This takes place during a loop but does not appear to register the properties. After the task is ran, the property is still showing the value none. I'm wondering if I'm doing something wrong ? Or Is there a way to accomplish this ?
Variables:
users:
- username: someguy
description: "Some Guy"
groups: ['sudo', 'guy']
new_id: 6001
old_uid:
old_gid:
user_exists:
password: waffles
- username: somedude
description: "Some Dude"
groups: ['dude']
new_id: 6002
old_uid:
old_gid:
user_exists:
password: toast
Tasks
---
- name: Check if user exists
shell: /usr/bin/getent passwd {{ item.username }} | /usr/bin/wc -l | tr -d ' '
with_items: "{{ users }}"
register: item.user_exists
- name: Check user current UID
shell: /usr/bin/id -u {{ item.username }}
with_items: "{{ users }}"
register: item.old_uid
when: item.user_exists == 1
- name: Check user current GID
shell: /usr/bin/id -g {{ item.username }}
with_items: "{{ users }}"
register: item.old_gid
when: item.user_exists == 1
Output
TASK: [users | Check if user exists] ******************************************
changed: [bserver] => (item={'username': 'someguy', 'password': 'waffles', 'description': 'Some Guy', 'new_id': 6001, 'groups': ['sudo', 'guy'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
changed: [aserver] => (item={'username': 'someguy', 'password': 'waffles', 'description': 'Some Guy', 'new_id': 6001, 'groups': ['sudo', 'guy'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
changed: [aserver] => (item={'username': 'somedude', 'password': 'toast', 'description': 'Some Dude', 'new_id': 6002, 'groups': ['dude'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
changed: [bserver] => (item={'username': 'somedude', 'password': 'toast', 'description': 'Some Dude', 'new_id': 6002, 'groups': ['dude'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
TASK: [users | Check user current UID] ****************************************
skipping: [aserver] => (item={'username': 'someguy', 'password': 'waffles', 'description': 'Some Guy', 'new_id': 6001, 'groups': ['sudo', 'guy'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
skipping: [aserver] => (item={'username': 'somedude', 'password': 'toast', 'description': 'Some Dude', 'new_id': 6002, 'groups': ['dude'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
skipping: [bserver] => (item={'username': 'someguy', 'password': 'waffles', 'description': 'Some Guy', 'new_id': 6001, 'groups': ['sudo', 'guy'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
skipping: [bserver] => (item={'username': 'somedude', 'password': 'toast', 'description': 'Some Dude', 'new_id': 6002, 'groups': ['dude'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
TASK: [users | Check user current GID] ****************************************
skipping: [aserver] => (item={'username': 'someguy', 'password': 'waffles', 'description': 'Some Guy', 'new_id': 6001, 'groups': ['sudo', 'guy'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
skipping: [aserver] => (item={'username': 'somedude', 'password': 'toast', 'description': 'Some Dude', 'new_id': 6002, 'groups': ['dude'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
skipping: [bserver] => (item={'username': 'someguy', 'password': 'waffles', 'description': 'Some Guy', 'new_id': 6001, 'groups': ['sudo', 'guy'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
skipping: [bserver] => (item={'username': 'somedude', 'password': 'toast', 'description': 'Some Dude', 'new_id': 6002, 'groups': ['dude'], 'user_exists': None, 'old_uid': None, 'old_gid': None})
Unfortunately that is not how it works.
Your line register: "{{item.user_exists}}" would probably result into the result registered in a variable called false.
You can not inject the result of a task into any object. The register feature will only take a string.
Additionally register has a complete different behavior in a loop. Instead of registering every iteration separately, it will register one single object which holds the results of all items in the key results.
You can still loop over your users and check for existence. But I don't think you will get with this where you want to be.
First register the result in one single variable, here users_checked:
- name: Check if user exists
shell: /usr/bin/getent passwd {{ item.username }} | /usr/bin/wc -l | tr -d ' '
with_items: users
register: users_checked
I recommend you to work with the debug module to always check with what structure of data you're working.
- debug: var=users_checked
This will show you something like this:
"var": {
"users_checked": {
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": "/usr/bin/getent passwd someguy | /usr/bin/wc -l | tr -d ' '",
"delta": "0:00:00.005415",
"end": "2015-09-08 20:59:33.379516",
"invocation": {
"module_args": "/usr/bin/getent passwd someguy | /usr/bin/wc -l | tr -d ' '",
"module_name": "shell"
},
"item": {
"description": "Some Guy",
"groups": [
"sudo",
"guy"
],
"new_id": 6001,
"old_gid": null,
"old_uid": null,
"password": "waffles",
"user_exists": null,
"username": "someguy"
},
"rc": 0,
"start": "2015-09-08 20:59:33.374101",
"stderr": "",
"stdout": "0",
"stdout_lines": [
"0"
],
"warnings": []
},
{
"changed": true,
"cmd": "/usr/bin/getent passwd somedude | /usr/bin/wc -l | tr -d ' '",
"delta": "0:00:00.006362",
"end": "2015-09-08 20:59:33.530546",
"invocation": {
"module_args": "/usr/bin/getent passwd somedude | /usr/bin/wc -l | tr -d ' '",
"module_name": "shell"
},
"item": {
"description": "Some Dude",
"groups": [
"dude"
],
"new_id": 6002,
"old_gid": null,
"old_uid": null,
"password": "toast",
"user_exists": null,
"username": "somedude"
},
"rc": 0,
"start": "2015-09-08 20:59:33.524184",
"stderr": "",
"stdout": "0",
"stdout_lines": [
"0"
],
"warnings": []
}
]
}
}
So the result not only holds the actual results of all items, but as well the input objects. That enables you to loop over users_checked.results instead of your original users list.
- name: Check user current UID
shell: /usr/bin/id -u {{ item.item.username }}
with_items: users_checked.results
when: item.stdout == 1
But now it would get nasty, since you want to check two more things. You probably could continue with this approach, register the above result, then use the registered data structure as input for the next loop, but you will end up with a deeply nested object and at the end having the original object somewhere in registered_var.results[x].item.results[y].item.results[z].item....
To avoid this mess you might want to look into creating a custom module for this task.

Resources