When Condition with Ansible - ansible

I am new to Ansible, and I am writing a script to install a package when disk space is more then a limit. I am getting error like this >> error while evaluating conditional
---
- hosts: dev
become: true
become_user: root
tasks:
- name: Install zsh if enough space
yum:
name: zsh
state: latest
with_items: "{{ ansible_mounts}}"
when: item.mount == "/" and item.size_available > 10737400
I am giving the size in bytes. ( Is there a way to give the size in MB ? )
Thanks.

Ansible uses the YAML format, you need to use the right indent.
In YAML, the indent is important as closing brackets or semicolons in most programming languages.
with_items is not a definition for the yum module, it is a directive for Ansible, so it should be at the same level as when and the module call (e.g. yum). Both examples below should work:
---
- hosts: dev
become: true
become_user: root
tasks:
- name: Install zsh if enough space
yum:
name: zsh
state: latest
with_items: "{{ ansible_mounts }}"
when: item.mount == "/" and item.size_available > 10737400
or
---
- hosts: dev
become: true
become_user: root
tasks:
- name: Install zsh if enough space
with_items: "{{ ansible_mounts }}"
when: item.mount == "/" and item.size_available > 10737400
yum:
name: zsh
state: latest

Related

Adding entry to end of line Ansible SLES 12

On SLES 12 server. Trying to append to this entry in my /etc/security/pam_winbind.conf file with this extra entry S-1-5-21-84296906-944397292-530207130-587119.
The line is
require_membership_of=S-1-5-21-84296906-944397292-530207130-496773,S-1-5-21-84296906-944397292-530207130-71056,S-1-5-21-84296906-944397292-530207130-218591
My playbook
---
- name: Configuring ad_access_filter for RHEL systems.
hosts: smt-test
become: yes
tasks:
- name: Taking Backup.
copy:
src: /etc/security/pam_winbind.conf
dest: /etc/security/pam_winbind.conf.backup
remote_src: yes
- name: Add HQCloud to the sssd.conf file
lineinfile:
path: /etc/security/pam_winbind.conf
backrefs: yes
regexp: '(^*2185915*)$'
line: '\1,S-1-5-21-84296906-944397292-530207130-587119'
- name: Add HQCloudScapeSupp to the sudoers file.
lineinfile:
path: /etc/sudoers
line: 'HQCloudScapeSupp ALL=(ALL) NOPASSWD: ALL'
- name: Restarting WinBind Service
service:
name: winbind
state: restarted
Since the pam_winbind.conf will be different on each server, how do I just add that entry to the end of that line regardless of the other memberships?
There are a few problems with your approach IMO
It might be possible to do add your membership line with only a regex and backrefences but achieving idempotence will be a real pain. Indeed, you actually need to add your required membership if it does not already exist anywhere in the string (it might be present but not in last position). If it is already present anywhere, you should not touch anything.
You are making a backup of your file separately where the lineinfile module can do this automatically for you and only when there is a change
you are unconditionally restarting your service where it should only restart when something has actually changed requiring a restart.
The below playbook addresses the above issues:
---
- name: Configuring ad_access_filter for RHEL systems.
hosts: smt-test
become: yes
vars:
config_file: /etc/security/pam_winbind.conf
required_member: S-1-5-21-84296906-944397292-530207130-587119
search_needle: require_membership_of=
search_regexp: "^{{ search_needle }}(.*)$"
tasks:
- name: slurp file content to get existing membership entries
slurp:
path: "{{ config_file }}"
register: slurped_file
- name: Add HQCloud to the sssd.conf file if it does not exist + backup if any change
vars:
file_content_lines: "{{ (slurped_file.content | b64decode).splitlines() }}"
requirement_line: "{{ file_content_lines | select('match', search_needle) | first }}"
existing_members: "{{ (requirement_line | regex_replace(search_regexp, '\\g<1>')).split(',') | map('trim') }}"
wanted_members: "{{ existing_members | union([required_member]) }}"
lineinfile:
path: "{{ config_file }}"
regexp: "{{ search_regexp }}"
backup: true
line: "{{ search_needle }}{{ wanted_members | join(',') }}"
- name: Add HQCloudScapeSupp to the sudoers file.
lineinfile:
path: /etc/sudoers
line: 'HQCloudScapeSupp ALL=(ALL) NOPASSWD: ALL'
# Not really sure this is needed
notify: Restart winbind
handlers:
- name: Restart winbind
service:
name: winbind
state: restarted

Where to put (-) hyphen in yaml ansible play books?

I am very confused, in the play book where actually we put single hyphen (-). I found similar threads here, but still confused, so decided to draft one new. I have read it will be used to indicate start of a list item. again i have difficulty in understanding where is the start of list and where is start of dictionary.
Can some experts explain me where should i put hyphen in below code. and why is that?
---
connection: local
gather_facts: false
hosts: rtr
tasks:
name: "read configs"
read_csv:
path: "{{ aws_config }}"
register: aws_requests
run_once: true
debug:
msg: "{{ aws_requests.list }}"
name: "display awsconfigs requests"
run_once: true
name: "set awsconfigs requests"
run_once: true
set_fact:
aws_configs: "{{ aws_requests.list }}"
name: "build template"
template:
dest: "{{ config_filename }}"
lstrip_blocks: true
src: "{{ template }}"
I recommend that you read the "Intro to playbooks", which should answer your questions, but below is a summary.
As you noted correctly, hyphens are list items in YAML. YAML documents start with ---, which is why there are hyphens at the start of the file.
The starting point for any Ansible playbook is the playbook itself in the file. The playbook file itself may contain one or more so-called "plays", each as its own list element. Each play typically contains a hosts and a tasks part. In many playbooks, there is just one "play", so your typical minimal playbook looks like this:
---
- hosts: webservers
tasks:
- name: Task 1
...
As you can see above, each "play" then has a list of tasks, each starting with a hyphen. So in the following example there are two tasks, each with a name and the module (yum and service in this case):
---
- hosts: webservers
tasks:
- name: ensure apache is at the latest version
yum:
name: httpd
state: latest
- name: ensure apache is running
service:
name: httpd
state: started
Each Ansible module has different arguments, so you'll need to check the modules documentation for each one how to specify these arguments.
So the correct version for your playbook above would look like this:
---
- connection: local
gather_facts: false
hosts: rtr
tasks:
- name: "read configs"
read_csv:
path: "{{ aws_config }}"
register: aws_requests
run_once: true
- name: "display awsconfigs requests"
debug:
msg: "{{ aws_requests.list }}"
run_once: true
- name: "set awsconfigs requests"
run_once: true
set_fact:
aws_configs: "{{ aws_requests.list }}"
- name: "build template"
template:
dest: "{{ config_filename }}"
lstrip_blocks: true
src: "{{ template }}"

ansible error 'first argument must be string or compiled pattern'

I have this code in my playbook:
- hosts: standby
remote_user: root
tasks:
- name: replace hostname in config
replace:
path: /opt/agentd.conf
regexp: #\s+Hostname\=
replace: Hostname={{hname}}
backup: yes
- name: add database array in files
lineinfile:
path: /opt/zabbix_agent/share/scripts/{{ item }}
line: 'DBNAME_ARRAY=( {{dbname}} )'
insertafter: DB2PATH=/home/db2inst1/sqllib/bin/db2
backup: yes
with_items:
- Connections
- HadrAndLog
- Memory
- Regular
- name: restart service
shell: /etc/init.d/agent restart
register: command_output
become: yes
become_user: root
tags: restart
- debug: msg="{{command_output.stdout_lines}}"
tags: set_config_st
it will replace # Hostname= in a config file with Hostname= givenhostname and add an array in 4 scripts. array is the name of given database. then it will restart the agent to apply the changes.
when i run this command:
ansible-playbook -i /Ansible/inventory/hostfile /Ansible/provision/nconf.yml --tags set_config_st --extra-vars "hname=fazi dbname=fazidb"
i get this error:
first argument must be string or compiled pattern
i searched a bit but couldn't find the reason. what should i do?
The problem is in this line:
regexp: #\s+Hostname\=
You have to quote the regex because YAML comments start with #, so everything after the # will be ignored by ansible and that is why the error message occures.
So the correct line should be:
regexp: '#\s+Hostname\='
or
regexp: "#\s+Hostname\="
I think the problem is with indention. Please try as below.
- hosts: standby
remote_user: root
tasks:
- name: replace hostname in config
replace:
path: /opt/agentd.conf
regexp: #\s+Hostname\=
replace: Hostname={{hname}}
backup: yes
- name: add database array in files
lineinfile:
path: /opt/zabbix_agent/share/scripts/{{ item }}
line: 'DBNAME_ARRAY=( {{dbname}} )'
insertafter: DB2PATH=/home/db2inst1/sqllib/bin/db2
backup: yes
with_items:
- Connections
- HadrAndLog
- Memory
- Regular
- name: restart service
shell: /etc/init.d/agent restart
register: command_output
become: yes
become_user: root
tags: restart
- debug: msg="{{command_output.stdout_lines}}"
tags: set_config_st

How to use nested loops with sequence and a list and how get around of curly braces in Ansible

I have a code to backup config using the ios_config module. I used ios_facts to get the hostname of devices and I want to use it to put the backup file in a similarly named folder and also use it in the file name itself.
In the last task of my code, I need to loop through two items - the sequence from 0 to 1(or how many items are in my inventory) as I need to access the hostname in the results and use it in the backup options, and also loop through my inventory of devices which I extracted from a csv file. I am aware of the rule of double curly braces but I do not know how to get around it.
---
- hosts: localhost
gather_facts: false
tasks:
- name: Block
block:
- name: Use CSV
csv_to_facts:
src: '{{playbook_dir}}/NEW/Inventory.csv'
vsheets:
- INFO:
- IP
- OS
- debug:
msg: '{{item.IP}}'
loop: '{{INFO}}'
- name: Create Inventory
add_host:
hostname: '{{item.IP}}'
ansible_network_os: '{{item.OS}}'
ansible_user: cisco
ansible_ssh_pass: cisco
ansible_connection: network_cli
ansible_become: yes
ansible_become_method: enable
groups: group_01
loop: '{{INFO}}'
- name: Gather Facts (IOS)
ios_facts:
register: ios_facts_loop
delegate_to: '{{item}}'
loop: "{{groups['group_01']}}"
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname }}"
filename: "{{ios_facts_loop.results.item{{[0]}}.ansible_facts.ansible_net_hostname}} {{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S')}}"
register: backup_ios_location
delegate_to: '{{item[1]}}'
loop:
- with_sequence: "0-{{output|length - 3}}"
- "{{groups['group_01']}}"
TLDR; for vars notation
You cannot add double curly braces inside double curly braces like in your above code. You current var reference:
ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname
should be turned to
ios_facts_loop.results[item[0]].ansible_facts.ansible_net_hostname
# or equivalent
ios_facts_loop.results[item.0].ansible_facts.ansible_net_hostname
Meanwhile, this will only fix your current syntax error (that you didn't share in your question) as the first element in your loop is a string 'with_sequence: "0-X"' which therefore has no index 0.
Attempt to fix the logic
If I understand correctly, for your last task, you just need to loop over the results of your ios_facts register and delegate the task to the server it was taken from. Luckilly, you should already have all the info you need in ios_facts_loop.results
It is a list so you can directly loop over it
Each element should contain an item key with the actual item that was used in the previous run at time of register (i.e. one of your groups['group_01'] element).
So I would try to write your last task like this. Disclaimer this is a pure guess as I didn't see your exact datastructure.
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ item.ansible_facts.ansible_net_hostname }}"
filename: "{{ item.ansible_facts.ansible_net_hostname}}{{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S')}}"
register: backup_ios_location
delegate_to: '{{item.item}}'
loop: "{{ ios_facts_loop.results }}"
Going further.
I'm not really familiar with the ios_* modules but they should be really close to other stuff I use daily and I think you could really simplify your playbook taking advantage of more ansible feature (e.g. multiple plays in a playbook). I believe the following should actually do the job:
---
- name: Construct inventory from CSV
hosts: localhost
gather_facts: false
tasks:
- name: Use CSV
csv_to_facts:
src: '{{playbook_dir}}/NEW/Inventory.csv'
vsheets:
- INFO:
- IP
- OS
- name: Create Inventory
add_host:
hostname: '{{item.IP}}'
ansible_network_os: '{{item.OS}}'
ansible_user: cisco
ansible_ssh_pass: cisco
ansible_connection: network_cli
ansible_become: yes
ansible_become_method: enable
groups: group_01
loop: '{{INFO}}'
- name: Backup switches from created inventory
hosts: group_01
gather_facts: false
tasks:
- name: Get facts from network os
ios_facts:
gather_subset: all
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ ansible_net_hostname }}"
filename: "{{ ansible_net_hostname }}{{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S') }}"
More background on dot and brackets notation for vars
You can basically navigate a yaml datastructure with two notation which are equivalent.
the dot notation
a_list_var.index_number
a_hasmap_var.keyname
the brackets notation
a_list_var[index_number]
a_hashmap_var['key_name']
If we take the following example:
my_servers:
hostA:
ips:
- x.x.x.x
- y.y.y.y
env:
shell: bash
home: somewhere
hostB:
ips:
- a.a.a.a
- b.b.b.b
env:
shell: sh
home: elsewhere
The following notation are all strictly equivalent:
# all vars of hostA
hostA_vars: "{{ my_servers.hostA }}"
hostA_vars: "{{ my_server['hostA'] }}"
# first IP of hostB
hostB_ip: "{{ my_servers.hostB.0 }}"
hostB_ip: "{{ my_servers.hostB[0] }}"
hostB_ip: "{{ my_servers['hostB'].0 }}"
hostB_ip: "{{ my_servers['hostB'][0] }}"
As you can see, the dot notation tends to be less verbose and more readable. Meanwhile, you cannot use a variable identifier with the dot notation. So If you want to ave the home env of a variable server you would have to use:
# set a var for server
server: hostA
# all equivalent again
server_home: "{{ my_servers[server].env.home }}"
server_home: "{{ my_servers[server]['env'].home }}"
server_home: "{{ my_servers[server].env['home'] }}"
server_home: "{{ my_servers[server]['env']['home'] }}"

When conditional from ssh command line in Ansible Role

I am new to ansible so any help would be appreciated.
I need to check if my remote Centos Servers have a writable /boot before I try and push VMware tools to them . Install will fail if it's read-only . How do I add another WHEN for this raw Linux command? I know if have to use register or standard out, but I cannot find examples to guide me .
RAW Linux Would be >
mount | grep boot
And I need to catch rw, the target must not be ro like in this example
>
/dev/sda1 on /boot type ext4 (ro,relatime,data=ordered)
I tried adding a task under the block like in the ansible documentation.
- name: Catch Targets with read only boot
tasks:
- command: mount | grep boot
register: boot_mode
- shell: echo "motd contains the word hi"
when: boot_mode.stdout.find('ro') != -1
---
- name: Wrapper for conditional tasks
block:
- name: Copy Files from Mirror to Remote Guest
get_url:
url: "{{ item }}"
dest: /tmp
owner: root
group: root
with_items:
- http://mirror.compuscan.co.za/repo/vmwaretools65u2/CentOS7/VMwareTools-10.3.5-10430147.tar.gz
- name: UnTAR the installer
unarchive:
src: /tmp/VMwareTools-10.3.5-10430147.tar.gz
dest: /tmp
remote_src: yes
- name: Run the PL install
become: yes
command: /tmp/vmware-tools-distrib/vmware-install.pl -d
- name: Perform Clean Up
file:
state: absent
path: "{{ item }}"
with_items:
- /tmp/vmware-tools-distrib/
- /tmp/VMwareTools-10.3.5-10430147.tar.gz
- name: Report on success or failure
service:
name: vmware-tools
state: started
enabled: yes
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'
ignore_errors: yes
I want the role/playbook to ignore Targets in read-only /boot mode.
Put stat task in front of the block
- stat:
path: /boot
register: boot_mode
Then add the condition to execute the block if /boot is writeable
when:
- boot_mode.stat.writeable
- ansible_distribution == 'CentOS'
- ansible_distribution_major_version == '7'

Resources