Ansible lineinfile append - ansible

I have a hosts inventory file. That has
url.example.com hostnames='["hostname1.example.com", "hostname2.example.com"]'
Im trying to loop over it to add all the hostnames to /etc/hosts file
I am trying to use lineinfile, but how can I get it to append all of the hostnames to one specific line
line: "{{ ansible_default_ipv4.address }} {{ inventory_hostname }} {{ inventory_hostname_short}}" + append item here
with_items: "{{ hostnames }}"
state: present
How can i append all the items in the end of the line.

The iteration is not needed, I think. Is this what you're looking for?
line: "{{ ansible_default_ipv4.address }}
{{ inventory_hostname }}
{{ inventory_hostname_short}}
{{ hostnames|join(' ') }}"
For example, given the file
shell> cat hosts
10.1.0.27 localhost localhost
The playbook
shell> cat playbook.yml
- hosts: localhost
vars:
hostnames: [hostname1.example.com, hostname2.example.com]
tasks:
- lineinfile:
path: hosts
regex: '^{{ ansible_default_ipv4.address }}\s+(.*)$'
line: "{{ ansible_default_ipv4.address }}
{{ inventory_hostname }}
{{ inventory_hostname_short}}
{{ hostnames|join(' ') }}"
works as expected
shell> ansible-playbook playbook.yml -CD
TASK [lineinfile] ***************************************************************
--- before: hosts (content)
+++ after: hosts (content)
## -1 +1 ##
-10.1.0.27 localhost localhost
+10.1.0.27 localhost localhost hostname1.example.com hostname2.example.com
changed: [localhost]

Related

Looping over ansible_hosts from inventory

I have a bunch of root servers with different IP addresses. I'm trying to configure the ufw firewall on the server with MySQL server to only allow access from my servers (might change to webservers later) with Ansible. Initially, I only had the FQDNs in the inventory but added the ansible_host IPs because the firewall is not going to resolve the host names (makes sense).
Unfortunately I do not know how to access the ansible_host in the loop query("inventory_hostnames", "all")
My inventory:
all:
vars:
ansible_ssh_user: me
children:
sqlserver:
hosts:
sqlserver.my-domain.de:
ansible_ssh_user: mysql_me_user
ansible_host: 1.1.1.1
webserver:
hosts:
webserver1.my-domain.de:
ansible_host: 2.2.2.2
webserver2.my-domain.de:
ansible_host: 3.3.3.3
now I am trying to loop in the playbook:
- hosts: '{{target|default("sqlserver")}}'
roles:
- { name: oefenweb.ufw, become: yes } # needs root but does not define become by itself...
vars:
ufw_logging: true
ufw_rules:
- rule: allow
to_port: 22
protocol: tcp
tasks:
- name: open MySQL for servers in my
ufw:
rule: allow
to_port: 3306
protocol: tcp
from_ip: '{{ item }}'
loop: '{{ hosts|default(query("inventory_hostnames", "all")) }}'
tags: test
become: true
There are many options. Make your choice depending on the use case.
For example, the play below
shell> cat pb.yml
- hosts: '{{ target|default("sqlserver") }}'
tasks:
- debug:
msg: "{{ item }}: {{ ansible_host }}"
loop: "{{ clients|default(groups.webserver) }}"
vars:
ansible_host: "{{ hostvars[item].ansible_host }}"
gives
shell> ansible-playbook pb.yml
PLAY [sqlserver] *****************************************************************************
TASK [debug] *********************************************************************************
ok: [sqlserver.my-domain.de] => (item=webserver1.my-domain.de) =>
msg: 'webserver1.my-domain.de: 2.2.2.2'
ok: [sqlserver.my-domain.de] => (item=webserver2.my-domain.de) =>
msg: 'webserver2.my-domain.de: 3.3.3.3'
PLAY RECAP ***********************************************************************************
sqlserver.my-domain.de: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Example of the project for testing
shell> tree .
.
├── ansible.cfg
├── hosts
└── pb.yml
0 directories, 3 files
shell> cat ansible.cfg
[defaults]
gathering = explicit
inventory = $PWD/hosts
stdout_callback = yaml
shell> cat hosts
all:
vars:
ansible_ssh_user: me
children:
sqlserver:
hosts:
sqlserver.my-domain.de:
ansible_ssh_user: mysql_me_user
ansible_host: 1.1.1.1
webserver:
hosts:
webserver1.my-domain.de:
ansible_host: 2.2.2.2
webserver2.my-domain.de:
ansible_host: 3.3.3.3
shell> cat pb.yml
- hosts: '{{ target|default("sqlserver") }}'
tasks:
- debug:
msg: "{{ item }}: {{ ansible_host }}"
loop: "{{ clients|default(groups.webserver) }}"
vars:
ansible_host: "{{ hostvars[item].ansible_host }}"
Create the list and the dictionary below
clients: "{{ groups.webserver }}"
ah_list: "{{ clients|map('extract', hostvars, 'ansible_host')|list }}"
ah_dict: "{{ dict(clients|zip(ah_list)) }}"
give
ah_list:
- 2.2.2.2
- 3.3.3.3
ah_dict:
webserver1.my-domain.de: 2.2.2.2
webserver2.my-domain.de: 3.3.3.3
Then, all tasks below give the same results
2a)
- debug:
msg: "{{ item.key }}: {{ item.value }}"
with_dict: "{{ ah_dict }}"
2b)
- debug:
msg: "{{ item.0 }}: {{ item.1 }}"
with_together:
- "{{ clients }}"
- "{{ ah_list }}"
2c)
- debug:
msg: "{{ item }}: {{ ah_dict[item] }}"
loop: "{{ clients }}"
Example of a complete playbook for testing
- hosts: '{{ target|default("sqlserver") }}'
vars:
clients: "{{ groups.webserver }}"
ah_list: "{{ clients|map('extract', hostvars, 'ansible_host')|list }}"
ah_dict: "{{ dict(clients|zip(ah_list)) }}"
tasks:
- debug:
var: ah_list
- debug:
var: ah_dict
- debug:
msg: "{{ item.key }}: {{ item.value }}"
with_dict: "{{ ah_dict }}"
- debug:
msg: "{{ item.0 }}: {{ item.1 }}"
with_together:
- "{{ clients }}"
- "{{ ah_list }}"
- debug:
msg: "{{ item }}: {{ ah_dict[item] }}"
loop: "{{ clients }}"
As I was writting the question I understood that should google for accessing inventory property and found a solution in Accessing inventory host variable in Ansible playbook - use
from_ip: '{{ hostvars[item].ansible_host }}'
for my task.
I hope that this the way to go.

List name server from resolv.conf with hostname in one line per host

I need to get the DNS server(s) from my network, I tried using:
- hosts: localhost
gather_facts: no
tasks:
- name: check resolv.conf exists
stat:
path: /etc/resolv.conf
register: resolv_conf
- name: check nameservers list in resolv.conf
debug:
msg: "{{ contents }}"
vars:
contents: "{{ lookup('file', '/etc/resolv.conf') | regex_findall('\\s*nameserver\\s*(.*)') }}"
when: resolv_conf.stat.exists == True
But this does not quite gives the result I need.
Will it be possible to write a playbook in such a way that the result looks like the below?
hostname;dns1;dns2;dnsN
The declaration below gives the list of nameservers
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
You can join the hostname and the items on the list
msg: "{{ inventory_hostname }};{{ nameservers|join(';') }}"
Notes
Example of a complete playbook for testing
- hosts: localhost
vars:
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
tasks:
- debug:
var: nameservers
- debug:
msg: |
{{ inventory_hostname }};{{ nameservers|join(';') }}
The simplified declaration below works fine if there is no nameserver.* in the comments
nameservers: "{{ lookup('file', '/etc/resolv.conf')|
regex_findall('\\s*nameserver\\s*(.*)') }}"
Unfortunately, the Linux default file /etc/resolv.conf contains the comment:
| # run "systemd-resolve --status" to see details about the actual nameservers.
This regex will match nameservers.
nameservers:
- s.
You can solve this problem by putting at least one space behind the keyword nameserver.
regex_findall('\\s*nameserver\\s+(.*)') }}"
However, this won't help if there is the keyword nameserver in the comment.
Q: "No filter named 'split'"
A: There is no filter split in Ansible less than 2.11. Use regex_replace instead
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('regex_replace', '^(.*) (.*)$', '\\2')|list }}"
Since your regex_findall already creates you a list with all DNS servers, you just need to add the hostname to that list and join the whole list with a semicolon.
- name: check nameservers list in resolv.conf
debug:
msg: >-
{{
(
[ ansible_hostname ] +
lookup('file', '/etc/resolv.conf', errors='ignore')
| regex_findall('\s*nameserver\s*(.*)')
) | join(';')
}}
Which will result in something like (b176263884e6 being the actual hostname of a container):
TASK [check nameservers list in resolv.conf] *****************************
ok: [localhost] =>
msg: b176263884e6;1.1.1.1;4.4.4.4;8.8.8.8
Note that you don't even need the stat task, as you can ignore errors of the lookup with errors='ignore'.
This will, then, give you only the hostname, along with a warning:
TASK [check nameservers list in resolv.conf] *****************************
[WARNING]: Unable to find '/etc/resolv.conf' in expected paths
(use -vvvvv to see paths)
ok: [localhost] =>
msg: b176263884e6

Ansible lint : Found a bare variable

This is my ansible script.
- name: Validate that blacklisted URLs are unreachable
environment:
SSL_CERT_FILE: "{{ ssl_cert_path }}"
ansible.builtin.uri:
url: "{{ item }}"
timeout: 10
register: blacklisted_http_responses
with_lines: cat {{ role_path }}/files/blacklisted_urls.txt
And i am getting this lint error for the sbove code
Found a bare variable 'cat {{ role_path }}/files/blacklisted_urls.txt' used in a 'with_lines' loop.
any idea how to resolve this ? I tried Putting the variable name in double quotes.
What you see is very probably an ansible-lint issue. You should use loop instead of with_lines. There are no complaints by ansible-lint about the code below
loop: "{{ lookup('file',
role_path ~ '/files/blacklisted_urls.txt').splitlines() }}"
You can also use the pipe lookup plugin instead of the file if you want to. The loop below gives the same result
loop: "{{ lookup('pipe',
'cat ' ~ role_path ~ '/files/blacklisted_urls.txt').splitlines() }}"
For example, the playbook
shell> cat pb.yml
---
- hosts: localhost
roles:
- role_a
the role
shell> cat roles/role_a/tasks/main.yml
---
- name: Debug
debug:
var: item
loop: "{{ lookup('file',
role_path ~ '/files/blacklisted_urls.txt').splitlines() }}"
and the file
shell> cat roles/role_a/files/blacklisted_urls.txt
www1.example.com
www2.example.com
give (abridged)
TASK [role_a : Debug] ****************************************************
ok: [localhost] => (item=www1.example.com) =>
ansible_loop_var: item
item: www1.example.com
ok: [localhost] => (item=www2.example.com) =>
ansible_loop_var: item
item: www2.example.com

Ansible search sublists for value

A webhook triggers an AWX job and I want to run the deployment on a certain host depending on the service, since they run on different servers. I need to know which server uses that service to set is as a var so it can be used as a host in the following play.
My variable inside vars.yaml looks like this:
staging_hosts:
server1: ['service1', 'service2', 'service3']
server2: ['service4', 'service5', 'service6']
server3: ['service7', 'service8', 'service9']
Playbook:
- name: write deployment hosts
hosts: localhost
vars:
deployment_hosts: absent
vars_files:
- ./group_vars/vars.yaml
tasks:
- set_fact:
modified_repos: (small regex filter to find modified repository)
- set_fact:
deployment_hosts: "{{ item }}"
when: '{{ modified_repos }} in {{ item }}'
with_list:
- "{{ staging_hosts }}"
- name: connect to Cluster
hosts: "{{ hostvars['localhost']['deployment_hosts'] }}"
What can I do against this warning and error?
[WARNING]: conditional statements should not include jinja2 templating
delimiters such as {{ }} or {% %}. Found: {{ modified_repos }} in {{ item }}
fatal: [localhost]: FAILED! => {"msg": "The conditional check '{{ modified_repos }} in {{ item }}' failed. True {% else %} False {% endif %}): unhashable type: 'list'
Oh I forgot to mention. It is important, that deployment_hosts could also contain two hosts if modified repos include for example service1 and service4.
Q: "deployment_hosts could also contain two hosts if modified repos include for example service1 and service4."
A: Use intersect filter. For example, the playbook
- hosts: localhost
vars:
staging_hosts:
server1: ['service1', 'service2', 'service3']
server2: ['service4', 'service5', 'service6']
server3: ['service7', 'service8', 'service9']
modified_repos: ['service1', 'service4']
tasks:
- set_fact:
deployment_hosts: "{{ deployment_hosts|default([]) + [item.key] }}"
loop: "{{ staging_hosts|dict2items }}"
when: modified_repos|intersect(item.value)|length > 0
- debug:
var: deployment_hosts
gives
deployment_hosts:
- server1
- server2

generate a host list from ansible ini file

It is not possible to read this file with the ini plugin.
$ cat hosts
[webservers]
www[01:50].example.com
The play
- hosts: localhost
tasks:
- debug:
msg: "{{ item }}"
with_ini:
- '.* section=webservers file=hosts re=True'
gives
ok: [localhost] => (item=11].example.com) => {
"msg": "11].example.com"
}
Is it possible to generate a host list like this?
[webservers]
www01.example.com
www02.example.com
www03.example.com
www04.example.com
www05.example.com
www06.example.com
Q: "Is it possible to generate a host list like ...?"
A: Yes. Use template. For example
$ cat hosts
[webservers]
www[01:50].example.com
$ cat play.yml
- hosts: localhost
vars:
my_group: webservers
tasks:
- template:
src: hosts-template.j2
dest: /etc/ansible/hosts-webservers
$ cat hosts-template.j2
[{{ my_group }}]
{% for my_host in groups[my_group] %}
{{ my_host }}
{% endfor %}
Notes
Ranges in the inventory will be expanded.
If your ansible hosts inventory file is in a non standard location (i.e. not in /etc/ansible/hosts), you will have to load it when launching your playbook: ansible-playbook -i /path/to/inventory/hosts play.yml

Resources