Ansible loop over variables - ansible

i am using ansible to update configuration file of newly added NIC
for that i have defined some variables in separate yml file
/tmp/ip.yml
#first interface
interface1: eth1
bootproto1: static
ipaddress1: 192.168.211.249
netmask1: 255.255.255.0
gateway: 192.168.211.2
DNS1: 192.168.211.2
#second interface
interface2: eth2
bootproto2: static
ipaddress2: 10.0.0.100
netmask2: 255.0.0.0
Playbook
- include_vars: /tmp/ip.yml
- name: configuring interface
lineinfile:
state=present
create=yes
dest=/etc/sysconfig/network-scripts/ifcfg-{{interface1}}
regexp="{{ item.regexp }}"
line="{{ item.line }}"
with_items:
- { regexp: '^BOOTPROTO=.*', line: 'BOOTPROTO={{interface1}}' }
- { regexp: '^IPADDR=.*', line: 'IPADDR={{ipaddress1}' }
- { regexp: '^NETMASK=.*', line: 'NETMASK={{netmask1}}' }
- { regexp: '^GATEWAY=.*', line: 'GATEWAY={{gateway}}' }
- { regexp: '^PEERDNS=.*', line: 'PEERDNS=no' }
- { regexp: '^DNS1=.*', line: 'DNS1={{DNS1}}' }
- { regexp: '^ONBOOT=.*', line: 'ONBOOT={{onboot}}' }
when: bootproto1 == 'static'
- name: configuring for DHCP
lineinfile:
state=present
create=yes
dest=/etc/sysconfig/network-scripts/ifcfg-{{interface1}}
regexp="{{ item.regexp }}"
line="{{ item.line }}"
with_items:
- { regexp: '^BOOTPROTO=.*',line: 'BOOTPROTO={{bootproto1}}' }
- {regexp: '^PEERDNS=.*',line: 'PEERDNS=yes' }
- { regexp: '^ONBOOT=.*', line: 'ONBOOT={{onboot}}' }
when: bootproto1 == 'dhcp'
similarly repeated for second interface.
Even Though this method works for 2 NIC,this is too difficult to manage ,that is for each new NIC added i need to modify playbook and update corresponding variable in /tmp/ip.yml.
Is there a way to add variables to /tmp/ip.yml and may be using some separator parse it to playbook with out modifying playbook each time for plugging in new NIC.

There is a lot to say here.
First, try to avoid lineinfile like plague. It is really a last-resort solution. lineinfile makes it hard to write consistent and idempotents playbooks.
Now, since you're trying to populate RH style interface files, it is quite easy to do.
Organize your variables
The first thing to do is to have a proper structure for your variables. You'll want to loop over your interfaces so you have to make stuff 'loopable'. Having interface1, interface2 ... interfaceN is not scalable as you mentioned.
Here is a suggestion :
interfaces_ipv4:
- name: eth0
bootproto: static
ipaddress: 192.168.211.249
netmask: 255.255.255.0
gateway: 192.168.211.2
dns: 192.168.211.2
- name: eth2
bootproto: static
ipaddress: 10.0.0.100
netmask: 255.0.0.0
Write your template
Now that you have your data, you need a template to create your OS config file.
BOOTPROTO={{item.bootproto}}
IPADDR={{item.ipaddress}}
NETMASK={{item.netmask}}
{% if item.gateway is defined %}
GATEWAY={{item.gateway}}
{% endif %}
PEERDNS=no
DNS1={{item.dns}}
ONBOOT={{item.onboot|default('no')}}
I included two variations : you can skip outputting a line when it's not set ({% if ... %} construct) or provide default values (for instance {{item.onboot|default('no')}}).
Your mileage may vay, depending if you want to use a default or to skip with the if construct.
Create a task
Finally, here is a task that will create interface configuration files for each interface :
- name: Push template
template:
src=/path/to/the/above/template.j2
dest=/etc/sysconfig/network-scripts/ifcfg-{{item.name}}.cfg
with_items:
- "{{ interfaces_ipv4 }}"
This should do it all.
Of course, best way to use this task is to add it to some "network" role, and call it from a playbook.
Good luck.

Related

Ansible task with switching variables

I'm trying to create ansible playbook that will use variables if they are defined without using "while:" and manually typing the undefined variables & duplicating tasks.
For example I have the below variables:
vars:
service_List:
- 1:
state: present
address_type: ipv4
ip: 10.0.0.0
- 2:
state: present
jump: true
ip: 10.5.5.0
hold_true: yes
- 3:
state: present
address_type: ipv4
is_enabled: true
dhcp: none
I want to have a single task that will use the above variables on a specific module.
example of a task: (notice the with_dict)
tasks:
- name: task name here
some_module:
**This here will include the code for adding the variables form vars**
**So for example, for 1st dict it will include state, address_type and ip**
**for 2nd dict it will include variables state,jump,ip,hold_true**
**example: state: "{{ item.value.state }}"
with_dict: "{{ service_List }}"
Please help with missing code inside the task
It depends on the some_module use case. In particular, whether the parameters are required or not. And, if required, whether there is a default value or not. There are three options if a parameter is missing in the dictionary
The parameter is not required. Use default(omit)
The parameter is required. Use default(defaul_value_of_this_param)
The parameter is required but there is no default. The module will crash.
For example,
tasks:
- name: task name here
some_module:
state: "{{ item.value.state }}"
address_type: "{{ item.value.address_type|default('ipv4') }}"
ip: "{{ item.value.ip|default(omit) }}"
jump: "{{ item.value.jump|default(False) }}"
hold_true: "{{ item.value.hold_true|default(omit) }}"
with_dict: "{{ service_List }}"
In addition to this, you can use Module defaults.

Ansible loop over inventory file (two dimensional list)

I would like to parse a two dimensional list from the inventory file with ansible playbook
Inventory file: .ini would have a list of macs and IPs
mac1=b8:27:eb:12:53:1b ip1=192.168.8.101
mac2=b8:27:eb:f1:65:32 ip2=192.168.8.102
...
and the ansible task would be to add a line everytime in the `/etc/ethers``file in this form
b8:27:eb:f1:65:32 192.168.8.102
this is the task
- name: Assign static IPs to MACs
lineinfile:
path: /etc/ethers
line: "{{ mac }} {{ ip }}"
mode: 0644
loop: "{{ listname }}"
become: yes
Any recommendations please on how to set my list in the inventory that it will work with the playbook?
Thank you!
I would add the list as a variable in group_var folder or in your playbook.
list:
- mac: b8:27:eb:12:53:1b
ip: 192.168.8.101
- mac: b8:27:eb:f1:65:32
ip: 192.168.8.102
Your task can then look something like this:
- name: Assign static IPs to MACs
lineinfile:
path: /etc/ethers
line: "{{ item.mac }} {{ item.ip }}"
mode: 0644
loop: "{{ list }}"
become: yes

Use Ansible to ensure a file exists, ignoring any extra lines

I'm trying to update the sssd.conf file on about 200 servers with a standardized configuration file, however, there is one possible exception to the standard. Most servers will have a config that looks like this:
[domain/domainname.local]
id_provider = ad
access_provider = simple
simple_allow_groups = unixsystemsadmins, datacenteradmins, sysengineeringadmins, webgroup
default_shell = /bin/bash
fallback_homedir = /export/home/%u
debug_level = 0
ldap_id_mapping = false
case_sensitive = false
cache_credentials = true
dyndns_update = true
dyndns_refresh_interval = 43200
dyndns_update_ptr = true
dyndns_ttl = 3600
ad_use_ldaps = True
[sssd]
services = nss, pam
config_file_version = 2
domains = domainname.local
[nss]
[pam]
However, on some servers, there's an additional line after simple_allow_groups called simple_allow_users, and each server that has this line has it configured for specific users to be allowed to connect without being a member of an LDAP group.
My objective is to replace the sssd.conf file on all servers, but not to remove this simple_allow_users line, if it exists. I looked into lineinfile and blockinfile, but neither of these seems to really handle this exception. I'm thinking I'm going to have to check the file for the existance of the line, store it to a variable, push the new file, and then add the line back, using the variable afterwards, but I'm not entirely sure if this is the best way to handle it. Any suggestions on the best way to accomplish what I'm looking to do?
Thanks!
I would do the following
See if the simple_allow_users exists in the current sssd.conf file
Change your model configuration to add the current value of the line simple_allow_users is exists
overwrite the sssd.conf file with the new content
You can use jinja2 conditional to achieve step 2 https://jinja2docs.readthedocs.io/
I beleive the above tasks will solve what you need, just remember to test on a simngle host and backup the original file just for good measure ;-)
- shell:
grep 'simple_allow_users' {{ sssd_conf_path }}
vars:
sssd_conf_path: /etc/sssd.conf
register: grep_result
- set_fact:
configuration_template: |
[domain/domainname.local]
id_provider = ad
access_provider = simple
simple_allow_groups = unixsystemsadmins, datacenteradmins, sysengineeringadmins, webgroup
{% if 'simple_allow_users' in grep_result.stdout %}
{{ grep_result.stdout.rstrip() }}
{% endif %}
default_shell = /bin/bash
..... Rest of your config file
- copy:
content: "{{ configuration_template }}"
dest: "{{ sssd_conf_path }}"
vars:
sssd_conf_path: /etc/sssd.conf
I used Zeitounator's tip, along with this question Only check whether a line present in a file (ansible)
This is what I came up with:
*as it turns out, the simple_allow_groups are being changed after the systems are deployed (thanks for telling the admins about that, you guys... /snark for the people messing with my config files)
---
- name: Get Remote SSSD Config
become: true
slurp:
src: /etc/sssd/sssd.conf
register: slurpsssd
- name: Set simple_allow_users if exists
set_fact:
simpleallowusers: "{{ linetomatch }}"
loop: "{{ file_lines }}"
loop_control:
loop_var: linetomatch
vars:
- decode_content: "{{ slurpsssd['content'] | b64decode }}"
- file_lines: "{{ decode_content.split('\n') }}"
when: '"simple_allow_users" in linetomatch'
- name: Set simple_allow_groups
set_fact:
simpleallowgroups: "{{ linetomatch }}"
loop: "{{ file_lines }}"
loop_control:
loop_var: linetomatch
vars:
- decode_content: "{{ slurpsssd['content'] | b64decode }}"
- file_lines: "{{ decode_content.split('\n') }}"
when: '"simple_allow_groups" in linetomatch'
- name: Install SSSD Config
copy:
src: etc/sssd/sssd.conf
dest: /etc/sssd/sssd.conf
owner: root
group: root
mode: 0600
backup: yes
become: true
- name: Add simple_allow_users back to file if it existed
lineinfile:
path: /etc/sssd/sssd.conf
line: "{{ simpleallowusers }}"
insertafter: "^simple_allow_groups"
when: simpleallowusers is defined
become: true
- name: Replace simple allow groups with existing values
lineinfile:
path: /etc/sssd/sssd.conf
line: "{{ simpleallowgroups }}"
regexp: "^simple_allow_groups"
backrefs: true
when: simpleallowgroups is defined
become: true

Loop variable is not appending file

When i use blockinfile loop to append /etc/environment file it only adds last key and value of item from loop variable rather then adding all of it.
I am trying to modify files using Blockinfile module in roles main.yml:
- name: Add proxy to global /etc/environments
blockinfile:
path: "/etc/environment"
block: |
export {{item.key}}={{item.value}}
loop: "{{proxy_details}}"
my vars/main.yaml looks like this:
proxy_details:
- key: http_proxy
value: "http://"{{ProxyHost}}":"{{ProxyPort}}""
- key: https_proxy
value: "http://"{{ProxyHost}}":"{{ProxyPort}}""
my group_vars/all looks like this:
ProxyHost: test.com
ProxyPort: 9999
See the last example in the documentaion at https://docs.ansible.com/ansible/latest/modules/blockinfile_module.html. You need to use a custom marker for each item so Ansible knows where each one is in the file to replace it.
Per documentation note:
When using ‘with_*’ loops be aware that if you do not set a unique mark the block will be overwritten on each iteration.
The example is:
- name: Add mappings to /etc/hosts
blockinfile:
path: /etc/hosts
block: |
{{ item.ip }} {{ item.name }}
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.name }}"
loop:
- { name: host1, ip: 10.10.1.10 }
- { name: host2, ip: 10.10.1.11 }
- { name: host3, ip: 10.10.1.12 }
You could modify yours to be:
- name: Add proxy to global /etc/environments
blockinfile:
path: "/etc/environment"
marker: "# {mark} ANSIBLE MANAGED BLOCK FOR {{item.key}}"
block: |
export {{item.key}}={{item.value}}
loop: "{{proxy_details}}"

Ansible recursive checks in playbooks

We need to go through this structure
Zone spec
https://gist.github.com/git001/9230f041aaa34d22ec82eb17d444550c
I was able to run the following snipplet but now I'm stucked at the error checking.
playbook
--
- hosts: all
gather_facts: no
vars_files:
- "../doc/application-zone-spec.yml"
roles:
- { role: ingress_add, customers: "{{ application_zone_spec }}" }
role
- name: check if router exists
shell: "oc get dc -n default {{ customers.zone_name }}-{{ item.type }}"
with_items: "{{ customers.ingress }}"
ignore_errors: True
register: check_router
- name: Print ingress hostnames
debug: var=check_router
- name: create new router
shell: "echo 'I will create a router'"
with_items: "{{ customers.ingress }}"
when: check_router.rc == 1
Output of a ansible run
https://gist.github.com/git001/dab97d7d12a53edfcf2a69647ad543b7
The problem is that I need to go through the ingress items and I need to map the error of the differnt types from the "check_router" register.
It would be nice to make something like.
Pseudo code.
Iterate through the "customers.ingress"
check in "check_router" if the rc is ! 0
execute command.
We use.
ansible-playbook --version
ansible-playbook 2.1.0.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
You can replace the second loop with:
- name: create new router
shell: "echo 'I will create a router with type {{ item.item }}'"
with_items: "{{ check_router.results }}"
when: item.rc == 1
This will iterate over every step of check_route loop and you can access original items via item.item.

Resources