Loop multiple times to create multiple directory - ansible

I would like to loop many times to create multiple directory. I tried below was not working.
Would you be able to advice?
This my variable
main_dir:
- "/exec/db/dir1"
- "/exec/db/dir1/v1"
sub_dir:
- { directory: "subdir/database/connection", directory: "db_connection.xml" }
- { directory: "subdir/database/properties", directory: "application.properties" }
- { directory: "subdir/application", directory: "index.html" }
- { directory: "subdir/application/pom", directory: "pom.xml" }
This my playbook role
- name: Create multiple sub directory
file:
path: "{{ item[0] }}/{{ item[1].directory }}"
state: directory
with_nested:
- "{{ main_dir }}"
- "{{ sub_dir }}"
Expected result
/exec/db/dir1/subdir/database/connection
/exec/db/dir1/subdir/database/properties
/exec/db/dir1/subdir/application
/exec/db/dir1/subdir/application/pom
/exec/db/dir1/v1/subdir/database/connection
/exec/db/dir1/v1/subdir/database/properties
/exec/db/dir1/v1/subdir/application
/exec/db/dir1/v1/subdir/application/pom

It looks like the dictionaries in your list sub-dir are bogus.
Indeed, you have as directory key for both what seems to be a directory and what seems to be a file.
Your variable would be more correct this way:
sub_dir:
- { directory: "subdir/database/connection", file: "db_connection.xml" }
- { directory: "subdir/database/properties", file: "application.properties" }
- { directory: "subdir/application", file: "index.html" }
- { directory: "subdir/application/pom", file: "pom.xml" }
And with that dictionary, your playbooks works properly because the file does not override the directory anymore.
Given the playbook:
- hosts: all
gather_facts: no
tasks:
- file:
path: "{{ item[0] }}/{{ item[1].directory }}"
state: directory
with_nested:
- "{{ main_dir }}"
- "{{ sub_dir }}"
vars:
main_dir:
- "/exec/db/dir1"
- "/exec/db/dir1/v1"
sub_dir:
- { directory: "subdir/database/connection", file: "db_connection.xml" }
- { directory: "subdir/database/properties", file: "application.properties" }
- { directory: "subdir/application", file: "index.html" }
- { directory: "subdir/application/pom", file: "pom.xml" }
This give the recap:
PLAY [all] ************************************************************************************************************************
TASK [file] ***********************************************************************************************************************
changed: [localhost] => (item=['/exec/db/dir1', {'directory': 'subdir/database/connection', 'file': 'db_connection.xml'}])
changed: [localhost] => (item=['/exec/db/dir1', {'directory': 'subdir/database/properties', 'file': 'application.properties'}])
changed: [localhost] => (item=['/exec/db/dir1', {'directory': 'subdir/application', 'file': 'index.html'}])
changed: [localhost] => (item=['/exec/db/dir1', {'directory': 'subdir/application/pom', 'file': 'pom.xml'}])
changed: [localhost] => (item=['/exec/db/dir1/v1', {'directory': 'subdir/database/connection', 'file': 'db_connection.xml'}])
changed: [localhost] => (item=['/exec/db/dir1/v1', {'directory': 'subdir/database/properties', 'file': 'application.properties'}])
changed: [localhost] => (item=['/exec/db/dir1/v1', {'directory': 'subdir/application', 'file': 'index.html'}])
changed: [localhost] => (item=['/exec/db/dir1/v1', {'directory': 'subdir/application/pom', 'file': 'pom.xml'}])
PLAY RECAP ************************************************************************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Which creates the directory tree:
tree /exec/db/dir1/
/exec/db/dir1/
├── subdir
│   ├── application
│   │   └── pom
│   └── database
│   ├── connection
│   └── properties
└── v1
└── subdir
├── application
│   └── pom
└── database
├── connection
└── properties
13 directories, 0 files

Related

ansible load zshenv and use ENV in another task

I'd like to load a zshenv file (using source command) and then use the ENVs in another task.
This is what I have. I'm hoping there's a better solution
files
directory structure
.
├── ansible.cfg
├── hosts.yaml
├── profiles
│   └── macos.yaml
├── roles
│   └── base
│      ├── tasks
│      │   ├── git.yaml
│      │   └── main.yaml
│      └── vars
└── tools
└── zsh
└── .zshenv
./ansible.cfg
[defaults]
inventory = ./hosts.yaml
roles_path = ./roles/
stdout_callback = yaml
./hosts.yaml
---
all:
hosts:
localhost
./profiles/macos.yaml
---
# run MacOS configs
# - hosts: localhost
# connection: local
# tags: macos
# roles:
# - macos
# # when: ansible_distribution == "MacOSX"
- hosts: localhost
connection: local
tags: base
roles:
- base
./roles/base/main.yaml
---
- import_tasks: tasks/git.yaml
./roles/base/git.yaml
---
- name: source zshenv
shell:
cmd: source ../tools/zsh/.zshenv; echo $GIT_CONFIG_PATH
register: gitConfigPath
- name: Link gitconfig file
file:
# PWD: ./profiles
src: "{{ ansible_env.PWD }}/../tools/git/.gitconfig"
dest: "{{ gitConfigPath.stdout }}"
state: link
# - name: print ansible_env
# debug:
# msg: "{{ ansible_env }}"
#
# - name: print gitConfigPath
# debug:
# msg: "{{ gitConfigPath.stdout }}"
#
./tools/zsh/.zshenv
export XDG_CONFIG_HOME="$HOME/.config"
export GIT_CONFIG_PATH="$XDG_CONFIG_HOME/git/config"
command to run
ansible-playbook profiles/macos.yaml -v
PS: It'd be easier to do something like this in ansible
source tools/zsh/.zshenv && ansible-playbook profiles/macos.yaml -v
Given the simplified project without a role
shell> tree -a .
.
├── ansible.cfg
├── hosts
├── pb.yml
└── tools
├── git
└── zsh
└── .zshenv
shell> cat hosts
localhost
shell> cat tools/zsh/.zshenv
export GIT_CONFIG_PATH=/home/admin/git/.gitconfig
export ENV1=env1
export ENV2=env2
export ENV3=env3
eval "$(direnv hook zsh)"
Parse the environment on your own. For example
zshenv: "{{ dict(lookup('file', 'tools/zsh/.zshenv').splitlines()|
select('match', '^\\s*export .*$')|
map('regex_replace', '^\\s*export\\s+', '')|
map('split', '=')) }}"
gives
zshenv:
ENV1: env1
ENV2: env2
ENV3: env3
GIT_CONFIG_PATH: /home/admin/git/.gitconfig
Then, use the dictionary zshenv
- name: Link gitconfig file
file:
dest: "{{ playbook_dir }}/tools/git/.gitconfig"
src: "{{ zshenv.GIT_CONFIG_PATH }}"
state: link
gives, running with --check -- diff options
TASK [Link gitconfig file] *******************************************************************
--- before
+++ after
## -1,2 +1,2 ##
path: /export/scratch/tmp7/test-116/tools/git/.gitconfig
-state: absent
+state: link
changed: [localhost]
Notes
Example of a complete playbook for testing
shell> cat pb.yml
- hosts: localhost
vars:
zshenv: "{{ dict(lookup('file', 'tools/zsh/.zshenv').splitlines()|
select('match', '^\\s*export .*$')|
map('regex_replace', '^\\s*export\\s+', '')|
map('split', '=')) }}"
tasks:
- debug:
var: zshenv
- name: Link gitconfig file
file:
dest: "{{ playbook_dir }}/tools/git/.gitconfig"
src: "{{ zshenv.GIT_CONFIG_PATH }}"
state: link
gives
shell> ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [debug] *********************************************************************************
ok: [localhost] =>
zshenv:
ENV1: env1
ENV2: env2
ENV3: env3
GIT_CONFIG_PATH: /home/admin/git/.gitconfig
TASK [Link gitconfig file] *******************************************************************
changed: [localhost]
PLAY RECAP ***********************************************************************************
localhost: ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The link tools/git/.gitconfig -> /home/admin/git/.gitconfig was created
shell> tree -a .
.
├── ansible.cfg
├── hosts
├── pb.yml
└── tools
├── git
│   └── .gitconfig -> /home/admin/git/.gitconfig
└── zsh
└── .zshenv
You can use the dictionary zshenv to set the environment. For example,
- command: echo $GIT_CONFIG_PATH
environment: "{{ zshenv }}"
register: out
- debug:
var: out.stdout
gives
out.stdout: /home/admin/git/.gitconfig
Cache the dictionary if you want to use this environment globally in the whole play. For example,
shell> grep fact_caching ansible.cfg
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_cache
fact_caching_prefix = ansible_facts_
fact_caching_timeout = 86400
- set_fact:
zshenv: "{{ dict(lookup('file', 'tools/zsh/.zshenv').splitlines()|
select('match', '^\\s*export .*$')|
map('regex_replace', '^\\s*export\\s+', '')|
map('split', '=')) }}"
cacheable: true
Then,
- hosts: localhost
environment: "{{ zshenv }}"
tasks:
- command: echo $GIT_CONFIG_PATH
register: out
- debug:
var: out.stdout
gives
PLAY [localhost] *****************************************************************************
TASK [command] *******************************************************************************
changed: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
out.stdout: /home/admin/git/.gitconfig
PLAY RECAP ***********************************************************************************
localhost: ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
With the help from Vladimir Botka, using the answer from https://stackoverflow.com/a/74924664/3053548
I modified a little bit of his code
TLDR
source the zshenv file
print out all the ENV in the shell session
store the output to as ansible facts
access the ENV in a task
Files
./profiles/macos.yaml
---
# run MacOS configs
- hosts: localhost
connection: local
tags: always
tasks:
- name: source zshenv
shell:
cmd: source ../tools/zsh/.zshenv; env
register: out
changed_when: false
- name: store zshenv as fact
set_fact:
zshenv: "{{ dict(out.stdout.splitlines() | map('split', '=')) }}"
changed_when: false
# - hosts: localhost
# connection: local
# tags: macos
# roles:
# - macos
# # when: ansible_distribution == "MacOSX"
- hosts: localhost
connection: local
tags: base
roles:
- base
./roles/base/tasks/git.yaml
---
- name: Link gitconfig file
file:
src: "{{ ansible_env.PWD }}/../tools/git/.gitconfig"
dest: "{{ zshenv.GIT_CONFIG_PATH }}"
state: link
command to run
ansible-playbook profiles/macos.yaml

Looping through a product of two arrays

I have this vars file in Ansible:
for_create:
client: ["VK","SB"]
folders: ["toula","tina"]
for_delete:
client: ["VK","SB"]
folders: ["invoices","scripts"]
for_rename:
client: ["VK", "SB"]
old_name: ["home"]
new_name: ["town"]
Is it possible to do something like that in YAML? The following code in Ruby
clients = ["VK", "SB"]
folders = ["toula","tina"]
clients.each do |client|
folders.each do |folder|
puts "folder #{folder} for client #{client} created"
end
end
To sum up I want each client to create the folders ["toula", "tina"]
I have tried a lot but I can't manage to make it loop twice for each folder.
The output of the above code
folder toula for client VK created
folder tina for client VK created
folder toula for client SB created
folder tina for client SB created
Iterate the lists with_nested. For example,
- debug:
msg: "folder {{ item.1 }} for client {{ item.0 }} created"
with_nested:
- "{{ for_create.client }}"
- "{{ for_create.folders }}"
vars:
for_create:
client: [VK, SB]
folders: [toula, tina]
gives (abridged)
msg: folder toula for client VK created
msg: folder tina for client VK created
msg: folder toula for client SB created
msg: folder tina for client SB created
To rename folders zip the lists
- debug:
msg: "folder {{ item.1 }} renamed to {{ item.2 }} for client {{ item.0 }}"
with_nested:
- "{{ for_rename.client }}"
- "{{ for_rename.old_name|zip(for_rename.new_name) }}"
vars:
for_rename:
client: [VK, SB]
old_name: [home]
new_name: [town]
gives (abridged)
msg: folder home renamed to town for client VK
msg: folder home renamed to town for client SB
Q: "Is it possible to check if the folder exists inside the loop?"
A: Yes. It is. Use the parameters creates or removes. For example, given the tree
shell> tree /tmp/home/
/tmp/home/
├── SB
│   └── home
└── VK
└── home
The playbook below
shell: cat pb.yml
- hosts: localhost
tasks:
- command:
cmd: "mv {{ main_path }}/{{ item.1 }} {{ main_path }}/{{ item.2 }}"
removes: "{{ main_path }}/{{ item.1 }}"
with_nested:
- "{{ for_rename.client }}"
- "{{ for_rename.old_name|zip(for_rename.new_name) }}"
vars:
main_path: "/tmp/home/{{ item.0 }}"
for_rename:
client: [VK, SB]
old_name: [home]
new_name: [town]
moves the files only if exist
shell> ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [command] *******************************************************************************
changed: [localhost] => (item=['VK', 'home', 'town'])
changed: [localhost] => (item=['SB', 'home', 'town'])
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> tree /tmp/home/
/tmp/home/
├── SB
│   └── town
└── VK
└── town
2 directories, 2 files
The playbook is idempotent. The commands will not execute if the files are missing
shell> ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [command] *******************************************************************************
ok: [localhost] => (item=['VK', 'home', 'town'])
ok: [localhost] => (item=['SB', 'home', 'town'])
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can use the module command the same way also to create or remove files. The next option is using the module file. For example, the playbook below
shell> cat pb.yml
- hosts: localhost
tasks:
- file:
state: touch
path: "{{ main_path }}/{{ item.1 }}"
with_nested:
- "{{ for_create.client }}"
- "{{ for_create.folders }}"
vars:
main_path: "/tmp/home/{{ item.0 }}"
for_create:
client: [VK, SB]
folders: [toula, tina]
creates the files
shell> ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [file] **********************************************************************************
changed: [localhost] => (item=['VK', 'toula'])
changed: [localhost] => (item=['VK', 'tina'])
changed: [localhost] => (item=['SB', 'toula'])
changed: [localhost] => (item=['SB', 'tina'])
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> tree /tmp/home/
/tmp/home/
├── SB
│   ├── tina
│   ├── toula
│   └── town
└── VK
├── tina
├── toula
└── town
This task is not idempotent because the files will receive updated file access and modification times (similar to the way touch works from the command line). Preserve access_time and modification_time to make the task idempotent
- file:
state: touch
path: "{{ main_path }}/{{ item.1 }}"
access_time: preserve
modification_time: preserve
...

Ansible - Find and Replace

Use case
List all files in a directory with the format - a1.{{ env }}.js, a2.{{ env }}.js
Find corresponding files in the destination directory with the format - a1.js, a2.js
Copy a1.{{ env }}.js in the directory where a1.js exists, Copy a2.{{ env }}.js in the directory where a2.js exists
Sample code: This code does a direct find and replace
- name: Find files in archive
find:
paths: "archive/"
file_type: file
recurse: yes
register: tmp_file_path
- name: Find files in code matching names in archive
find:
paths: "code/"
file_type: file
recurse: yes
patterns: "{{ tmp_file_path.files | map(attribute='path') | map('basename') | list }}"
register: code_file_path
- set_fact:
code_files: "{{ code_files|default([]) +
[{'path': item, 'name': item|basename}] }}"
loop: "{{ code_file_path.files|map(attribute='path')|list }}"
- name: Copy files from archive to code directory
command: cp "{{ item.0 }}" "{{ item.1.path }}"
when:
- item.0|basename == item.1.path|basename
with_together:
- "{{ tmp_file_path.files|map(attribute='path')|list|sort }}"
- "{{ code_files|sort(attribute='name') }}"
Below listed is the directory structure
├── archive
│   ├── a1.test.js
│   ├── a2.test.js
│   ├── a3.test.js
│   └── a4.test.js
└── code
├── a1.js
├── dir1
│   └── a2.js
└── dir2
├── a4.js
└── dir3
└── a3.js
Copy archive/a1.test.js to code/
Copy archive/a2.test.js to code/dir1/
Copy archive/a3.test.js to code/dir1/dir2/dir3/
Copy archive/a4.test.js to code/dir1/dir2/
Is there a solution to do a direct copy as per the above use case?
Some explanation on the approach:
The entire idea of it is based on the creation of a dictionary instructing the playbook on which file should be added where.
The dictionary, for your use case would look like this:
{
"a1.js": {
"archive": "archive/a1.test.js",
"paths": [
"code"
]
},
"a2.js": {
"archive": "archive/a2.test.js",
"paths": [
"code/dir1"
]
},
"a3.js": {
"archive": "archive/a3.test.js",
"paths": [
"code/dir1/dir2/dir3"
]
},
"a4.js": {
"archive": "archive/a4.test.js",
"paths": [
"code/dir1/dir2"
]
}
}
Where the keys are the files we are searching for under the code folder, the key archive represent the file we aim to copy from the archive folder and paths is an array where the said file should find its destination(s).
Most of the logic is done by the Ansible filter regex_replace, that extract the name of the file to find in the code folder via a quite simple expression: (.*)\..*\.js$
item.path | basename | regex_replace('(.*)\\..*\\.js$', '\\1.js')
Another filter used here that might be interesting to explore is the combine filter, with the parameter recursive=true, that allows to create the paths where the files should find their destination(s).
There is also a Python operation used here: dict.keys(), in order to create the comma separated list of files to search in the code folder out of the keys of the dictionary above.
It is also making use of loop with the subelement filter, to traverse both the dictionary and its sub array paths at the same time.
And to be complete, here are the other, more commonly used filters used in this playbook:
default: to specify a default value when a variable is not defined
basename: to get only the name of a file out of its full path
dirname: to get only the directory from a full path to a file
join: to concatenate the elements of an array, here, based on a separator
Yes, it might be over-engineered compared to your use case, the playbook here-under is able to cope with the fact that a1.js could be in two different folders and will be able to copy a1.test.js in both those folders.
So, here is a solution:
- hosts: localhost
gather_facts: no
tasks:
- find:
paths: archive
file_type: file
recurse: yes
register: archives
- set_fact:
searches: "{{ searches | default({}) | combine({ key: value }) }}"
vars:
key: "{{ item.path | basename | regex_replace('(.*)\\..*\\.js$', '\\1.js') }}"
value: "{{ { 'archive': item.path, 'paths': [] } }}"
loop: "{{ archives.files }}"
loop_control:
label: "{{ item.path }}"
- find:
path: code
file_type: file
recurse: yes
pattern: "{{ searches.keys() | join(',') }}"
register: paths
- set_fact:
searches: "{{ searches | combine({key: value}, recursive=true) }}"
vars:
key: "{{ item.path | basename }}"
value: "{{ { 'paths': [item.path | dirname] + searches[item.path | basename].paths } }}"
loop: "{{ paths.files }}"
loop_control:
label: "{{ item.path }}"
- copy:
src: "{{ item.0.archive }}"
dest: "{{ item.1 ~ '/' ~ item.0.archive | basename }}"
loop: "{{ searches | subelements('paths') }}"
loop_control:
label: "{{ item.0.archive }}"
Situation before:
tree archive code
archive
├── a1.test.js
├── a2.test.js
├── a3.test.js
└── a4.test.js
code
├── a1.js
└── dir1
├── a2.js
└── dir2
├── a4.js
└── dir3
└── a3.js
3 directories, 8 files
Recap of the playbook:
PLAY [localhost] **************************************************************************************************
TASK [find] *******************************************************************************************************
ok: [localhost]
TASK [set_fact] ***************************************************************************************************
ok: [localhost] => (item=archive/a3.test.js)
ok: [localhost] => (item=archive/a2.test.js)
ok: [localhost] => (item=archive/a1.test.js)
ok: [localhost] => (item=archive/a4.test.js)
TASK [find] *******************************************************************************************************
ok: [localhost]
TASK [set_fact] ***************************************************************************************************
ok: [localhost] => (item=code/a1.js)
ok: [localhost] => (item=code/dir1/a2.js)
ok: [localhost] => (item=code/dir1/dir2/a4.js)
ok: [localhost] => (item=code/dir1/dir2/dir3/a3.js)
TASK [copy] *******************************************************************************************************
changed: [localhost] => (item=archive/a3.test.js)
changed: [localhost] => (item=archive/a2.test.js)
changed: [localhost] => (item=archive/a1.test.js)
changed: [localhost] => (item=archive/a4.test.js)
PLAY RECAP ********************************************************************************************************
localhost : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Situation after:
tree code
code
├── a1.js
├── a1.test.js
└── dir1
├── a2.js
├── a2.test.js
└── dir2
├── a4.js
├── a4.test.js
└── dir3
├── a3.js
└── a3.test.js
3 directories, 8 files

Ansible role of duplicate works for the absolute parameters only

I have playbook like this
test_playbook/
├── dep_test.yaml
├── my_hosts_file
└── roles
├── common
│   └── vars
│   └── main.yaml
├── dep_test
│   ├── meta
│   │   └── main.yaml
│   └── tasks
│   └── main.yaml
├── dep_test_a
│   └── tasks
│   └── main.yaml
└── dep_test_b
├── meta
│   └── main.yaml
└── tasks
└── main.yaml
Files content are as below.
dep_test.yaml
- hosts: my_host
gather_facts: no
roles:
- common
- dep_test
my_hosts_file
[my_host]
localhost
roles/common/vars/main.yaml
python_version: "3"
roles/dep_test/tasks/main.yaml
- name: debug test
debug:
msg: test debug
roles/dep_test/meta/main.yaml
dependencies:
- role: dep_test_a
# pyenv_versions: ["{{ python_version }}"]
pyenv_versions: ["3"]
- role: dep_test_b
# python_versions: ["{{ python_version }}"]
python_versions: ["3"]
roles/dep_test_a/tasks/main.yaml
- name: Dep test a
debug:
msg: "Dependency test a called with {{ pyenv_versions }}"
roles/dep_test_b/tasks/main.yaml
- name: Dep test b
debug:
msg: "Dependency test b called with {{ python_versions }}"
roles/dep_test_b/meta/main.yaml
dependencies:
- role: dep_test_a
# pyenv_versions: "{{ python_versions }}"
pyenv_versions: ["3"]
When I pass parameter as ["3"] it works fine and apply the Role Duplication and Execution
ansible-playbook dep_test.yaml -i my_hosts_file -u root --ask-pass
SSH password:
PLAY [my_host] ****************************************************************************************************
TASK [dep_test_a : Dep test a] ************************************************************************************
ok: [localhost] => {
"msg": "Dependency test a called with [u'3']"
}
TASK [dep_test_b : Dep test b] ************************************************************************************
ok: [localhost] => {
"msg": "Dependency test b called with [u'3']"
}
TASK [dep_test : debug test] **************************************************************************************
ok: [localhost] => {
"msg": "test debug"
}
PLAY RECAP ********************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
When I change the parameter from ["3"] to use the variable form common/vars/main.yaml's python_version, it fail the Duplication rule and execute same role with duplicate arguments.
After change code would be
roles/dep_test/meta/main.yaml
dependencies:
- role: dep_test_a
pyenv_versions: ["{{ python_version }}"]
# pyenv_versions: ["3"]
- role: dep_test_b
python_versions: ["{{ python_version }}"]
# python_versions: ["3"]
roles/dep_test_b/meta/main.yaml
dependencies:
- role: dep_test_a
pyenv_versions: "{{ python_versions }}"
# pyenv_versions: ["3"]
Playbook execution output.
ansible-playbook dep_test.yaml -i my_hosts_file -u root --ask-pass
SSH password:
PLAY [my_host] ****************************************************************************************************
TASK [dep_test_a : Dep test a] ***********************************************************************************
ok: [localhost] => {
"msg": "Dependency test a called with [u'3']"
}
TASK [dep_test_a : Dep test a] ************************************************************************************
ok: [localhost] => {
"msg": "Dependency test a called with [u'3']"
}
TASK [dep_test_b : Dep test b] ************************************************************************************
ok: [localhost] => {
"msg": "Dependency test b called with [u'3']"
}
TASK [dep_test : debug test] **************************************************************************************
ok: [localhost] => {
"msg": "test debug"
}
PLAY RECAP ********************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
Role dep_test_a called 2 times with same arguments [u'3']
TASK [dep_test_a : Dep test a] ***********************************************************************************
ok: [localhost] => {
"msg": "Dependency test a called with [u'3']"
}
TASK [dep_test_a : Dep test a] ************************************************************************************
ok: [localhost] => {
"msg": "Dependency test a called with [u'3']"
}
One for the dependencies in dep_test role and another for dep_test_b.
As per the rule of dependencies, this should be called only once.
Question: Why dependent role runs twice when passing the parameter?
Question: Why dependent role runs twice when passing the parameter?
Answer: Because Ansible uses lazy evaluation, hence Jinja2 templating is not being triggered until the variable is used.
Passing a variable to a role is not considered a usage, so it passes and compares the templates, not the values.
You call the dep_test_a role twice:
- role: dep_test_a
pyenv_versions: ["{{ python_version }}"]
and:
- role: dep_test_a
pyenv_versions: "{{ python_versions }}"
["{{ python_version }}"] is not equal to "{{ python_versions }}", thus Ansible executes the role twice.
And btw, the code to illustrate the behaviour in the question can be shortened to:
- hosts: localhost
connection: local
gather_facts: no
vars:
my_var1: 1
my_var2: 1
roles:
- role: my_role
role_param: "{{ my_var1 }}"
- role: my_role
role_param: "{{ my_var2 }}"

Ansible: playbook calling Role in a directory that is in the roles directory

I would like to shape my directory structure of my ansible roles and playbooks.
Currently I have a directory structure like.
group_vars
* all
* group-one
- group-vars.yml
- group-vault.yml
...
host_vars
- server1.yml
plays
- java_plays
* deploy_fun_java_stuff.yml
* deploy_playbook.yml
roles
- role1
- tasks
* main.yml
- handlers
- (the rest of the needed directories)
- role2
- java
- java_role1
- tasks
* main.yml
- handlers
- (the rest of the needed directories)
I would like to be able to call upon the role java_role1 in the play deploy_fun_java_stuff.yml
I can call
---
- name: deploy fun java stuff
hosts: java
roles:
- { role: role1 }
but I cannot call (I've tried multiple ways). Is this possible?
- name: deploy fun java stuff
hosts: java
roles:
- { role: java/java_role1 }
What I really want to accomplish is to be able to structure my plays in an orderly fashion along with my roles.
I will end up with a large number of both roles and plays I would like to organize them.
I can handle this with a separate ansible.cfg file for each play directory but I cannot add those cfg files to ansible tower (So I'm looking for an alternate solution).
I think the problem is that you need to set the relative path properly. Ansible first applies the given path relative to the called playbooks directory, then looks in the current working path (from which you are executing the ansible-playbook command) and finally checks in /etc/ansible/roles, so instead of { role: java/java_role1 } in your dir structure you could use { role: ../../roles/java/java_role1 } or { role: roles/java/java_role1 }. Yet another option would be to configure the paths in which ansible is looking for roles. For that you could set the roles_path inside your projects ansible.cfg as described in the Ansible docs.
Based on your example:
Dir tree:
ansible/
├── hosts
│   └── dev
├── plays
│   └── java_plays
│   └── java.yml
└── roles
├── java
│   └── java_role1
│   └── tasks
│   └── main.yml
└── role1
└── tasks
└── main.yml
To test it, the play would include java_role1 and role1.
plays/java_plays/java.yml:
---
- name: deploy java stuff
hosts: java
roles:
- { role: roles/role1 }
- { role: roles/java/java_role1 }
For testing purposes these roles simply print a debug msg.
role1/tasks/main.yml:
---
- debug: msg="Inside role1"
The dev hosts file simply sets localhost to the java group. Now I can use the playbook:
fishi#zeus:~/workspace/ansible$ ansible-playbook -i hosts/dev plays/java_plays/java.yml
PLAY [deploy java stuff] *******************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [role1 : debug] ***********************************************
ok: [localhost] => {
"msg": "Inside role1"
}
TASK [java_role1 : debug] *************************************
ok: [localhost] => {
"msg": "Inside java_role1"
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
Now doing the same when you use { role: ../../roles/java/java_role1 } and { role: ../../roles/role1 } your log output inside the TASK brackets would show the whole relative path instead of just the role name:
fishi#zeus:~/workspace/ansible$ ansible-playbook -i hosts/dev plays/java_plays/java.yml
PLAY [deploy java stuff] *******************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [../../roles/role1 : debug] ***********************************************
ok: [localhost] => {
"msg": "Inside role1"
}
TASK [../../roles/java/java_role1 : debug] *************************************
ok: [localhost] => {
"msg": "Inside java_role1"
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
Another option and one that I use is to create an ansible.cfg file in your playbook directory and place the following in it:
[defaults]
roles_path = /etc/ansible/roles: :
or in your case:
[defaults]
roles_path = /etc/ansible/roles:/etc/ansible/roles/java
Then don't use any relative paths.
A more elegant solution (imo) is symlinking your roles directory to inside the playbooks directory.
My directory structure is as follows:
inventory/
playbooks/
|-> roles -> ../roles
|-> group_vars -> ../group_vars
|-> host_vars -> ../host_vars
roles/
group_vars/
host_vars/
In my case, I created the symlink by running ln -s ../roles playbooks/roles

Resources