How to write a file's content in variable in Ansible - ansible

I have written Ansible code where I am generating keys. The Script generates a private key file.
- name: Generating Public and Private Key
local_action:
module: command
cmd: './Auth-PUB-PVT-keytool.sh -privK {{OUTPUT_FOLDER}}/keys/{{PVT_KEY_NAME}}.key'
become: yes
become_user: "{{HOST_USER}}"
# run_once: True
no_log: "false"
Now I want to write the key data into an Ansible variable. For example: I have the file test.key with below content
jsbciusgdcxjasbciuygwndichsiuzgxciukjsdgniugziuduwyfmygxynYUXGNiusgzbuxtsaiuxdniufgdbyxfaiysrbcuyiacfxuyibstycfbxuybuyxtduyntzicytnyudn
Now I want that in my Ansible variable "MY_KEY_VALUE" the content of key file will be assigned i.e.
MY_KEY_VALUE: "jsbciusgdcxjasbciuygwndichsiuzgxciukjsdgniugziuduwyfmygxynYUXGNiusgzbuxtsaiuxdniufgdbyxfaiysrbcuyiacfxuyibstycfbxuybuyxtduyntzicytnyudn"
How to do it? Thanks in advance.

Probably the best approach for files on the Control Node would be using lookup plugins. See Ansible: Set variable to file content or How to store the contents of the file to a variable in Ansible?.
Another approach can be be to use the slurp module – Slurps a file from remote nodes.
For a file
~/test$ cat test.key
VALUE
a minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Slurp var from file
# delegate_to: localhost # if necessary
slurp:
src: test.key
register: MY_KEY
- name: Show var
debug:
msg: "{{ MY_KEY['content'] | b64decode }}"
results into an output of
TASK [Slurp var from file] ******
ok: [localhost]
TASK [Show var] ******
ok: [localhost] =>
msg: VALUE
If the data structure of your file test.key is already YAML, you could just read it in via include_vars module – Load variables from files, dynamically within a task.
For a file
~/test$ cat test.key
MY_KEY: "VALUE"
a minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Read var file
# delegate_to: localhost # if necessary
include_vars:
file: test.key
name: stuff
- name: Show var
debug:
var: stuff
will result into an output of
TASK [Read var file] ******
ok: [localhost]
TASK [Show var] ***********
ok: [localhost] =>
stuff:
MY_KEY: VALUE

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.

Ansible How to store multiple "register" in one file with one playbook

Ansible store only the first output in a file
Example
I have 3 hosts inside the inventory
My playbook ask for memory info.
with
- name: Check memory
hosts: all
tasks:
- name: Check Memory
shell: free
register: memory_output
- name: save
lineinfile:
path: "mypc/test.log"
line: "--{{ memory_output.stdout }}% "
create: yes
delegate_to: localhost
output write in file sometimes all the hosts memory,sometimes only the first,sometimes only the last
How i append every result from every hosts in one file.
Sometimes it export all the results but not every time
For example, given the inventory
shell> cat hosts
test_11
test_12
test_13
declare the below variable and put it into the vars
vmstat: "{{ out.stdout|community.general.jc('vmstat') }}"
Get the free memory
- command: vmstat
register: out
- set_fact:
free_mem: "{{ vmstat.1.free_mem }}"
- debug:
var: free_mem
gives (abridged)
ok: [test_11] =>
free_mem: '3434124'
ok: [test_12] =>
free_mem: '3496908'
ok: [test_13] =>
free_mem: '3434992'
Q: "How to store multiple 'register' in one file with one playbook."
A: Write it to the log
- lineinfile:
create: true
path: /tmp/test.log
line: >-
{{ '%Y-%m-%d %H:%M:%S'|strftime() }}
{{ item }}
{{ hostvars[item].free_mem }}
loop: "{{ ansible_play_hosts }}"
delegate_to: localhost
run_once: true
gives
shell> cat /tmp/test.log
2022-09-12 13:39:48 test_11 3434124
2022-09-12 13:39:49 test_12 3496908
2022-09-12 13:39:49 test_13 3434992
Example of a complete playbook for testing
- hosts: test_11,test_12,test_13
vars:
vmstat: "{{ out.stdout|community.general.jc('vmstat') }}"
tasks:
- command: vmstat
register: out
- set_fact:
free_mem: "{{ vmstat.1.free_mem }}"
- debug:
var: free_mem
- lineinfile:
create: true
path: /tmp/test.log
line: >-
{{ '%Y-%m-%d %H:%M:%S'|strftime() }}
{{ item }}
{{ hostvars[item].free_mem }}
loop: "{{ ansible_play_hosts }}"
delegate_to: localhost
run_once: true
Here's the simplest example. Assuming that you are running ansible in controller machine and you have to append the output of executing tasks in remote machines. The host list will obviously be different for you and will have all the remote machines.
- hosts: localhost
tasks:
## Playbook to copy the file from controller machine to remote machine
- name: Copy the file from controller machine to remote machine
copy:
src: /tmp/tmpdir/output.txt
dest: /tmp/tmpdir/output.txt
## Playbook to store the shell output to a variable
- name: Store the output of the shell command to a variable
shell: "echo '\nHello World'"
register: output
- name: Print the output of the shell command
debug:
msg: "{{ output.stdout }}"
## Playbook to append output to a file
- name: Append output to a file
lineinfile:
path: /tmp/tmpdir/output.txt
line: "{{ output.stdout }}"
create: yes
## Playbook to copy the file from remote machine to controller machine
- name: Copy the file from remote machine to controller machine
fetch:
src: /tmp/tmpdir/output.txt
dest: /tmp/tmpdir/output.txt
flat: yes
After running it the third time
╰─ ansible-playbook test.yaml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************
ok: [localhost]
TASK [Copy the file from controller machine to remote machine] *********************************************************************************************
ok: [localhost]
TASK [Store the output of the shell command to a variable] *************************************************************************************************
changed: [localhost]
TASK [Print the output of the shell command] ***************************************************************************************************************
ok: [localhost] => {
"msg": "\nHello World"
}
TASK [Append output to a file] *****************************************************************************************************************************
changed: [localhost]
TASK [Copy the file from remote machine to controller machine] *********************************************************************************************
ok: [localhost]
PLAY RECAP *************************************************************************************************************************************************
localhost : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
╰─ cat output.txt
Hello World
Hello World
Hello World
So on every machine you run the controller machine will get the latest file output. We will copy the file to remote, add contents to the file and then copy it back to controller. Continue the same until all the hosts have been completed.
If you want to take the result from selective servers then the last task can be replaced by following. Replace the hostnames with required values
## Playbook to copy the file from remote machine to controller machine if the hostname maches localhost1 or localhost2
- name: Copy the file from remote machine to controller machine if the hostname maches localhost1 or localhost2
fetch:
src: /tmp/tmpdir/output.txt
dest: /tmp/tmpdir/output.txt
flat: yes
fail_on_missing: yes
when: inventory_hostname == 'localhost1' or inventory_hostname == 'localhost2'

How to pass additional environment variables to the imported ansible playbook

I have a main_play.yml Ansible playbook in which I am importing a reusable playbook a.yml.
main_play.yml
- import_playbook: "reusable_playbooks/a.yml"
a.yml
---
- name: my_playbook
hosts: "{{ HOSTS }}"
force_handlers: true
gather_facts: false
environment:
APP_DEFAULT_PORT: "{{ APP_DEFAULT_PORT }}"
tasks:
- name: Print Msg
debug:
msg: "hello"
My question is: how can I pass an additional environment variable from my main_playbook.yml playbook to my re-usable playbook a.yml (if needed) so that the environment variables become like
environment:
APP_DEFAULT_PORT: "{{ APP_DEFAULT_PORT }}"
SPRING_PROFILE: "{{ SPRING_PROFILE }}"
import_playbook is not really a module but a core feature. It does not allow for any parameter to be passed to the imported playbook. You can see this keyword as a simple commodity to facilitate playing several playbooks in a row exactly as if they were defined in the same file.
So your problem comes down to:
How do I pass additional environment variables to a play ?
Here is one solution with illustrations to use it with extra_vars or setting a fact from a previous play. This far from being exhaustive but I hope it will guide you to you own best solution.
To ease readability:
I used the APP_ prefix for all environment variables in my below examples and filtered only on those for the results.
I truncated the playbook output to the only relevant debug task
We can define the following reusable.yml playbook containing a single play
---
- hosts: localhost
gather_facts: false
vars:
default_env:
APP_DEFAULT_PORT: "{{ APP_DEFAULT_PORT | d(8080) }}"
environment: "{{ default_env | combine(additionnal_env | d({})) }}"
tasks:
- name: get the output on env for APP_* vars
shell: env | grep -i app_
register: env_cmd
changed_when: false
- name: debug the output of env
debug:
var: env_cmd.stdout_lines
We can directly run this playbook as-is which will give
$ ansible-playbook reusable.yml
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_DEFAULT_PORT=8080"
]
}
We can override the default port with
$ ansible-playbook reusable.yml -e APP_DEFAULT_PORT=1234
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_DEFAULT_PORT=1234"
]
}
We can pass additional environment variables with:
$ ansible-playbook reusable.yml -e '{"additionnal_env":{"APP_SPRING_PROFILE": "/toto/pipo"}}'
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_SPRING_PROFILE=/toto/pipo",
"APP_DEFAULT_PORT=8080"
]
}
Now if we want to do this from a parent playbook, we can set the needed variable for the given host in a previous play. We can define a parent.yml playbook:
---
- hosts: localhost
gather_facts: false
tasks:
- name: define additionnal env vars for this host to be used in next play(s)
set_fact:
additionnal_env:
APP_WHATEVER: some_value
APP_VERY_IMPORTANT: "ho yes!"
- import_playbook: reusable.yml
which will give:
$ ansible-playbook parent.yml
[... truncated ...]
TASK [define additionnal env vars for this host to be used in next play(s)] ************************************************************************************************************************
ok: [localhost]
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_WHATEVER=some_value",
"APP_VERY_IMPORTANT=ho yes!",
"APP_DEFAULT_PORT=8080"
]
}

Ansible Automate configuration by iterating over files and assigning variables for each host

I need some help here in determining the best way to do this and how to setup my playbook to appropriately pull variables for each node.
Lets say I have 3 hosts
/etc/ansible/hosts:
host1
host2
host3
I have a variable file with multiple entries in it
vars/IPs.yaml:
---
IP: ['192.168.77.35', '192.167.77.36', '192.168.77.37']
I am running this playbook:
network_change.yaml:
---
- hosts: all
vars_files:
- vars/IPs.yaml
tasks:
- name: Check 10G Interface
stat:
path: /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1
register: teng
- name: Change 10G Interface Settings
lineinfile:
path: /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1
regexp: '{{item.From}}'
line: '{{item.To}}'
when: teng.stat.exists
with_items:
- { From: 'IPADDR=', To: 'IPADDR={{IP}}'}
I have this working for a single host just fine...but when I have multiple hosts I'm not sure how to loop through the IPs.yaml variables and pull the next value the next time the loop runs. Is there also a way for me to not use a dictionary .yaml variable, can I just use a raw text file that has the IPs on newlines?
Essentially I want to loop through and have the hosts show this in each of the respective hosts /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1.
host1 = 'IPADDR=192.168.77.35'
host2 = 'IPADDR=192.168.77.36'
host3 = 'IPADDR=192.168.77.37'
The end game is to be able to do this over 100+ hosts with simple text files, rather than dictionary yaml files and will be including multiple variables, etc. The systems will all be able to be hit via dhcp/hostnames/IP.
Create a dictionary with the data, e.g.
- set_fact:
_dict: '{{ dict(ansible_play_hosts|zip(IP)) }}'
run_once: true
should give
_dict:
host1: 192.168.77.35
host2: 192.167.77.36
host3: 192.168.77.37
Then use this dictionary and select the IP, e.g.
- name: Change 10G Interface Settings
lineinfile:
path: /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1
regexp: 'IPADDR='
line: 'IPADDR={{ _dict[inventory_hostname] }}'
when: teng.stat.exists
You might want to test it first, e.g.
- name: Change 10G Interface Settings
debug:
msg: 'IPADDR={{ _dict[inventory_hostname] }}'
should give
ok: [host2] =>
msg: IPADDR=192.167.77.36
ok: [host1] =>
msg: IPADDR=192.168.77.35
ok: [host3] =>
msg: IPADDR=192.168.77.37
The next option, instead of creating the dictionary, is to calculate the index in the list, e.g. should give the same result
- name: Change 10G Interface Settings
debug:
msg: 'IPADDR={{ IP[_index|int] }}'
vars:
_index: '{{ ansible_play_hosts.index(inventory_hostname) }}'
Q: "Use a raw text file that has the IPs on newlines"
A: Create the file, e.g.
shell> cat IP.txt
192.168.77.35
192.167.77.36
192.168.77.37
Create the list on the fly, e.g.
- set_fact:
IP: "{{ lookup('file', 'IP.txt').split('\n') }}"
run_once: true
should give
IP:
- 192.168.77.35
- 192.167.77.36
- 192.168.77.37
The more robust solution would be to put the hashes into the file, e.g.
shell> cat IP.txt
host1: 192.168.77.35
host2: 192.167.77.36
host3: 192.168.77.37
Then the task
- include_vars:
file: IP.txt
name: _dict
should create the dictionary
_dict:
host1: 192.168.77.35
host2: 192.167.77.36
host3: 192.168.77.37
But, the most simple solution would be to store this dictionary in IP.yml and put it into the directory group_vars/all.

ansible vars_files and extra_vars to read input

looking to pass the dict to read a set of key value pairs based on the location. When values are hardcoded to the playbook, it works fine but calling through extra_vars giving an error message. Not sure even if it supports. appreciate, your thoughts and inputs.
ansible-playbook play3.yml -e '{"var1":"loc2"}' -vv
play3.yml
---
- name: testing
hosts: localhost
connection: local
gather_facts: no
vars_files:
- var_file.yml
tasks:
- debug:
msg: "{{ var1['first'] }}"
var_file.yml
---
loc1:
first: name1
last: name2
loc2:
first: python
last: perl
...
"Anything's possible in an animated cartoon." -Bugs Bunny
This playook:
---
- name: testing
hosts: localhost
connection: local
gather_facts: no
vars_files:
- var_file.yml
tasks:
- debug:
var: "{{ item }}.first"
with_items: "{{ var1 }}"
Gave me this output:
TASK [debug] **********************************************************************************************************************************
task path: /home/jack/Ansible/CANES/PLAYBOOKS/play3.yml:9
ok: [localhost] => (item=None) => {
"loc2.first": "python"
}

Resources