Ansible: any file from dictionary - ansible

Today I totally broke my brain with that. I have some structure with dictionary files and I need to make some text or json with variables which uses in dictionary:
> hosts_vars
varfile.yaml
> inventory
hosts.yml
tasks.yml
varfile.yaml
host_vars:
host1: { ip: 192.168.1.1 }
host2: { ip: 192.168.1.2 }
hosts.yml
all:
hosts:
host1:
ansible_host: 192.168.1.1
host2:
ansible_host: 192.168.1.2
tasks.yml
- hosts: all
become: no
gather_facts: false
vars_files:
- hosts_vars/varfile.yaml
vars:
temp_file: /tmp/out_file.txt
tasks:
- local_action:
module: ansible.builtin.lineinfile
path: '{{ temp_file }}'
line: |
- targets: ['{{ host_vars[inventory_hostname].ip }}']
state: present
When I run it: ansible-playbook tasks.yml -l all
I got:
~> cat /tmp/out_file.txt
- targets: ['192.168.1.1']
I didn't understand why it happened. When if I had 60 hosts I'll got ~54 lines in file. How it works?

You need to create the file in localhost and delegate the task to localhost looping hosts.
tasks:
- name: update host
lineinfile:
path: '{{ temp_file }}'
create: yes
line: |
- targets: ['{{ host_vars[item].ip }}'] # or ['{{ host_vars[item]['ip'] }}']
insertafter: EOF
delegate_to: localhost
run_once: true
with_inventory_hostnames:
- all

For example
tasks:
- ansible.builtin.lineinfile:
create: yes
path: "{{ temp_file }}"
line: "- targets: ['{{ host_vars[inventory_hostname].ip }}']"
delegate_to: localhost
gives
shell> cat /tmp/out_file.txt
- targets: ['192.168.1.2']
- targets: ['192.168.1.1']
Notes:
Don't use Yaml Literal Style block with line. It says what it is. You don't want any newlines \n in the line. If you for whatever reason have to use the block put dash - behind the pipe |. This will remove the trailing newline \n
- ansible.builtin.lineinfile:
create: true
path: "{{ temp_file }}"
line: |-
- targets: ['{{ host_vars[inventory_hostname].ip }}']
delegate_to: localhost
You can use delegate_to: localhost instead of local_action
Use create: yes. It is no by default and the task will fail Destination /tmp/out_file.txt does not exist ! if the file doesn't exist.
state: present is default
insertafter: EOF is default
Putting varfile.yaml into the directory host_vars is missing the point how host_vars work. There is no host with the name varfile. You should put the file, e.g. into vars/varfile.yaml instead.
The variable host_vars is redundant. The IP of the hosts is stored in the variable ansible_host. The task below gives the same result
- ansible.builtin.lineinfile:
create: true
path: "{{ temp_file }}"
line: "- targets: ['{{ ansible_host }}']"
delegate_to: localhost
If you'd like to create a list of all IP addresses the task below
- ansible.builtin.lineinfile:
create: true
path: "{{ temp_file }}"
line: "- targets: {{ ansible_play_hosts_all|
map('extract', hostvars, 'ansible_host') }}"
run_once: true
delegate_to: localhost
gives
shell> cat /tmp/out_file.txt
- targets: ['192.168.1.1', '192.168.1.2']

macOS with ansible [core 2.11.2] had issue with write files in /tmp If you want to use this you need to set the home folder or any other folder with root permissions.

Related

Create Local File With Ansible Template From Variables

I'm running an ansible playbook against a number of ec2 instances to check if a directory exists.
---
- hosts: all
become: true
tasks:
- name: Check if foo is installed
stat:
path:
/etc/foo
register: path
- debug: msg="{{path.stat.exists}}"
And I would like to generate a localfile that lists the private IP addresses of the ec2 instances and states whether or not the directory foo does exist.
I can get the private IP addresses of the instances with this task
- name: Get info from remote
shell: curl http://169.254.169.254/latest/meta-data/local-ipv4
register: bar
- debug: msg="{{bar.stdout}}"
How do I create a local file with content
IP address: 10.100.0.151 directory foo - false
IP address: 10.100.0.152 directory foo - true
I've tried adding a block for this as such
- hosts: localhost
become: false
vars:
installed: "{{bar.stdout}}"
status: "{{path.stat.exists}}"
local_file: "./Report.txt"
tasks:
- name: Create local file with info
copy:
dest: "{{ local_file }}"
content: |
"IP address {{ installed }} foo - {{ status }}"
But it doesn't look like I can read values of variables from earlier steps.
What am I doing wrong please?
A similar question has been answered here.
Basically what you want is to reference it through the host var variable.
This should work.
- hosts: localhost
become: false
vars:
local_file: "./Report.txt"
tasks:
- name: Create local file with info
lineinfile:
path: "{{ local_file }}"
line:
"IP Address: {{ hostvars[item]['bar'].stdout }} - Installed: {{ hostvars[item]['path'].stat.exists }}"
with_items: "{{ query('inventory_hostnames', 'all') }}"
And this should populate your local ./Report.txt file, with the info you need.
I've used the ec2_metadata_facts module to get the IP address us ingansible_ec2_local_ipv4
I've also created the directory /tmp/testdir on the second host.
- hosts: test_hosts
gather_facts: no
vars:
directory_name: /tmp/testdir
tasks:
- ec2_metadata_facts:
- name: check if directory '{{ directory_name }}' exsists
stat:
path: "{{ directory_name }}"
register: path
# I make the outputfile empty
# because the module lineinfile(as I know) can't overwrite a file
# but appends line to the old content
- name: create empty output file
copy:
content: ""
dest: outputfile
delegate_to: localhost
- name: write output to outputfile
lineinfile:
dest: outputfile
line: "IP Address: {{ ansible_ec2_local_ipv4 }} {{ directory_name }} - {{ path.stat.exists }}"
state: present
with_items: "{{ groups.all }}"
# with_items: "{{ ansible_play_hosts }}" can also be used here
delegate_to: localhost
The outputfile looks like:
IP Address: xxx.xx.x.133 /tmp/testdir - False
IP Address: xxx.xx.x.45 /tmp/testdir - True

ansible lineinfile replace a line begin containing a pattern with default ports in multiple files based on input array

---
-
gather_facts: true
hosts: abc,pqr,xyz
name: "playbook"
serial: 1
tasks:
-
block:
-
shell: "ls /etc/maria/" # could be n number of ports like 123, 456, 789
register: ports
-
lineinfile:
line: PORT={{ new_ports }}
path: "/etc/instance_{{ item }}/mariadb.conf"
regexp: '^PORT='
with_items: "{{ ports.stdout_lines }}"
new_ports: # length of this would be based on number of ports, should create an array based on array size of `ports`
- 8880
- 8881
- 8882
Say for example I've some 3 ports from ls /etc/maria/ 123456789 and 3 corresponding files /etc/instance_123/mariadb.conf,
/etc/instance_456/mariadb.conf,
/etc/instance_789/mariadb.conf.
could be n number of ports & files.
I would like to replace line starting with ^PORT= in /etc/instance_123/mariadb.conf to PORT=8880 and so on until the length of array ports.
Results getting with suggested code:
Before
/etc/instance_1234/mariadb.conf | grep port
port=1234
playbook
- hosts: abc
gather_facts: false
vars:
ansible_python_interpreter: /usr/bin/python3
serial: 1
tasks:
- shell: ls /etc/maria/
register: ports
- debug:
msg: "{{ ports.stdout_lines|length }}"
- lineinfile:
path: "/etc/instance_{{ item }}/mariadb.conf"
regexp: "^port=.*$"
line: "port={{ new_ports[ansible_loop.index0] }}"
loop: "{{ ports.stdout_lines }}"
loop_control:
extended: true
vars:
new_ports: "{{ range(8880, 8880 + ports.stdout_lines|length) }}"
After
/etc/instance_1234/mariadb.conf | grep port
port=x
For example, given the files
shell> ssh admin#test_11 ls /tmp/mongo
123
456
shell> ssh admin#test_12 ls /tmp/mongo
123
456
789
The playbook
- hosts: test_11,test_12
gather_facts: false
serial: 1
tasks:
- command: ls /tmp/mongo
register: ports
- lineinfile:
path: "/tmp/etc/instance_{{ item }}/mongodb.conf"
regexp: "^PORT=.*$"
line: "PORT={{ new_ports[ansible_loop.index0] }}"
create: true
loop: "{{ ports.stdout_lines }}"
loop_control:
extended: true
vars:
new_ports: "{{ range(8880, 8880 + ports.stdout_lines|length) }}"
creates, or updates the configuration files
shell> ssh admin#test_11 ls /tmp/etc/
instance_123
instance_456
shell> ssh admin#test_11 cat /tmp/etc/instance_123/mongodb.conf
PORT=8880
shell> ssh admin#test_11 cat /tmp/etc/instance_456/mongodb.conf
PORT=8881
shell> ssh admin#test_12 ls /tmp/etc/
instance_123
instance_456
instance_789
shell> ssh admin#test_12 cat /tmp/etc/instance_123/mongodb.conf
PORT=8880
shell> ssh admin#test_12 cat /tmp/etc/instance_456/mongodb.conf
PORT=8881
shell> ssh admin#test_12 cat /tmp/etc/instance_789/mongodb.conf
PORT=8882

How to change and use a global variable for each host in Ansible?

I am trying to write a playbook to setup mysql master-slave replication with multiple slave servers. For each slave server, I need access to a variable called next_id that should be incremented before use for each host. For example, for the first slave server, next_id should be 2 and for the second slave server it should be 3 and so on. What is the way to achieve this in Ansible?
This is the yaml file I use to run my role.
- name: Setup the environment
hosts: master , slave_hosts
serial: 1
roles:
- setup-master-slave
vars:
master_ip_address : "188.66.192.11"
slave_ip_list :
- "188.66.192.17"
- "188.66.192.22"
This is the yaml file where I use the variable.
- name: Replace conf file with template
template:
src: masterslave.j2
dest: /etc/mysql/mariadb.conf.d/50-server.cnf
when: inventory_hostname != 'master'
vars:
- ip_address : "{{ master_ip_address }}"
- server_id : "{{ next_id }}"
I can suggest a way which will work according to you requirement for any number of slave servers but it is not based on any any module but self conscience.
Here my master_ip_address is 10.x.x.x and input is any value of next_id you want to increment for every slave server
- hosts: master,slave1,slave2,slave3,slave4
serial: 1
gather_facts: no
tasks:
- shell: echo "{{ input }}" > yes.txt
delegate_to: localhost
when: inventory_hostname == '10.x.x.x'
- shell: cat yes.txt
register: var
delegate_to: localhost
when: inventory_hostname != '10.x.x.x'
- shell: echo "$(({{var.stdout}}+1))"
register: next_id
delegate_to: localhost
when: inventory_hostname != '10.x.x.x'
- shell: echo "{{ next_id.stdout }}" > yes.txt
delegate_to: localhost
when: inventory_hostname != '10.x.x.x'
- name: Replace conf file with template
template:
src: masterslave.j2
dest: 50-server.cnf
when: inventory_hostname != '10.x.x.x'
vars:
- ip_address : "{{ master_ip_address }}"
- server_id : "{{ next_id.stdout }}"
[ansibleserver#172 test_increment]$ cat masterslave.j2
- {{ ip_address }}
- {{ server_id }}
Now, if you run
ansible-playbook increment.yml -e 'master_ip_address=10.x.x.x input=1'
slave1 server
[root#slave1 ~]# cat 50-server.cnf
- 10.x.x.x
- 2
slave2 server
[root#slave2 ~]# cat 50-server.cnf
- 10.x.x.x
- 3
slave3 server
[root#slave3 ~]# cat 50-server.cnf
- 10.x.x.x
- 4
and so on
"serial" is available in a playbooks only and not working in roles
therefore, for fully automatic generation of server_id for MySQL in Ansible roles, you can use the following:
roles/my_role/defaults/main.yml
---
cluster_alias: test_cluster
mysql_server_id_config: "settings/mysql/{{ cluster_alias }}/server_id.ini"
roles/my_role/tasks/server-id.yml
---
- name: Ensures '{{ mysql_server_id_config | dirname }}' dir exists
file:
path: '{{ mysql_server_id_config | dirname }}'
state: directory
owner: root
group: root
mode: 0755
delegate_to: localhost
- name: Ensure mysql server id config file exists
copy:
content: "0"
dest: "{{ mysql_server_id_config }}"
force: no
owner: root
mode: '0755'
delegate_to: localhost
- name: server-id
include_tasks: server-id-tasks.yml
when: inventory_hostname == item
with_items: "{{ ansible_play_hosts }}"
tags:
- server-id
roles/my_role/tasks/server-id-tasks.yml
# tasks file
---
- name: get last server id
shell: >
cat "{{ mysql_server_id_config }}"
register: _last_mysql_server_id
check_mode: no
delegate_to: localhost
tags:
- server-id
- name: get new server id
shell: >
echo "$(({{_last_mysql_server_id.stdout}}+1))"
register: _new_mysql_server_id
check_mode: no
delegate_to: localhost
tags:
- server-id
- name: save new server id
shell: >
echo -n "{{ _new_mysql_server_id.stdout }}" > "{{ mysql_server_id_config }}"
check_mode: no
delegate_to: localhost
tags:
- server-id
- debug:
var: _new_mysql_server_id.stdout
tags:
- server-id
- name: Replace conf file with template
template:
src: server-id.j2
dest: server-id.cnf

Place file contents in email body in Ansible

I have a playbook that checks hard drive space on a group of servers, sends out an email, and it attaches a file containing the output contents. Is there a way to place the contents of the file in the body itself? I would like the file contents to be able to be seen at a glance in the email. The content would be the registered variable, result.
The current tasks:
---
- name: Check for output file
stat:
path: /tmp/ansible_output.txt
register: stat_result
delegate_to: localhost
- name: Create file if it does not exist
file:
path: /tmp/ansible_output.txt
state: touch
mode: '0666'
when: stat_result.stat.exists == False
delegate_to: localhost
- name: Check hard drive info
become: yes
become_user: root
shell: cat /etc/hostname; echo; df -h | egrep 'Filesystem|/dev/sd'
register: result
- debug: var=result.stdout_lines
- local_action: lineinfile line={{ result.stdout_lines | to_nice_json }} dest=/tmp/ansible_output.txt
- name: Email Result
mail:
host: some_email_host
port: some_port_number
username: my_username
password: my_password
to:
- first_email_address
- second_email_address
- third_email_address
from: some_email_address
subject: My Subject
body: <syntax for file contents in body here> <--- What is the syntax?
attach:
/tmp/ansible_output.txt
run_once: true
delegate_to: localhost
- name: Remove Output File
file:
path: /tmp/ansible_output.txt
state: absent
run_once: true
delegate_to: localhost
Edit: I tried
body: "{{ result.stdout_lines | to_nice_json }}"
but it only sends me the output of the first host in the group.
Ok, I figured it out. I created a directory, files, and sent the output to a file in that directory using the {{ role_path }} variable. In the body portion of the email task, I used the lookup plugin to grab the contents of the file.
Here is the updated playbook with the original lines commented out:
---
- name: Check for output file
stat:
#path: /tmp/ansible_output.txt
path: "{{ role_path }}/files/ansible_output.txt"
register: stat_result
delegate_to: localhost
- name: Create file if it does not exist
file:
#path: /tmp/ansible_output.txt
path: "{{ role_path }}/files/ansible_output.txt"
state: touch
mode: '0666'
when: stat_result.stat.exists == False
delegate_to: localhost
- name: Check hard drive info
become: yes
become_user: root
shell: cat /etc/hostname; echo; df -h | egrep 'Filesystem|/dev/sd'
register: result
- debug: var=result.stdout_lines
#- local_action: lineinfile line={{ result.stdout_lines | to_nice_json }} dest=/tmp/ansible_output.txt
- local_action: lineinfile line={{ result.stdout_lines | to_nice_json }} dest="{{ role_path }}/files/ansible_output.txt"
- name: Email Result
mail:
host: some_email_host
port: some_port_number
username: my_username
password: my_password
to:
- first_email
- second_email
- third_email
from: some_email_address
subject: Ansible Disk Space Check Result
#body: "{{ result.stdout_lines | to_nice_json }}"
body: "{{ lookup('file', '{{ role_path }}/files/ansible_output.txt') }}"
#attach:
#/tmp/ansible_output.txt
attach:
"{{ role_path }}/files/ansible_output.txt"
run_once: true
delegate_to: localhost
- name: Remove Output File
file:
#path: /tmp/ansible_output.txt
path: "{{ role_path }}/files/ansible_output.txt"
state: absent
run_once: true
delegate_to: localhost
Now, my email contains the attachment, as well as the contents in the body, and I didn't have to change much in the playbook. :-)

Concatenating files with Ansible

Using Ansible, how would I go about copying the contents of the example file:
/tmp/example.txt
cat example.txt
1234
And adding its contents to the below file:
/tmp/example2.txt
cat example2.txt
5678
So, in the end, example2.txt has the contents of:
12345678
The below task will accomplish this:
- hosts: localhost
connection: local
gather_facts: false
tasks:
- name: update example2.txt
lineinfile:
path: example2.txt
regexp: "^{{ lookup('file', 'example2.txt') }}"
line: "{{ lookup('file', 'example.txt') }}{{ lookup('file', 'example2.txt') }}"

Resources