Yml must be stored as a dictionary/hash - ansible

I have a yaml file of usernames and their ssh keys stored like this:
---
- user: bob
name: bob McBob
ssh_keys:
- ssh-rsa ...
- user: fred
name: fred McFred
ssh_keys:
- ssh-rsa ...
I'm trying to grab the user and ssh_keys keys so i can use this file to setup the users on linux hosts
It seems Ansible does not like the format of this file though, as this simple task throws an error:
- name: Get SSH Keys
include_vars:
file: ../admins.yml
name: ssh_keys
TASK [network-utility-servers : Get SSH Keys] *******************************************************************************************************************************
task path: main.yml:1
fatal: [127.0.0.1]: FAILED! => {
"ansible_facts": {
"ssh_keys": {}
},
"ansible_included_var_files": [],
"changed": false,
"message": "admins.yml must be stored as a dictionary/hash"
}
Unfortunately I can't change the format of the admins.yml file as it is used in other tools and changing the format will break them.
Any suggestions on how I can work around this?
Looks like ansible wants the admins.yml file to look like this:
---
foo:
- user: bob
name bob mcbob
ssh_keys:
- ssh-rsa ..

As you found out, include_vars is expecting a file containing dict key(s) at the top level. But there are other ways to read yaml files in ansible.
If you cannot change the file, the simplest way is to read its content inside a variable using a file lookup and the from_yaml filter.
Here is an example playbook. For this test, your above example data was stored in admins.yml in the same folder as the playbook. Adapt accordingly.
---
- hosts: localhost
gather_facts: false
vars:
admins: "{{ lookup('file', 'admins.yml') | from_yaml }}"
tasks:
- name: Show admin users list
debug:
var: admins
Which gives:
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Show admin users list] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"admins": [
{
"name": "bob McBob",
"ssh_keys": [
"ssh-rsa ..."
],
"user": "bob"
},
{
"name": "fred McFred",
"ssh_keys": [
"ssh-rsa ..."
],
"user": "fred"
}
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

As you said, the given vars file has not a valid format. You could use a task to fix the file in the fashion you described, and then attempt to load it.
The lineinfile task could be of use to do the trick, as in below example:
---
- hosts: localhost
gather_facts: false
vars:
tasks:
- name: fix vars file
lineinfile:
path: "{{ playbook_dir }}/vars_file.yml"
insertafter: "^---$"
line: "ssh_keys:"
backup: yes
- name: Get SSH Keys
include_vars:
file: "{{ playbook_dir }}/vars_file.yml"
- debug: var=ssh_keys
If you are not supposed to edit that file, you could copy to a new file name (vars_file_fixed.yml), then apply the lineinfile and load it.
cheers

Related

Ansible Task Using 'ansible.builtin.unvault' lookup

The ansible code below takes an ansible vault (vault.yml) and then uses the ansible.builtin.unvault lookup to retrieve and save the entire vault as the variable full_vault. The output of the debug shows the code in json. This code is working as expected.
- name: Pull vault into Variable from encrypted YAML file
hosts: localhost
gather_facts: no
tasks:
- name: Get specific value from vault file
set_fact:
full_vault: "{{ lookup('ansible.builtin.unvault', 'vault.yml') | from_yaml }}"
- name: Display Vault
ansible.builtin.debug:
msg: "Vault: {{ full_vault }}"
The challenge I am having is trying to use the ansible.builtin.vault lookup to put the full_vault variable back into an ansible vault. How can I accomplish this in a single task?
I am using the environment variable ANSIBLE_VAULT_PASSWORD_FILE=pass.txt for encryption/decryption.
Your question and example is focusing on the ansible.builtin.unvault lookup which is abolutely not needed in your situation. The only case I can think of where this would be needed is if you fetch your vault password from an other system/app/source while running your playbook. But since it is available with classic env vars to ansible, you just have to use the encrypted file which will be decrypted on the fly.
For the rest of the example, let's imagine your vault.yml file contains (decrypted):
my_login: vip
my_pass: v3rys3cr3t
some_other_key: toto
Using the above encrypted file is as easy as
---
- hosts: localhost
gather_facts: false
vars_files:
- vault.yml
tasks:
- name: Dummy use of login and pass
ansible.builtin.debug:
msg: "Login in {{ my_login }} and password is {{ my_pass }}"
Now if you want to easily load that file with all its content, change a value for a key in the contained dict and push back the content encrypted with the same configured password, here is a first draft that you will probably have to enhanced. But it worked for my local test with your current configuration.
The update_vault.yml playbook
---
- hosts: localhost
gather_facts: false
vars:
vault_file: vault.yml
new_pass: n3ws3cr3t
tasks:
- name: Import vaulted variables in a namespace (for further easier manipulation)
ansible.builtin.include_vars:
file: "{{ vault_file }}"
name: my_vault
- name: Dummy task just to show above worked
debug:
msg:
- Login is {{ my_vault.my_login }}.
- Password is {{ my_vault.my_pass }}
- Some other key is {{ my_vault.some_other_key }}
- name: Update an element and push back to encrypted file
vars:
new_vault_content: "{{ my_vault | combine({'my_pass': new_pass}) }}"
vault_pass_file: "{{ lookup('ansible.builtin.env', 'ANSIBLE_VAULT_PASSWORD_FILE') }}"
vault_pass: "{{ lookup('ansible.builtin.file', vault_pass_file) }}"
copy:
content: "{{ new_vault_content | to_nice_yaml | ansible.builtin.vault(vault_pass) }}"
dest: "{{ vault_file }}"
decrypt: false
gives:
$ ansible-playbook update_vault.yml
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Import vaulted variables in a namespace (for furthre easier manipulation)] *******************************************************************************************************************************************************
ok: [localhost]
TASK [Dummy task just to show above worked] ********************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"Login is vip.",
"Password is v3rys3cr3t",
"Some other key is toto"
]
}
TASK [Update an element and push back to encrypted file] *******************************************************************************************************************************************************************************
changed: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
And you can easilly check the file was correctly updated:
$ ansible-vault view vault.yml
my_login: vip
my_pass: n3ws3cr3t
some_other_key: toto
Note that the above playbook is not idempotent. If you run it a second time, the decrypted content of your file will stay identical (with the same new password), but the file will still be changed as the vault salt will change and the encrypted content will be different.

What is the best way to dynamically add variables as Ansible Facts?

I have an Ansible task where I navigate to a YAML variable file in GitHub, download the file, and add the variables as Ansible Facts where they're later used.
My YAML file looks like:
---
foo: bar
hello: world
I have a method where I loop over this file, and individually add the key/value pairs as the facts:
- name: Grab contents of variable file
win_shell: cat '{{ playbook_dir }}/DEV1.yml'
register: raw_config
- name: Add variables to workspace
vars:
config: "{{ raw_config.stdout | from_yaml }}"
set_fact:
"{{ item.key }}": "{{ item.value }}"
loop: "{{ config | dict2items }}"
This works but generates much larger log outputs that look like:
ok: [localhost] => (item={u'key': u'foo', u'value': u'bar'}) => {
"ansible_facts": {
"foo": "bar"
},
"ansible_loop_var": "item",
"changed": false,
"item": {
"key": "foo",
"value": "bar"
}
}
ok: [localhost] => (item={u'key': u'hello', u'value': u'world'}) => {
"ansible_facts": {
"hello": "world"
},
"ansible_loop_var": "item",
"changed": false,
"item": {
"key": "hello",
"value": "world"
}
}
I was wondering if it was possible to add the entire variable file as Ansible Facts instead of needing to loop through it. The way I tried was like:
- name: Grab contents of variable file
win_shell: cat '{{ playbook_dir }}/DEV1.yml'
register: raw_config
- name: Add variables to workspace
vars:
config: '{{ raw_config.stdout | from_yaml }}'
set_fact: '{{ config }}'
This almost works, but it looks like this:
ok: [msf1vpom04d.corp.tjxcorp.net] => {
"ansible_facts": {
"_raw_params": {
"foo": "bar",
"hello": "world"
…
Can I add the entire object as Ansible Facts without generating this _raw_params object?
... where I navigate to a YAML variable file in GitHub, download the file, and add the variables ... I was wondering if it was possible to add the entire variable file ...
There are several possibilities.
One option (annot.: like in Ansible Tower) can be to checkout, download, sync the variable file before executing the playbook. To do so
curl --silent --user "${ACCOUNT}:${PASSWORD}" -X GET "https://${REPOSITORY_URL}/raw/group_vars/test?at=refs%2Fheads%2Fmaster" -o group_vars/test && \
sshpass -p ${PASSWORD} ansible-playbook --user ${ACCOUNT} --ask-pass test.yml
... used a Bitbucket URL here for demonstration and test
The advantage of this approach is that there is no implementation or logic within the playbooks necessary at all. The only requirement is just to Organize host and group variables. Furthermore it is the there recommended approach
Keeping your inventory file and variables in a git repo (or other version control) is an excellent way to track changes to your inventory and host variables.
An other option would be to use include_vars module to
Loads YAML/JSON variables dynamically from a file or directory, recursively, during task runtime.
In respect to simplicity it is still recommended to sync before execute.
Further Q&A
... and as already mentioned within the comments
Getting variable values from URL
You can take advantage of the play level vars_files parameter and the fact that it will be loaded and expanded for each running task. We just need to have a fallback file when your file does not yet locally exist so that we don't get an error (during facts gathering for example).
Here is an example with a uri of mine containing default values for an ansible role.
First, create an empty.yml file which will be empty as its name suggest. (It's adjacent to my playbook for the example but you can put it wherever your want, just reflect this in your playbook accordingly)
Then the following playbook:
---
- hosts: localhost
gather_facts: false
vars:
external_vars_uri: https://raw.githubusercontent.com/ansible-ThoTeam/nexus3-oss/main/defaults/main.yml
external_vars_file: /tmp/external_vars.yml
vars_files:
- "{{ lookup('first_found', [external_vars_file, 'empty.yml']) }}"
tasks:
- name: make sure we have our external file
get_url:
url: "{{ external_vars_uri }}"
dest: "{{ external_vars_file }}"
# Note: we're only using localhost here so the below
# parameters are useless. But they will be necessary
# if you target other (groups of) hosts.
run_once: true
delegate_to: localhost
- name: debug a var we know is in the external file
debug:
var: nexus_repos_maven_proxy
Gives:
$ ansible-playbook play.yml
PLAY [localhost] **************************************************************************************************************************************************************************************************
TASK [make sure we have our external file] ************************************************************************************************************************************************************************
changed: [localhost]
TASK [debug a var we know is in the external file] ****************************************************************************************************************************************************************************
ok: [localhost] => {
"nexus_repos_maven_proxy": [
{
"layout_policy": "permissive",
"name": "central",
"remote_url": "https://repo1.maven.org/maven2/"
},
{
"name": "jboss",
"remote_url": "https://repository.jboss.org/nexus/content/groups/public-jboss/"
}
]
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Create password and write it to file

The idea is to generate user accounts over an API. Using default variables as the basic information:
---
students:
- Username: testuser1
E-Mail: student1#student.com
- Username: testuser2
E-Mail: student2#student.com
The Creating User role will then create all users with the API:
- name: "Creating User"
uri:
url: "https://URL/api/v1/users"
method: POST
headers:
Content-Type: application/json
Authorization: AUTH TOKEN
body:
name: "{{ item['Username'] }}"
email: "{{ item['E-Mail'] }}"
password: "{{ item['Password'] }}"
body_format: json
validate_certs: no
loop: "{{ students }}"
I can not find a way to generate a password for each user and write them to a file. Is there a way I can append a Password variable to each student item before the Creating User role? If so I could just write the default variable to a file as a last role.
I've played with the password module. I do not want to have 100+ files with the passwords of the users. I need to have a singe file at the end with all information.
I'm not 100% sure I understood your question.
The below will take your actual user list, create a new one with a generated password for each and store the result in a single file for all users. Bonus: if the file exists, the var will be initialized from there bypassing the password creation.
Notes:
The below can be enhanced. You can put the block tasks for file creation in a separate file and use a conditional include so that the skipped loop iteration do not take place at all when the file already exists.
I obviously did not take security into account here and I strongly suggest you secure the way your file is stored.
Demo playbook:
---
- name: Create random passwords and store
hosts: localhost
gather_facts: false
vars:
users_with_pass_file: /tmp/test_pass.txt
students:
- Username: testuser1
E-Mail: student1#student.com
- Username: testuser2
E-Mail: student2#student.com
tasks:
- name: Try to load users and passwords from file if it exists
vars:
file_content: "{{ lookup('ansible.builtin.file', users_with_pass_file, errors='ignore') }}"
ansible.builtin.set_fact:
users_with_pass: "{{ file_content }}"
when:
- file_content | length > 0
# Unfortunately, there is no test 'is list' in jinja2...
- file_content is iterable
- file_content is not mapping
- file_content is not string
ignore_errors: true
changed_when: false
register: load_from_disk
- name: Create user list with passwords and store it if it does not exists (or is malformed...)
block:
- name: Create the list
vars:
user_password: "{{ lookup('ansible.builtin.password', '/dev/null', length=12) }}"
ansible.builtin.set_fact:
users_with_pass: "{{ users_with_pass | default([]) + [item | combine({'password': user_password})] }}"
loop: "{{ students }}"
- name: Store the result
ansible.builtin.copy:
dest: "{{ users_with_pass_file }}"
content: "{{ users_with_pass | to_json }}"
when: load_from_disk is skipped or load_from_disk is failed
- name: Show the result
ansible.builtin.debug:
var: users_with_pass
first run (with used ansible version):
$ ansible-playbook --version
ansible-playbook 2.10.1
config file = None
configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.6/dist-packages/ansible
executable location = /usr/local/bin/ansible-playbook
python version = 3.6.9 (default, Jul 17 2020, 12:50:27) [GCC 8.4.0]
$ ansible-playbook test.yml
PLAY [Create random passwords and store] ***********************************************************************************************************************************************************************************************
TASK [Try to load users and passwords from file if it exists] **************************************************************************************************************************************************************************
[WARNING]: Unable to find '/tmp/test_pass.txt' in expected paths (use -vvvvv to see paths)
skipping: [localhost]
TASK [Create the list] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'Username': 'testuser1', 'E-Mail': 'student1#student.com'})
ok: [localhost] => (item={'Username': 'testuser2', 'E-Mail': 'student2#student.com'})
TASK [Store the result] ****************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [Show the result] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"users_with_pass": [
{
"E-Mail": "student1#student.com",
"Username": "testuser1",
"password": "5l1RG7HzqaKMWJcH:mRH"
},
{
"E-Mail": "student2#student.com",
"Username": "testuser2",
"password": "tZvLT3LVj3_60GV_WoQd"
}
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Second run:
PLAY [Create random passwords and store] ***********************************************************************************************************************************************************************************************
TASK [Try to load users and passwords from file if it exists] **************************************************************************************************************************************************************************
ok: [localhost]
TASK [Create the list] *****************************************************************************************************************************************************************************************************************
skipping: [localhost] => (item={'Username': 'testuser1', 'E-Mail': 'student1#student.com'})
skipping: [localhost] => (item={'Username': 'testuser2', 'E-Mail': 'student2#student.com'})
TASK [Store the result] ****************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [Show the result] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"users_with_pass": [
{
"E-Mail": "student1#student.com",
"Username": "testuser1",
"password": "5l1RG7HzqaKMWJcH:mRH"
},
{
"E-Mail": "student2#student.com",
"Username": "testuser2",
"password": "tZvLT3LVj3_60GV_WoQd"
}
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
If you have diceware installed, then you can generate the password:
- name: generate new password
delegate_to: localhost
command: diceware --no-caps -d ' ' -w en_eff
register: generatedpass
Then you can store in the file:
- name: store password
copy:
dest: '/path/to/password/file'
content: |
{{ generatedpass }}
If I understand your question correctly, this should do the trick.

How do I register a variable and persist it between plays targeted on different nodes?

I have an Ansible playbook, where I would like a variable I register in a first play targeted on one node to be available in a second play, targeted on another node.
Here is the playbook I am using:
---
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: main
gather_facts: no
tasks:
- debug:
msg: {{ foo.stdout }}
But, when I try to access the variable in the second play, targeted on main, I get this message:
The task includes an option with an undefined variable. The error was: 'foo' is undefined
How can I access foo, registered on localhost, from main?
The problem you're running into is that you're trying to reference facts/variables of one host from those of another host.
You need to keep in mind that in Ansible, the variable foo assigned to the host localhost is distinct from the variable foo assigned to the host main or any other host.
If you want to access one hosts facts/variables from another host then you need to explicitly reference it via the hostvars variable. There's a bit more of a discussion on this in this question.
Suppose you have a playbook like this:
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: localhost
gather_facts: no
tasks:
- debug:
var: foo
This will work because you're referencing the host localhost and localhosts's instance of the variable foo in both plays.
The output of this playbook is something like this:
PLAY [localhost] **************************************************
TASK: [command] ***************************************************
changed: [localhost]
PLAY [localhost] **************************************************
TASK: [debug] *****************************************************
ok: [localhost] => {
"var": {
"foo": {
"changed": true,
"cmd": [
"echo",
"hello world"
],
"delta": "0:00:00.004585",
"end": "2015-11-24 20:49:27.462609",
"invocation": {
"module_args": "echo \"hello world\",
"module_complex_args": {},
"module_name": "command"
},
"rc": 0,
"start": "2015-11-24 20:49:27.458024",
"stderr": "",
"stdout": "hello world",
"stdout_lines": [
"hello world"
],
"warnings": []
}
}
}
If you modify this playbook slightly to run the first play on one host and the second play on a different host, you'll get the error that you encountered.
Solution
The solution is to use Ansible's built-in hostvars variable to have the second host explicitly reference the first hosts variable.
So modify the first example like this:
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: main
gather_facts: no
tasks:
- debug:
var: foo
when: foo is defined
- debug:
var: hostvars['localhost']['foo']
## alternatively, you can use:
# var: hostvars.localhost.foo
when: hostvars['localhost']['foo'] is defined
The output of this playbook shows that the first task is skipped because foo is not defined by the host main.
But the second task succeeds because it's explicitly referencing localhosts's instance of the variable foo:
TASK: [debug] *************************************************
skipping: [main]
TASK: [debug] *************************************************
ok: [main] => {
"var": {
"hostvars['localhost']['foo']": {
"changed": true,
"cmd": [
"echo",
"hello world"
],
"delta": "0:00:00.005950",
"end": "2015-11-24 20:54:04.319147",
"invocation": {
"module_args": "echo \"hello world\"",
"module_complex_args": {},
"module_name": "command"
},
"rc": 0,
"start": "2015-11-24 20:54:04.313197",
"stderr": "",
"stdout": "hello world",
"stdout_lines": [
"hello world"
],
"warnings": []
}
}
}
So, in a nutshell, you want to modify the variable references in your main playbook to reference the localhost variables in this manner:
{{ hostvars['localhost']['foo'] }}
{# alternatively, you can use: #}
{{ hostvars.localhost.foo }}
Use a dummy host and its variables
For example, to pass a Kubernetes token and hash from the master to the workers.
On master
- name: "Cluster token"
shell: kubeadm token list | cut -d ' ' -f1 | sed -n '2p'
register: K8S_TOKEN
- name: "CA Hash"
shell: openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
register: K8S_MASTER_CA_HASH
- name: "Add K8S Token and Hash to dummy host"
add_host:
name: "K8S_TOKEN_HOLDER"
token: "{{ K8S_TOKEN.stdout }}"
hash: "{{ K8S_MASTER_CA_HASH.stdout }}"
- name:
debug:
msg: "[Master] K8S_TOKEN_HOLDER K8S token is {{ hostvars['K8S_TOKEN_HOLDER']['token'] }}"
- name:
debug:
msg: "[Master] K8S_TOKEN_HOLDER K8S Hash is {{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}"
On worker
- name:
debug:
msg: "[Worker] K8S_TOKEN_HOLDER K8S token is {{ hostvars['K8S_TOKEN_HOLDER']['token'] }}"
- name:
debug:
msg: "[Worker] K8S_TOKEN_HOLDER K8S Hash is {{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}"
- name: "Kubeadmn join"
shell: >
kubeadm join --token={{ hostvars['K8S_TOKEN_HOLDER']['token'] }}
--discovery-token-ca-cert-hash sha256:{{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}
{{K8S_MASTER_NODE_IP}}:{{K8S_API_SERCURE_PORT}}
I have had similar issues with even the same host, but across different plays. The thing to remember is that facts, not variables, are the persistent things across plays. Here is how I get around the problem.
#!/usr/local/bin/ansible-playbook --inventory=./inventories/ec2.py
---
- name: "TearDown Infrastructure !!!!!!!"
hosts: localhost
gather_facts: no
vars:
aws_state: absent
vars_prompt:
- name: "aws_region"
prompt: "Enter AWS Region:"
default: 'eu-west-2'
tasks:
- name: Make vars persistant
set_fact:
aws_region: "{{aws_region}}"
aws_state: "{{aws_state}}"
- name: "TearDown Infrastructure hosts !!!!!!!"
hosts: monitoring.ec2
connection: local
gather_facts: no
tasks:
- name: set the facts per host
set_fact:
aws_region: "{{hostvars['localhost']['aws_region']}}"
aws_state: "{{hostvars['localhost']['aws_state']}}"
- debug:
msg="state {{aws_state}} region {{aws_region}} id {{ ec2_id }} "
- name: last few bits
hosts: localhost
gather_facts: no
tasks:
- debug:
msg="state {{aws_state}} region {{aws_region}} "
results in
Enter AWS Region: [eu-west-2]:
PLAY [TearDown Infrastructure !!!!!!!] ***************************************************************************************************************************************************************************************************
TASK [Make vars persistant] **************************************************************************************************************************************************************************************************************
ok: [localhost]
PLAY [TearDown Infrastructure hosts !!!!!!!] *********************************************************************************************************************************************************************************************
TASK [set the facts per host] ************************************************************************************************************************************************************************************************************
ok: [XXXXXXXXXXXXXXXXX]
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [XXXXXXXXXXX] => {
"changed": false,
"msg": "state absent region eu-west-2 id i-0XXXXX1 "
}
PLAY [last few bits] *********************************************************************************************************************************************************************************************************************
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "state absent region eu-west-2 "
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
XXXXXXXXXXXXX : ok=2 changed=0 unreachable=0 failed=0
localhost : ok=2 changed=0 unreachable=0 failed=0
You can use an Ansible known behaviour. That is using group_vars folder to load some variables at your playbook. This is intended to be used together with inventory groups, but it is still a reference to the global variable declaration. If you put a file or folder in there with the same name as the group, you want some variable to be present, Ansible will make sure it happens!
As for example, let's create a file called all and put a timestamp variable there. Then, whenever you need, you can call that variable, which will be available to every host declared on any play inside your playbook.
I usually do this to update a timestamp once at the first play and use the value to write files and folders using the same timestamp.
I'm using lineinfile module to change the line starting with timestamp :
Check if it fits for your purpose.
On your group_vars/all
timestamp: t26032021165953
On the playbook, in the first play:
hosts: localhost
gather_facts: no
- name: Set timestamp on group_vars
lineinfile:
path: "{{ playbook_dir }}/group_vars/all"
insertafter: EOF
regexp: '^timestamp:'
line: "timestamp: t{{ lookup('pipe','date +%d%m%Y%H%M%S') }}"
state: present
On the playbook, in the second play:
hosts: any_hosts
gather_facts: no
tasks:
- name: Check if timestamp is there
debug:
msg: "{{ timestamp }}"

Add object to a dictionary in variables

I'd like to add objects to a list in variables like this
system_user:
- user1
system_users: "{{ system_users | union(system_user) }}"
It fails with a recursion error:
AnsibleError: recursive loop detected in template string
Is there any way to solve this? I want to create a definition file for each user in group_vars/all/ and then loop through them in a playbook. I don't want to redefine the list for every new user.
PS: There's a workaround: create variables with user names, like system_user_otto20 but it's not elegant at all.
There is a similar opened issue: https://github.com/ansible/ansible/issues/17906
I suggest you not to use undefined variables in template strings to define them.
As another workaround you could use hash_behaviour=merge with following definitions:
group_vars/all.yml
system_users:
user1:
user2:
book1.yml
- hosts: localhost
gather_facts: false
vars:
system_users:
user3:
user4:
tasks:
- debug: msg="{{ system_users | unique }}"
Running a playbook:
$ ansible-playbook book1.yml
[WARNING]: Host file not found: /etc/ansible/hosts
[WARNING]: provided hosts list is empty, only localhost is available
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": [
"user4",
"user2",
"user3",
"user1"
]
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
unique is used to convert dictionary to unsorted list.

Resources